├── .editorconfig
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── elm-package.json
├── examples
├── build
│ └── index.html
├── elm-package.json
├── src
│ ├── Reducer.elm
│ ├── components
│ │ ├── button.js
│ │ └── layout.js
│ ├── containers
│ │ ├── counter.js
│ │ └── ticktock.js
│ └── index.js
└── webpack.config.js
├── logo
├── logo.ai
├── logo.png
└── logo.svg
├── package.json
├── src
├── Redux.elm
└── index.js
└── test
├── middleware.js
├── reducer.js
└── runMiddleware.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # elm-package generated files
2 | elm-stuff/
3 | # elm-repl generated files
4 | repl-temp-*
5 | node_modules
6 | index.js
7 | npm-debug.log
8 | coverage
9 | .nyc_output
10 | coverage.lcov
11 | examples/build
12 | !examples/build/index.html
13 | Native
14 | index.html
15 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | before_install:
2 | git config user.name "Travis CI"
3 |
4 | language:
5 | node_js
6 |
7 | node_js:
8 | - stable
9 |
10 | script:
11 | - npm test
12 |
13 | after_script: "npm run codecov"
14 |
15 | sudo: false
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Christoph Hermann
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # redux-elm-middleware
2 |
3 | > Elm middleware for redux :sparkles:
4 |
5 |
6 |
7 | [](https://travis-ci.org/stoeffel/redux-elm-middleware)
8 | [](https://codecov.io/gh/stoeffel/redux-elm-middleware)
9 | [](https://david-dm.org/stoeffel/redux-elm-middleware)
10 | [](https://badge.fury.io/js/redux-elm-middleware)
11 |
12 | ## Installation
13 |
14 | You need to install redux-elm-middleware for js and elm.
15 |
16 | ```bash
17 | $ npm i redux-elm-middleware -S
18 | ```
19 |
20 | Redux-elm-middleware is currently only published to npm.
21 | You will need to add the following to you `elm-package.json`
22 |
23 | ```json
24 | "source-directories": ["node_modules/redux-elm-middleware/src", ...]
25 | ```
26 |
27 | ## Usage
28 |
29 | ### Setup Redux Middleware
30 |
31 | ```js
32 | import createElmMiddleware from 'redux-elm-middleware'
33 | import { reducer as elmReducer } from 'redux-elm-middleware'
34 |
35 | // Import your Elm Reducer
36 | import Elm from '../build/elm'
37 |
38 | const reducer = combineReducers({
39 | elm: elmReducer
40 | // ...middlewares
41 | })
42 |
43 |
44 | // create a worker of your elm reducer
45 | const elmStore = Elm.Reducer.worker();
46 |
47 | // create the middleware
48 | const { run, elmMiddleware } = createElmMiddleware(elmStore)
49 |
50 | // create the redux store and pass the elmMiddleware
51 | const store = createStore(reducer, {}, compose(
52 | applyMiddleware(elmMiddleware),
53 | window.devToolsExtension ? window.devToolsExtension() : f => f
54 | ));
55 |
56 | // you need to run the elm middleware and pass the redux store
57 | run(store)
58 | ```
59 |
60 | #### Elm root reducer
61 |
62 | The root reducer from redux-elm-middleware simply takes all actions from your elm reducers and returns the payload as the next state.
63 |
64 | The new model returned in your elm reducers update function is dispatched as a new action to the redux store.
65 |
66 | f.e.
67 |
68 | ```js
69 | {
70 | type: '@@elm/Increment',
71 | payload: {
72 | counter: 3
73 | }
74 | }
75 | ```
76 |
77 |
78 | ### Creating a Reducer in Elm
79 |
80 | A reducer in elm looks like a normal [TEA](https://github.com/evancz/elm-architecture-tutorial) module without the view.
81 | ```elm
82 | port module Reducer exposing (Model, Msg, init, update, subscriptions) -- Name of the module must match the worker
83 |
84 |
85 | import Redux
86 |
87 | -- define ports for all actions which should be handled by the elm reducer
88 | port increment : ({} -> msg) -> Sub msg
89 |
90 | -- define all subscriptions of your reducer
91 | subscriptions : Model -> Sub Msg
92 | subscriptions _ =
93 | Sub.batch
94 | [ increment <| always Increment
95 | -- ...
96 | ]
97 |
98 | -- In order for the Elm model to cross the border safely it must be encoded as a JSON value.
99 | encode : Model -> Json.Encode.Value
100 | encode model =
101 | ...
102 |
103 | -- MODEL
104 | -- UPDATE
105 |
106 | -- START THE REDUCER
107 | main =
108 | Redux.program
109 | { init = init
110 | , update = update
111 | , encode = encode
112 | , subscriptions = subscriptions
113 | }
114 | ```
115 |
116 |
117 | ## Motivation
118 |
119 | * write bulletproof businesslogic
120 | * handle state and effects
121 | * pure
122 | * in one place
123 | * with a safetynet
124 | * still have the rich react/redux ecosystem at your paws
125 | * components
126 | * middlewares
127 | * routing
128 | * persistent state (localstorage)
129 | * offline support
130 | * ui state ( redux-ui )
131 | * sneak a nice functional language into your projects
132 | * don't have to commit 100% to it
133 | * slowly convert a redux/react app into elm
134 |
135 | ## Running the Example
136 |
137 | * `npm install`
138 | * `npm run example`
139 | * open 127.0.0.1:8080
140 |
141 |
142 | ## Feedback and contributons welcome!
143 |
--------------------------------------------------------------------------------
/elm-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.0.0",
3 | "summary": "Elm middleware for redux",
4 | "repository": "https://github.com/stoeffel/redux-elm-middleware.git",
5 | "license": "BSD3",
6 | "source-directories": [
7 | "src"
8 | ],
9 | "exposed-modules": ["Redux"],
10 | "dependencies": {
11 | "elm-lang/core": "5.0.0 <= v < 6.0.0",
12 | "elm-lang/html": "2.0.0 <= v < 3.0.0"
13 | },
14 | "elm-version": "0.18.0 <= v < 0.19.0"
15 | }
16 |
--------------------------------------------------------------------------------
/examples/build/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/examples/elm-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.0.0",
3 | "summary": "helpful summary of your project, less than 80 characters",
4 | "repository": "https://github.com/stoeffel/redux-elm-middleware.git",
5 | "license": "BSD3",
6 | "source-directories": [
7 | ".",
8 | "../src"
9 | ],
10 | "exposed-modules": [],
11 | "dependencies": {
12 | "elm-lang/core": "5.0.0 <= v < 6.0.0",
13 | "elm-lang/html": "2.0.0 <= v < 3.0.0"
14 | },
15 | "native-modules": true,
16 | "elm-version": "0.18.0 <= v < 0.19.0"
17 | }
18 |
--------------------------------------------------------------------------------
/examples/src/Reducer.elm:
--------------------------------------------------------------------------------
1 | port module Reducer exposing (Model, Msg, init, update, subscriptions)
2 |
3 | import Redux
4 | import Task exposing (..)
5 | import Process
6 | import Time exposing (..)
7 | import Json.Encode as Json exposing (object, string, int)
8 | import Json.Decode exposing (Value)
9 |
10 |
11 | port increment : (Value -> msg) -> Sub msg
12 |
13 |
14 | port asyncIncrement : (Value -> msg) -> Sub msg
15 |
16 |
17 | port asyncDecrement : (Value -> msg) -> Sub msg
18 |
19 |
20 | port decrement : (Value -> msg) -> Sub msg
21 |
22 |
23 | port changeCount : (Payload -> msg) -> Sub msg
24 |
25 |
26 | clock : Sub Msg
27 | clock =
28 | Time.every (second * 5) TickTock
29 |
30 |
31 | subscriptions : Model -> Sub Msg
32 | subscriptions _ =
33 | Sub.batch
34 | [ decrement <| always Decrement
35 | , increment <| always Increment
36 | , asyncIncrement <| always AsyncIncrement
37 | , asyncDecrement <| always AsyncDecrement
38 | , changeCount ChangeCount
39 | , clock
40 | ]
41 |
42 |
43 |
44 | -- MODEL
45 |
46 |
47 | init : Int -> ( Model, Cmd Msg )
48 | init value =
49 | ( { value = value, count = 1, tickTock = "TICK" }, Cmd.none )
50 |
51 |
52 | type alias Model =
53 | { value : Int
54 | , count : Int
55 | , tickTock : String
56 | }
57 |
58 |
59 | encodeModel : Model -> Json.Value
60 | encodeModel { value, count, tickTock } =
61 | object
62 | [ ( "value", int value )
63 | , ( "count", int count )
64 | , ( "tickTock", string tickTock )
65 | ]
66 |
67 |
68 | type alias Payload =
69 | Int
70 |
71 |
72 |
73 | -- ACTIONS
74 |
75 |
76 | type Msg
77 | = NoOp
78 | | TickTock Time
79 | | Increment
80 | | Decrement
81 | | AsyncIncrement
82 | | AsyncDecrement
83 | | ChangeCount Payload
84 |
85 |
86 |
87 | -- UPDATE
88 |
89 |
90 | update : Msg -> Model -> ( Model, Cmd Msg )
91 | update action model =
92 | case action of
93 | Increment ->
94 | ( { model | value = model.value + model.count }, Cmd.none )
95 |
96 | Decrement ->
97 | ( { model | value = model.value - model.count }, Cmd.none )
98 |
99 | AsyncIncrement ->
100 | ( model, asyncTask Increment )
101 |
102 | AsyncDecrement ->
103 | ( model, asyncTask Decrement )
104 |
105 | ChangeCount payload ->
106 | ( { model | count = payload }, Cmd.none )
107 |
108 | TickTock _ ->
109 | (case model.tickTock of
110 | "TICK" ->
111 | ( { model | tickTock = "TOCK" }, Cmd.none )
112 |
113 | "TOCK" ->
114 | ( { model | tickTock = "TICK" }, Cmd.none )
115 |
116 | _ ->
117 | ( model, Cmd.none )
118 | )
119 |
120 | NoOp ->
121 | ( model, Cmd.none )
122 |
123 |
124 | asyncTask : Msg -> Cmd Msg
125 | asyncTask msg =
126 | Process.sleep (2 * Time.second)
127 | |> Task.perform (always msg)
128 |
129 |
130 | main =
131 | Redux.program
132 | { init = init 0
133 | , update = update
134 | , encode = encodeModel
135 | , subscriptions = subscriptions
136 | }
137 |
--------------------------------------------------------------------------------
/examples/src/components/button.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export const Button = ({children, ...props}) => {children}
4 |
--------------------------------------------------------------------------------
/examples/src/components/layout.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'react-router'
3 |
4 | export const Layout = ({children}) => {
5 | return (
6 |
7 |
16 |
17 |
18 |
19 |
20 | {children}
21 |
22 |
23 |
24 |
25 |
26 | );
27 | };
28 |
29 |
--------------------------------------------------------------------------------
/examples/src/containers/counter.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { connect } from 'react-redux'
3 | import { Button } from '../components/button'
4 |
5 | export const Counter = connect(
6 | ({elm}) => ({ value: elm.value, count: elm.count })
7 | )
8 | (function({dispatch, value = 0, count = 1}) {
9 | return (
10 |
11 |
12 |
13 |
{value}
14 |
15 |
16 | dispatch({type: 'CHANGE_COUNT', payload: Number(target.value)})} />
17 |
18 | );
19 | });
20 |
21 |
--------------------------------------------------------------------------------
/examples/src/containers/ticktock.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { connect } from 'react-redux'
3 |
4 | export const TickTock = connect(
5 | ({elm}) => ({ tickTock: elm.tickTock })
6 | )
7 | (function({tickTock}) {
8 | return (
9 |
10 |
{tickTock}
11 | ticks every 5 sec
12 |
13 | );
14 | });
15 |
16 |
--------------------------------------------------------------------------------
/examples/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import { Provider } from 'react-redux'
4 | import { createStore, applyMiddleware, combineReducers } from 'redux'
5 | import { compose } from 'ramda'
6 | import { Router, Route, IndexRoute, browserHistory } from 'react-router'
7 | import { syncHistoryWithStore, routerReducer } from 'react-router-redux'
8 | import { Layout } from './components/layout'
9 | import { TickTock } from './containers/ticktock'
10 | import { Counter } from './containers/counter'
11 | import createElmMiddleware from 'redux-elm-middleware'
12 | import { reducer as elmReducer } from 'redux-elm-middleware'
13 | import Elm from '../build/elm'
14 |
15 | const reducer = combineReducers({
16 | elm: elmReducer
17 | , routing: routerReducer
18 | })
19 |
20 | const elmStore = Elm.Reducer.worker();
21 |
22 | const { run, elmMiddleware } = createElmMiddleware(elmStore)
23 | const store = createStore(reducer, {}, compose(
24 | applyMiddleware(elmMiddleware),
25 | window.devToolsExtension ? window.devToolsExtension() : f => f
26 | ));
27 | run(store)
28 |
29 | const history = syncHistoryWithStore(browserHistory, store)
30 |
31 | ReactDOM.render(
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | , document.getElementById('app'));
42 |
--------------------------------------------------------------------------------
/examples/webpack.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack')
2 | const path = require('path')
3 | const R = require('ramda')
4 |
5 | const PATHS = {
6 | build: path.join(__dirname, 'build'),
7 | src: path.join(__dirname, 'src', 'index.js')
8 | }
9 |
10 | module.exports = {
11 | entry: ['babel-polyfill', PATHS.src],
12 | output: {
13 | path: PATHS.build,
14 | filename: 'bundle.js'
15 | },
16 | resolve: {
17 | // add alias for application code directory
18 | alias:{
19 | 'redux-elm-middleware': path.resolve( __dirname, '..', 'src')
20 | },
21 | extensions: [ '', '.js' ]
22 | },
23 | externals: [{ 'window': 'window' }],
24 | module: {
25 | loaders: [{
26 | test: /\.js$/,
27 | loader: 'babel-loader',
28 | exclude: /(node_modules|bower_components)/
29 | }]
30 | },
31 | devServer: {
32 | historyApiFallback: true,
33 | contentBase: PATHS.build,
34 | hot: true,
35 | inline: true,
36 | progress: true
37 | },
38 | plugins: [
39 | new webpack.HotModuleReplacementPlugin()
40 | ]
41 | }
42 |
--------------------------------------------------------------------------------
/logo/logo.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stoeffel/redux-elm-middleware/cc8a5c3bd1881145e3b76f98eb0840def0d07d82/logo/logo.ai
--------------------------------------------------------------------------------
/logo/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stoeffel/redux-elm-middleware/cc8a5c3bd1881145e3b76f98eb0840def0d07d82/logo/logo.png
--------------------------------------------------------------------------------
/logo/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "redux-elm-middleware",
3 | "version": "4.0.0",
4 | "description": "Elm middleware for redux",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "nyc --r html mocha --require babel-core/register",
8 | "build": "babel src --out-dir .",
9 | "codecov": "nyc report --reporter=text-lcov > coverage.lcov && codecov",
10 | "release:major": "npm run build && xyz --repo git@github.com:stoeffel/redux-elm-middleware.git --increment major",
11 | "release:minor": "npm run build && xyz --repo git@github.com:stoeffel/redux-elm-middleware.git --increment minor",
12 | "release:patch": "npm run build && xyz --repo git@github.com:stoeffel/redux-elm-middleware.git --increment patch",
13 | "example": "npm-run-all --parallel serve elm",
14 | "serve": "cd examples && webpack-dev-server",
15 | "elm": "cd examples && npm run elm:install && elm-live src/Reducer.elm --output build/elm.js",
16 | "elm:install": "elm-package install -y && cd examples && elm-package install -y"
17 | },
18 | "babel": {
19 | "presets": [
20 | "es2015",
21 | "stage-0",
22 | "react"
23 | ]
24 | },
25 | "standard": {
26 | "ignore": [
27 | "/build/"
28 | ],
29 | "globals": [
30 | "__DEV__",
31 | "__TESTING__"
32 | ],
33 | "rules": {
34 | "arrow-parens": 0,
35 | "babel/arrow-parens": 0
36 | },
37 | "parser": "babel-eslint"
38 | },
39 | "repository": {
40 | "type": "git",
41 | "url": "git+https://github.com/stoeffel/redux-elm-middleware.git"
42 | },
43 | "author": "Christoph Hermann",
44 | "license": "MIT",
45 | "bugs": {
46 | "url": "https://github.com/stoeffel/redux-elm-middleware/issues"
47 | },
48 | "homepage": "https://github.com/stoeffel/redux-elm-middleware#readme",
49 | "dependencies": {
50 | "camelcase": "3.0.0"
51 | },
52 | "devDependencies": {
53 | "ava": "^0.15.2",
54 | "babel-core": "^6.7.7",
55 | "babel-eslint": "^6.0.3",
56 | "babel-loader": "^6.2.4",
57 | "babel-polyfill": "^6.7.4",
58 | "babel-preset-es2015": "^6.6.0",
59 | "babel-preset-react": "^6.5.0",
60 | "babel-preset-stage-0": "^6.5.0",
61 | "babel-register": "^6.7.2",
62 | "codecov": "^3.6.5",
63 | "elm": "^0.18.0",
64 | "elm-live": "^2.2.1",
65 | "jsdom-test-browser": "^4.0.2",
66 | "mocha": "^2.4.5",
67 | "npm-run-all": "^3.0.0",
68 | "nyc": "^8.3.0",
69 | "ramda": "^0.21.0",
70 | "react": "^15.0.1",
71 | "react-dom": "^15.0.1",
72 | "react-redux": "^4.4.5",
73 | "react-router": "^2.4.0",
74 | "react-router-redux": "^4.0.4",
75 | "redux": "^3.5.2",
76 | "redux-actions": "^0.11.0",
77 | "redux-mock-store": "^1.0.2",
78 | "redux-test-reducer": "^0.1.0",
79 | "sinon": "^1.17.4",
80 | "standard": "^8.1.0",
81 | "standard-format": "^2.1.1",
82 | "webpack": "^1.13.0",
83 | "webpack-dev-server": "^1.14.1",
84 | "xyz": "^1.0.1"
85 | },
86 | "files": [
87 | "index.js",
88 | "src/index.js",
89 | "src/Redux.elm",
90 | "src/Native/Redux.js",
91 | "elm-package.json"
92 | ]
93 | }
94 |
--------------------------------------------------------------------------------
/src/Redux.elm:
--------------------------------------------------------------------------------
1 | port module Redux exposing (program)
2 |
3 | {-| Redux elm middleware
4 | @docs program
5 | -}
6 |
7 | import Platform
8 | import Json.Encode as Json
9 |
10 |
11 | port elmToRedux : ( String, Json.Value ) -> Cmd msg
12 |
13 |
14 | {-| Creates a [Program](http://package.elm-lang.org/packages/elm-lang/core/4.0.0/Platform#Program) that defines how your reducer works. This is a convinient wrapper arround [Html.App.programm](http://package.elm-lang.org/packages/elm-lang/html/1.0.0/Html-App#program).
15 | main = Redux.program(
16 | { init = init
17 | , update = update
18 | , subscriptions
19 | })
20 | -}
21 | program :
22 | { init : ( model, Cmd msg )
23 | , update : msg -> model -> ( model, Cmd msg )
24 | , encode : model -> Json.Value
25 | , subscriptions : model -> Sub msg
26 | }
27 | -> Program Never model msg
28 | program app =
29 | let
30 | reducer action ( message, cmd ) =
31 | ( message
32 | , Cmd.batch
33 | [ cmd
34 | , ( toString action
35 | , app.encode message
36 | )
37 | |> elmToRedux
38 | ]
39 | )
40 |
41 | wrap update msg model =
42 | reducer msg <| update msg model
43 | in
44 | Platform.program
45 | { init = app.init
46 | , update = wrap app.update
47 | , subscriptions = app.subscriptions
48 | }
49 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import camelCase from 'camelcase'
2 |
3 | export const ELM = '@@elm'
4 |
5 | const createElmMiddleware = (elm) => {
6 | const elmMiddleware = ({dispatch}) => next => action => {
7 | const camelCaseType = camelCase(action.type)
8 | if (elm.ports && elm.ports[camelCaseType]) {
9 | elm.ports[camelCaseType].send(action.payload || null)
10 | }
11 | next(action)
12 | }
13 | const run = store => {
14 | if (elm && elm.ports && elm.ports.elmToRedux) {
15 | elm.ports.elmToRedux.subscribe(([action, payload]) => {
16 | const [actionType, ...rest] = action.split(' ')
17 | store.dispatch({
18 | type: `@@elm/${actionType}`,
19 | payload
20 | })
21 | })
22 | }
23 | }
24 |
25 |
26 | return { elmMiddleware, run }
27 | }
28 |
29 | export default createElmMiddleware
30 |
31 | export const createElmReducer = (init) => (state = init, action) => {
32 | const [elmAction, type] = action.type.split('/')
33 | if (elmAction === ELM) {
34 | return action.payload
35 | }
36 |
37 | return state
38 | }
39 |
40 | export const reducer = createElmReducer({})
41 |
--------------------------------------------------------------------------------
/test/middleware.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert'
2 | import sinon from 'sinon'
3 | import configureStore from 'redux-mock-store';
4 | import createMiddleware from '../src'
5 |
6 | describe('Middleware', () => {
7 | it('should be a function', () => {
8 | assert.equal(typeof createMiddleware, 'function')
9 | })
10 |
11 | it('should create a middleware', () => {
12 | const mockStore = configureStore([ createMiddleware({}).elmMiddleware ])({})
13 |
14 | mockStore.dispatch({ type: 'TEST' })
15 |
16 |
17 | assert.deepEqual(mockStore.getActions(), [{ type: 'TEST' }]);
18 | })
19 |
20 | it('uppercase action types should be valid', () => {
21 | const spy = sinon.spy()
22 | const { elmMiddleware } = createMiddleware({
23 | ports: {
24 | testingCamelCase: {
25 | send: spy
26 | }
27 | }
28 | })
29 | const mockStore = configureStore([elmMiddleware])({})
30 |
31 | mockStore.dispatch({ type: 'TESTING_CAMEL_CASE' })
32 | mockStore.dispatch({
33 | type: 'TESTING_CAMEL_CASE',
34 | payload: 'foo'
35 | })
36 |
37 | assert.deepEqual(mockStore.getActions(), [
38 | { type: 'TESTING_CAMEL_CASE' },
39 | { type: 'TESTING_CAMEL_CASE', payload: 'foo' },
40 | ]);
41 | assert.ok(spy.getCall(0).args[0] === null);
42 | assert.ok(spy.getCall(1).args[0] === 'foo');
43 |
44 | })
45 |
46 | it('should send a action to a port if present', () => {
47 | const spy = sinon.spy()
48 | const { elmMiddleware } = createMiddleware({
49 | ports: {
50 | test: {
51 | send: spy
52 | }
53 | }
54 | })
55 | const mockStore = configureStore([elmMiddleware])({})
56 |
57 | mockStore.dispatch({ type: 'TEST' })
58 | mockStore.dispatch({ type: 'NO_PORT' })
59 | mockStore.dispatch({ type: 'TEST', payload: 'foo' })
60 |
61 | assert.deepEqual(mockStore.getActions(), [
62 | { type: 'TEST' },
63 | { type: 'NO_PORT' },
64 | { type: 'TEST', payload: 'foo' }
65 | ]);
66 | assert.ok(spy.getCall(0).args[0] === null);
67 | assert.ok(spy.getCall(1).args[0] === 'foo');
68 | })
69 | })
70 |
--------------------------------------------------------------------------------
/test/reducer.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert'
2 | import testReducer from 'redux-test-reducer'
3 | import { reducer, createElmReducer } from '../src'
4 |
5 | describe('Reducer', () => {
6 | it('should be a function', () => {
7 | assert.equal(typeof reducer, 'function')
8 | })
9 |
10 | it('returns the payload of an elm-action as the new state', () => {
11 | const assertReducer = testReducer(reducer)
12 | const from = {
13 | init: true
14 | }
15 | const to = {
16 | elm: true
17 | }
18 | const action = {
19 | type: '@@elm/action',
20 | payload: {
21 | elm: true
22 | }
23 | }
24 | assertReducer({
25 | from, to, action
26 | })
27 | })
28 |
29 | it('should return the initial state if not an elm action', () => {
30 | const assertReducer = testReducer(reducer)
31 | const from = {
32 | init: true
33 | }
34 | const to = from
35 | const action = {
36 | type: 'OTHER_ACTION',
37 | payload: {
38 | foo: true
39 | }
40 | }
41 | assertReducer({
42 | from, to, action
43 | })
44 | })
45 |
46 | it('allows setting the initial state with createElmReducer', () => {
47 | const customInit = 1
48 | const assertReducer = testReducer(createElmReducer(customInit))
49 | const from = {
50 | init: customInit
51 | }
52 | const to = from
53 | const action = {
54 | type: 'OTHER_ACTION',
55 | payload: {
56 | foo: true
57 | }
58 | }
59 | assertReducer({
60 | from, to, action
61 | })
62 | })
63 | })
64 |
--------------------------------------------------------------------------------
/test/runMiddleware.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert'
2 | import bro from 'jsdom-test-browser'
3 | import createMiddleware, { ELM } from '../src'
4 | import sinon from 'sinon'
5 |
6 | describe('Run', () => {
7 | before(bro.newBrowser);
8 |
9 | it('should be a function', () => {
10 | assert.equal(typeof createMiddleware({}).run, 'function')
11 | })
12 |
13 | it('should subscribe to outgoing elm port', () => {
14 | const spy = sinon.spy()
15 | const noCallSpy = sinon.spy()
16 | const elm = {
17 | ports: {
18 | elmToRedux: {
19 | subscribe: (fn) => {
20 | fn(['Increment 5', { value: 5 }])
21 | }
22 | }
23 | }
24 | }
25 | const noPortElm = {
26 | ports: {
27 | incorrectPortName: {
28 | subscribe: noCallSpy
29 | }
30 | }
31 | }
32 | const store = {
33 | dispatch: spy
34 | }
35 | const expectedAction = {
36 | type: '@@elm/Increment',
37 | payload: { value: 5 }
38 | }
39 | createMiddleware(elm).run(store)
40 | createMiddleware(noPortElm).run(store)
41 | assert.deepEqual(
42 | spy.getCall(0).args[0],
43 | expectedAction
44 | )
45 | assert.ok(noCallSpy.callCount === 0)
46 | })
47 | })
48 |
--------------------------------------------------------------------------------