├── .gitignore ├── .npmignore ├── README.md ├── legacy.js ├── native.js ├── package.json ├── src ├── AltContainer.js ├── AltContainerLegacy.js ├── AltNativeContainerLegacy.js └── mixinContainer.js └── test ├── babel └── index.js └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AltContainer 2 | 3 | [Full Documentation](http://alt.js.org/docs/components/altContainer/) 4 | -------------------------------------------------------------------------------- /legacy.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/AltContainerLegacy.js') 2 | -------------------------------------------------------------------------------- /native.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/AltNativeContainerLegacy.js') 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "alt-container", 3 | "version": "1.1.1", 4 | "description": "A flux container for alt", 5 | "main": "lib/AltContainer.js", 6 | "scripts": { 7 | "clean": "rimraf lib && rimraf utils", 8 | "build": "babel src --out-dir lib --stage 0", 9 | "test": "npm run clean && npm run build && mocha -u exports -R nyan --require ./test/babel test" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git@github.com:altjs/container.git" 14 | }, 15 | "keywords": [ 16 | "altcontainer", 17 | "fluxcomponent", 18 | "component", 19 | "container", 20 | "connect", 21 | "stores", 22 | "flux", 23 | "react" 24 | ], 25 | "author": "Josh Perez ", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/altjs/container/issues" 29 | }, 30 | "homepage": "https://github.com/altjs/container", 31 | "dependencies": { 32 | "object.assign": "^4.0.1", 33 | "prop-types": "^15.5.10" 34 | }, 35 | "devDependencies": { 36 | "alt": "0.17.4", 37 | "alt-utils": "1.0.0", 38 | "babel": "5.8.23", 39 | "babel-core": "5.8.25", 40 | "chai": "3.3.0", 41 | "jsdom": "6.5.1", 42 | "mocha": "2.3.3", 43 | "react": "0.14.0", 44 | "react-addons-test-utils": "0.14.0", 45 | "react-dom": "0.14.0", 46 | "rimraf": "2.4.3", 47 | "sinon": "1.17.1" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/AltContainer.js: -------------------------------------------------------------------------------- 1 | /*eslint-disable*/ 2 | /** 3 | * AltContainer. 4 | * 5 | * There are many ways to use AltContainer. 6 | * 7 | * Using the `stores` prop. 8 | * 9 | * 10 | * children get this.props.FooStore.storeData 11 | * 12 | * 13 | * You can also pass in functions. 14 | * 15 | * 16 | * children get this.props.FooStore.storeData 17 | * 18 | * 19 | * Using the `store` prop. 20 | * 21 | * 22 | * children get this.props.storeData 23 | * 24 | * 25 | * Passing in `flux` because you're using alt instances 26 | * 27 | * 28 | * children get this.props.flux 29 | * 30 | * 31 | * Using a custom render function. 32 | * 33 | * ; 36 | * }} 37 | * /> 38 | * 39 | * Using the `transform` prop. 40 | * 41 | * 53 | * children get this.props.products 54 | * 55 | * 56 | * Full docs available at http://goatslacker.github.io/alt/ 57 | */ 58 | import React from 'react' 59 | import PropTypes from 'prop-types'; 60 | import assign from 'object.assign' 61 | 62 | const id = it => it 63 | const getStateFromStore = (store, props) => { 64 | return typeof store === 'function' ? store(props).value : store.getState() 65 | } 66 | const getStateFromKey = (actions, props) => { 67 | return typeof actions === 'function' ? actions(props) : actions 68 | } 69 | 70 | const getStateFromActions = (props) => { 71 | if (props.actions) { 72 | return getStateFromKey(props.actions, props) 73 | } else { 74 | return {} 75 | } 76 | } 77 | 78 | const getInjected = (props) => { 79 | if (props.inject) { 80 | return Object.keys(props.inject).reduce((obj, key) => { 81 | obj[key] = getStateFromKey(props.inject[key], props) 82 | return obj 83 | }, {}) 84 | } else { 85 | return {} 86 | } 87 | } 88 | 89 | const reduceState = (props) => { 90 | return assign( 91 | {}, 92 | getStateFromStores(props), 93 | getStateFromActions(props), 94 | getInjected(props) 95 | ) 96 | } 97 | 98 | const getStateFromStores = (props) => { 99 | var stores = props.stores 100 | if (props.store) { 101 | return getStateFromStore(props.store, props) 102 | } else if (props.stores) { 103 | // If you pass in an array of stores then we are just listening to them 104 | // it should be an object then the state is added to the key specified 105 | if (!Array.isArray(stores)) { 106 | return Object.keys(stores).reduce(function (obj, key) { 107 | obj[key] = getStateFromStore(stores[key], props) 108 | return obj 109 | }, {}) 110 | } 111 | } else { 112 | return {} 113 | } 114 | } 115 | 116 | // TODO need to copy some other contextTypes maybe? 117 | // what about propTypes? 118 | class AltContainer extends React.Component { 119 | static contextTypes = { 120 | flux: PropTypes.object, 121 | } 122 | 123 | static childContextTypes = { 124 | flux: PropTypes.object, 125 | } 126 | 127 | getChildContext() { 128 | var flux = this.props.flux || this.context.flux 129 | return flux ? { flux: flux } : {} 130 | } 131 | 132 | constructor(props) { 133 | super(props) 134 | 135 | if (props.stores && props.store) { 136 | throw new ReferenceError('Cannot define both store and stores') 137 | } 138 | 139 | this.storeListeners = [] 140 | 141 | this.state = reduceState(props) 142 | } 143 | 144 | componentWillReceiveProps(nextProps) { 145 | this._destroySubscriptions() 146 | this.setState(reduceState(nextProps)) 147 | this._registerStores(nextProps) 148 | if (this.props.onWillReceiveProps) { 149 | this.props.onWillReceiveProps(nextProps, this.props, this.context) 150 | } 151 | } 152 | 153 | componentDidMount() { 154 | this._registerStores(this.props) 155 | if (this.props.onMount) this.props.onMount(this.props, this.context) 156 | } 157 | 158 | componentWillUnmount() { 159 | this._destroySubscriptions() 160 | if (this.props.onWillUnmount) { 161 | this.props.onWillUnmount(this.props, this.context) 162 | } 163 | } 164 | 165 | _registerStores(props) { 166 | const stores = props.stores 167 | 168 | if (props.store) { 169 | this._addSubscription(props.store) 170 | } else if (props.stores) { 171 | if (Array.isArray(stores)) { 172 | stores.forEach(store => this._addSubscription(store)) 173 | } else { 174 | Object.keys(stores).forEach((formatter) => { 175 | this._addSubscription(stores[formatter]) 176 | }) 177 | } 178 | } 179 | } 180 | 181 | _destroySubscriptions() { 182 | this.storeListeners.forEach(storeListener => storeListener()) 183 | } 184 | 185 | _addSubscription(getStore) { 186 | const store = typeof getStore === 'function' 187 | ? getStore(this.props).store 188 | : getStore 189 | 190 | this.storeListeners.push(store.listen(this.altSetState)) 191 | } 192 | 193 | altSetState = () => { 194 | this.setState(reduceState(this.props)) 195 | } 196 | 197 | getProps() { 198 | var flux = this.props.flux || this.context.flux 199 | var transform = typeof this.props.transform === 'function' 200 | ? this.props.transform 201 | : id 202 | return transform(assign( 203 | flux ? { flux: flux } : {}, 204 | this.state 205 | )) 206 | } 207 | 208 | shouldComponentUpdate(nextProps, nextState) { 209 | return this.props.shouldComponentUpdate 210 | ? this.props.shouldComponentUpdate(this.getProps(), nextProps, nextState) 211 | : true 212 | } 213 | 214 | render() { 215 | const Node = 'div' 216 | const children = this.props.children 217 | 218 | // Custom rendering function 219 | if (typeof this.props.render === 'function') { 220 | return this.props.render(this.getProps()) 221 | } else if (this.props.component) { 222 | return React.createElement(this.props.component, this.getProps()) 223 | } 224 | 225 | // Does not wrap child in a div if we don't have to. 226 | if (Array.isArray(children)) { 227 | return React.createElement(Node, null, children.map((child, i) => { 228 | return React.cloneElement(child, assign( 229 | { key: i }, 230 | this.getProps() 231 | )) 232 | })) 233 | } else if (children) { 234 | return React.cloneElement(children, this.getProps()) 235 | } else { 236 | return React.createElement(Node, this.getProps()) 237 | } 238 | } 239 | } 240 | 241 | export default AltContainer 242 | -------------------------------------------------------------------------------- /src/AltContainerLegacy.js: -------------------------------------------------------------------------------- 1 | /*eslint-disable*/ 2 | /** 3 | * AltContainer. 4 | * 5 | * There are many ways to use AltContainer. 6 | * 7 | * Using the `stores` prop. 8 | * 9 | * 10 | * children get this.props.FooStore.storeData 11 | * 12 | * 13 | * You can also pass in functions. 14 | * 15 | * 16 | * children get this.props.FooStore.storeData 17 | * 18 | * 19 | * Using the `store` prop. 20 | * 21 | * 22 | * children get this.props.storeData 23 | * 24 | * 25 | * Passing in `flux` because you're using alt instances 26 | * 27 | * 28 | * children get this.props.flux 29 | * 30 | * 31 | * Using a custom render function. 32 | * 33 | * ; 36 | * }} 37 | * /> 38 | * 39 | * Using the `transform` prop. 40 | * 41 | * 53 | * children get this.props.products 54 | * 55 | * 56 | * Full docs available at http://goatslacker.github.io/alt/ 57 | */ 58 | import React from 'react' 59 | import mixinContainer from './mixinContainer' 60 | import assign from 'object.assign' 61 | 62 | const AltContainer = React.createClass(assign({ 63 | displayName: 'AltContainer', 64 | 65 | render() { 66 | return this.altRender('div') 67 | } 68 | }, mixinContainer(React))) 69 | 70 | export default AltContainer 71 | -------------------------------------------------------------------------------- /src/AltNativeContainerLegacy.js: -------------------------------------------------------------------------------- 1 | /*eslint-disable*/ 2 | /** 3 | * AltNativeContainer. 4 | * 5 | * @see AltContainer 6 | */ 7 | import React from 'react-native' 8 | import mixinContainer from './mixinContainer' 9 | import assign from 'object.assign' 10 | 11 | const AltNativeContainer = React.createClass(assign({ 12 | displayName: 'AltNativeContainer', 13 | 14 | render() { 15 | return this.altRender(React.View) 16 | } 17 | }, mixinContainer(React))) 18 | 19 | export default AltNativeContainer 20 | -------------------------------------------------------------------------------- /src/mixinContainer.js: -------------------------------------------------------------------------------- 1 | /*eslint-disable*/ 2 | import assign from 'object.assign' 3 | import PropTypes from 'prop-types'; 4 | 5 | const id = it => it 6 | 7 | const getStateFromStore = (store, props) => { 8 | return typeof store === 'function' ? store(props).value : store.getState() 9 | } 10 | 11 | const getStateFromKey = (actions, props) => { 12 | return typeof actions === 'function' ? actions(props) : actions 13 | } 14 | 15 | const mixinContainer = (React) => { 16 | const cloneElement = (element, props) => { 17 | return React.createElement(element.type, assign( 18 | {}, 19 | element.props, 20 | props, 21 | { children: element.props.children } 22 | )) 23 | } 24 | 25 | return { 26 | contextTypes: { 27 | flux: PropTypes.object 28 | }, 29 | 30 | childContextTypes: { 31 | flux: PropTypes.object 32 | }, 33 | 34 | getChildContext() { 35 | var flux = this.props.flux || this.context.flux 36 | return flux ? { flux: flux } : {} 37 | }, 38 | 39 | getInitialState() { 40 | if (this.props.stores && this.props.store) { 41 | throw new ReferenceError('Cannot define both store and stores') 42 | } 43 | 44 | this.storeListeners = [] 45 | 46 | return this.reduceState(this.props) 47 | }, 48 | 49 | componentWillReceiveProps(nextProps) { 50 | this.destroySubscriptions() 51 | this.setState(this.reduceState(nextProps)) 52 | this.registerStores(nextProps) 53 | }, 54 | 55 | componentDidMount() { 56 | this.registerStores(this.props) 57 | if (this.props.onMount) this.props.onMount(this.props, this.context) 58 | }, 59 | 60 | componentWillUnmount() { 61 | this.destroySubscriptions() 62 | }, 63 | 64 | registerStores(props) { 65 | var stores = props.stores 66 | 67 | if (props.store) { 68 | this.addSubscription(props.store) 69 | } else if (props.stores) { 70 | if (Array.isArray(stores)) { 71 | stores.forEach((store) => { 72 | this.addSubscription(store) 73 | }) 74 | } else { 75 | Object.keys(stores).forEach((formatter) => { 76 | this.addSubscription(stores[formatter]) 77 | }) 78 | } 79 | } 80 | }, 81 | 82 | destroySubscriptions() { 83 | this.storeListeners.forEach(storeListener => storeListener()) 84 | }, 85 | 86 | getStateFromStores(props) { 87 | var stores = props.stores 88 | if (props.store) { 89 | return getStateFromStore(props.store, props) 90 | } else if (props.stores) { 91 | // If you pass in an array of stores then we are just listening to them 92 | // it should be an object then the state is added to the key specified 93 | if (!Array.isArray(stores)) { 94 | return Object.keys(stores).reduce((obj, key) => { 95 | obj[key] = getStateFromStore(stores[key], props) 96 | return obj 97 | }, {}) 98 | } 99 | } else { 100 | return {} 101 | } 102 | }, 103 | 104 | getStateFromActions(props) { 105 | if (props.actions) { 106 | return getStateFromKey(props.actions, props) 107 | } else { 108 | return {} 109 | } 110 | }, 111 | 112 | getInjected(props) { 113 | if (props.inject) { 114 | return Object.keys(props.inject).reduce((obj, key) => { 115 | obj[key] = getStateFromKey(props.inject[key], props) 116 | return obj 117 | }, {}) 118 | } else { 119 | return {} 120 | } 121 | }, 122 | 123 | reduceState(props) { 124 | return assign( 125 | {}, 126 | this.getStateFromStores(props), 127 | this.getStateFromActions(props), 128 | this.getInjected(props) 129 | ) 130 | }, 131 | 132 | addSubscription(getStore) { 133 | const store = typeof getStore === 'function' 134 | ? getStore(this.props).store 135 | : getStore 136 | 137 | this.storeListeners.push(store.listen(this.altSetState)) 138 | }, 139 | 140 | altSetState() { 141 | this.setState(this.reduceState(this.props)) 142 | }, 143 | 144 | getProps() { 145 | var flux = this.props.flux || this.context.flux 146 | var transform = typeof this.props.transform === 'function' 147 | ? this.props.transform 148 | : id 149 | return transform(assign( 150 | flux ? { flux: flux } : {}, 151 | this.state 152 | )) 153 | }, 154 | 155 | shouldComponentUpdate() { 156 | return this.props.shouldComponentUpdate 157 | ? this.props.shouldComponentUpdate(this.getProps()) 158 | : true 159 | }, 160 | 161 | altRender(Node) { 162 | var children = this.props.children 163 | // Custom rendering function 164 | if (typeof this.props.render === 'function') { 165 | return this.props.render(this.getProps()) 166 | } else if (this.props.component) { 167 | return React.createElement(this.props.component, this.getProps()) 168 | } 169 | 170 | // Does not wrap child in a div if we don't have to. 171 | if (Array.isArray(children)) { 172 | return React.createElement(Node, null, children.map((child, i) => { 173 | return cloneElement(child, assign( 174 | { key: i }, 175 | this.getProps() 176 | )) 177 | }, this)) 178 | } else if (children) { 179 | return cloneElement(children, this.getProps()) 180 | } else { 181 | return React.createElement(Node, this.getProps()) 182 | } 183 | } 184 | } 185 | } 186 | 187 | export default mixinContainer 188 | -------------------------------------------------------------------------------- /test/babel/index.js: -------------------------------------------------------------------------------- 1 | require('babel-core/external-helpers') 2 | require('babel/register-without-polyfill')({ 3 | stage: 0 4 | }) 5 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | import { jsdom } from 'jsdom' 2 | import Alt from 'alt' 3 | import React from 'react' 4 | import AltContainer from '../' 5 | import withAltContext from 'alt-utils/lib/withAltContext' 6 | import { assert } from 'chai' 7 | import sinon from 'sinon' 8 | import TestUtils from 'react-addons-test-utils' 9 | import ReactDom from 'react-dom' 10 | 11 | const alt = new Alt() 12 | 13 | const action = alt.generateActions('sup') 14 | 15 | const TestStore = alt.createStore({ 16 | displayName: 'TestStore', 17 | 18 | bindListeners: { 19 | handleSup: action.sup 20 | }, 21 | 22 | state: { x: null }, 23 | 24 | handleSup(x) { 25 | this.setState({ x }) 26 | } 27 | }) 28 | 29 | const Store2 = alt.createStore({ 30 | displayName: 'Store2', 31 | 32 | bindListeners: { 33 | onSup: action.sup 34 | }, 35 | 36 | state: { y: null }, 37 | 38 | onSup(y) { 39 | this.setState({ y }) 40 | } 41 | }) 42 | 43 | class Flux extends Alt { 44 | constructor() { 45 | super() 46 | 47 | this.addActions('testActions', function () { 48 | this.generateActions('test') 49 | }) 50 | 51 | this.addStore('testStore', { 52 | bindListeners: { 53 | test: this.getActions('testActions').test 54 | }, 55 | 56 | state: { x: null }, 57 | 58 | test(x) { 59 | this.setState({ x }) 60 | } 61 | }) 62 | } 63 | } 64 | 65 | export default { 66 | 'AltContainer': { 67 | beforeEach() { 68 | global.document = jsdom('') 69 | global.window = global.document.defaultView 70 | 71 | alt.recycle() 72 | }, 73 | 74 | afterEach() { 75 | delete global.document 76 | delete global.window 77 | }, 78 | 79 | 'element mounts and unmounts'() { 80 | const div = document.createElement('div') 81 | ReactDom.render( 82 | 83 |
84 | 85 | , div) 86 | 87 | ReactDom.unmountComponentAtNode(div) 88 | }, 89 | 90 | 'many elements mount'() { 91 | TestUtils.renderIntoDocument( 92 | 93 |
94 |
95 |
96 |
97 | 98 | ) 99 | }, 100 | 101 | 'element has correct state'() { 102 | const node = TestUtils.renderIntoDocument( 103 | 104 |
105 | 106 | ) 107 | 108 | action.sup('hello') 109 | 110 | assert(node.state.TestStore.x === 'hello') 111 | 112 | action.sup('bye') 113 | 114 | assert(node.state.TestStore.x === 'bye') 115 | }, 116 | 117 | 'works with context'() { 118 | const flux = new Flux() 119 | 120 | @withAltContext(flux) 121 | class ContextComponent extends React.Component { 122 | render() { 123 | return 124 | } 125 | } 126 | 127 | const tree = TestUtils.renderIntoDocument() 128 | 129 | const contextComponent = TestUtils.findRenderedComponentWithType( 130 | tree, 131 | AltContainer 132 | ) 133 | 134 | assert.instanceOf(contextComponent.context.flux, Flux) 135 | }, 136 | 137 | 'children get flux as props with context'() { 138 | const flux = new Flux() 139 | 140 | const TestComponent = React.createClass({ 141 | render() { 142 | return ( 143 | 144 |
145 |
146 | 147 | 148 | 149 |
150 |
151 |
152 | ) 153 | } 154 | }) 155 | 156 | const WrappedComponent = withAltContext(flux)(TestComponent) 157 | 158 | const node = TestUtils.renderIntoDocument() 159 | const span = TestUtils.findRenderedDOMComponentWithTag(node, 'span') 160 | 161 | assert.instanceOf(span.props.flux, Flux) 162 | }, 163 | 164 | 'works with instances and props'() { 165 | const flux = new Flux() 166 | 167 | const node = TestUtils.renderIntoDocument( 168 | 169 |
170 | 171 | ) 172 | 173 | assert.instanceOf(node.props.flux, Flux, 'component gets flux prop') 174 | }, 175 | 176 | 'children have the flux prop'() { 177 | const flux = new Flux() 178 | 179 | const node = TestUtils.renderIntoDocument( 180 | 181 | 182 | 183 | ) 184 | 185 | const span = TestUtils.findRenderedDOMComponentWithTag(node, 'span') 186 | 187 | assert.instanceOf(span.props.flux, Flux) 188 | }, 189 | 190 | 'flux prop works with the transform function'() { 191 | const flux = new Flux() 192 | 193 | const TestComponent = React.createClass({ 194 | render() { 195 | return ( 196 | { return { flx: flux } }}> 197 |
198 |
199 | 200 | 201 | 202 |
203 |
204 |
205 | ) 206 | } 207 | }) 208 | 209 | const WrappedComponent = withAltContext(flux)(TestComponent); 210 | 211 | const node = TestUtils.renderIntoDocument() 212 | const div = TestUtils.scryRenderedDOMComponentsWithTag(node, 'div')[0] 213 | const span = TestUtils.findRenderedDOMComponentWithTag(node, 'span') 214 | 215 | assert(div.props.flx === flux) 216 | assert.isUndefined(span.props.flx) 217 | assert(span.props.flux === flux) 218 | }, 219 | 220 | 'children get the state via props'() { 221 | const node = TestUtils.renderIntoDocument( 222 | 223 | 224 | 225 | ) 226 | 227 | action.sup('foobar') 228 | 229 | const span = TestUtils.findRenderedDOMComponentWithTag(node, 'span') 230 | 231 | assert(span.props.TestStore.x === 'foobar') 232 | }, 233 | 234 | 'many children get state via props'() { 235 | const node = TestUtils.renderIntoDocument( 236 | 237 | 238 | 239 | 240 | 241 | ) 242 | 243 | action.sup('foobar') 244 | 245 | const span = TestUtils.findRenderedDOMComponentWithTag(node, 'span') 246 | const strong = TestUtils.findRenderedDOMComponentWithTag(node, 'strong') 247 | const em = TestUtils.findRenderedDOMComponentWithTag(node, 'em') 248 | 249 | assert(span.props.TestStore.x === 'foobar') 250 | assert(strong.props.TestStore.x === 'foobar') 251 | assert(em.props.TestStore.x === 'foobar') 252 | }, 253 | 254 | 'passing in other props'() { 255 | const node = TestUtils.renderIntoDocument( 256 | 257 |
258 | 259 | ) 260 | 261 | const div = TestUtils.findRenderedDOMComponentWithTag(node, 'div') 262 | 263 | assert(div.props.className === 'hello') 264 | assert.isUndefined(div.props.stores) 265 | }, 266 | 267 | 'does not wrap if it does not have to'() { 268 | const node = TestUtils.renderIntoDocument( 269 | 270 | 271 | 272 | ) 273 | 274 | assert(node.props.children.type === 'span', 'single node does not wrap') 275 | 276 | const many = TestUtils.renderIntoDocument( 277 | 278 | 279 | 280 | 281 | ) 282 | 283 | assert.ok(Array.isArray(many.props.children), 'multiple nodes are wrapped') 284 | }, 285 | 286 | 'passing in a single store'() { 287 | const node = TestUtils.renderIntoDocument( 288 | 289 | 290 | 291 | ) 292 | const span = TestUtils.findRenderedDOMComponentWithTag(node, 'span') 293 | 294 | action.sup('just testing') 295 | 296 | assert(span.props.x === 'just testing') 297 | }, 298 | 299 | 'pass in single function'() { 300 | const node = TestUtils.renderIntoDocument( 301 | { 302 | return { 303 | store: TestStore, 304 | value: { x: 'jesting' } 305 | } 306 | }}> 307 | 308 | 309 | ) 310 | const span = TestUtils.findRenderedDOMComponentWithTag(node, 'span') 311 | 312 | assert(span.props.x === 'jesting') 313 | }, 314 | 315 | 'function is called with props'() { 316 | const storeFunction = sinon.stub() 317 | storeFunction.returns({ 318 | store: TestStore, 319 | value: {} 320 | }) 321 | 322 | TestUtils.renderIntoDocument( 323 | 324 | 325 | 326 | ) 327 | 328 | assert.ok(storeFunction.calledTwice, 'called twice, once for store listening and another for props') 329 | assert(storeFunction.args[0].length === 1, 'called with one parameter') 330 | assert(storeFunction.args[1].length === 1, 'called with one parameter') 331 | assert.isObject(storeFunction.args[0][0], 'called with the props') 332 | assert.isObject(storeFunction.args[1][0], 'called with the props') 333 | assert(storeFunction.args[0][0].className === 'foo', 'props match') 334 | assert(storeFunction.args[1][0].className === 'foo', 'props match') 335 | }, 336 | 337 | 'pass in key-value of functions'() { 338 | const Functions = { 339 | x() { 340 | return { 341 | store: TestStore, 342 | value: { a: 'hello' } 343 | } 344 | }, 345 | y() { 346 | return { 347 | store: TestStore, 348 | value: { b: 'goodbye' } 349 | } 350 | } 351 | } 352 | 353 | const node = TestUtils.renderIntoDocument( 354 | 355 | 356 | 357 | ) 358 | const span = TestUtils.findRenderedDOMComponentWithTag(node, 'span') 359 | 360 | assert(span.props.x.a === 'hello') 361 | assert(span.props.y.b === 'goodbye') 362 | }, 363 | 364 | 'nested components pass down flux'() { 365 | const flux = new Flux() 366 | const node = TestUtils.renderIntoDocument( 367 | 368 | 369 | 370 | 371 | 372 | ) 373 | const span = TestUtils.findRenderedDOMComponentWithTag(node, 'span') 374 | 375 | assert.instanceOf(span.props.flux, Flux) 376 | }, 377 | 378 | 'custom rendering'() { 379 | const render = sinon.stub() 380 | render.onCall(0).returns(null) 381 | TestUtils.renderIntoDocument( 382 | 383 | ) 384 | 385 | assert.ok(render.calledOnce, 'render was called') 386 | 387 | const node = TestUtils.renderIntoDocument( 388 | { 391 | assert.isDefined(props.TestStore, 'test store exists in props') 392 | return 393 | }} 394 | /> 395 | ) 396 | const span = TestUtils.findRenderedDOMComponentWithTag(node, 'span') 397 | 398 | assert(span.props.className === 'testing testing') 399 | }, 400 | 401 | 'define both stores and store'() { 402 | assert.throws(() => { 403 | TestUtils.renderIntoDocument( 404 | 405 | ) 406 | }) 407 | }, 408 | 409 | 'changing an already mounted components props'() { 410 | let cb = null 411 | 412 | const El = React.createClass({ 413 | getInitialState() { 414 | return { store: TestStore } 415 | }, 416 | 417 | componentDidMount() { 418 | cb = state => this.setState(state) 419 | }, 420 | 421 | render() { 422 | return ( 423 | 424 | 425 | 426 | ) 427 | } 428 | }) 429 | 430 | const node = TestUtils.renderIntoDocument() 431 | 432 | assert(node.refs.test.props.store === TestStore, 'node gets first state') 433 | 434 | cb({ store: Store2 }) 435 | 436 | assert(node.refs.test.props.store === Store2, 'node changes props properly') 437 | }, 438 | 439 | 'inject actions'() { 440 | const node = TestUtils.renderIntoDocument( 441 | 442 | 443 | 444 | ) 445 | 446 | const span = TestUtils.findRenderedDOMComponentWithTag(node, 'span') 447 | 448 | assert.isObject(span.props.MyActions, 'MyActions exist') 449 | assert(span.props.MyActions === action, 'MyActions is injected actions') 450 | assert.isFunction(span.props.MyActions.sup, 'sup action is available') 451 | }, 452 | 453 | 'inject all actions directly shorthand'() { 454 | const node = TestUtils.renderIntoDocument( 455 | 456 | 457 | 458 | ) 459 | 460 | const span = TestUtils.findRenderedDOMComponentWithTag(node, 'span') 461 | 462 | assert.isFunction(span.props.sup, 'sup is available directly on the props') 463 | }, 464 | 465 | 'inject all actions using a function'() { 466 | const node = TestUtils.renderIntoDocument( 467 | 474 | 475 | 476 | ) 477 | 478 | const span = TestUtils.findRenderedDOMComponentWithTag(node, 'span') 479 | 480 | assert.isObject(span.props.FooActions, 'actions are injected') 481 | assert.isFunction(span.props.FooActions.sup, 'sup is available') 482 | }, 483 | 484 | 'scu'() { 485 | const scu = sinon.stub().returns(true) 486 | 487 | const node = TestUtils.renderIntoDocument( 488 | 489 | 490 | 491 | ) 492 | 493 | action.sup() 494 | assert.ok(scu.calledOnce, 'custom shouldComponentUpdate was called') 495 | assert(scu.args[0].length === 3, '3 args are passed, the nextProps, nextState, and this.getProps()') 496 | assert.isDefined(scu.args[0][0].x, 'x prop exists') 497 | }, 498 | 499 | 'injectables'() { 500 | const node = TestUtils.renderIntoDocument( 501 | 507 | 508 | 509 | ) 510 | 511 | const span = TestUtils.findRenderedDOMComponentWithTag(node, 'span') 512 | 513 | assert(span.props.className === 'foo', 'you can inject custom things') 514 | assert.isDefined(span.props.foo.x, 'functions are ran') 515 | 516 | action.sup(888) 517 | 518 | assert(span.props.foo.x === 888, 'when passing stores as Array they are just listened on') 519 | }, 520 | 521 | 'passing in a component as a prop'() { 522 | const App = React.createClass({ 523 | render() { 524 | return 525 | } 526 | }) 527 | 528 | const node = TestUtils.renderIntoDocument( 529 | 530 | ) 531 | 532 | const strong = TestUtils.findRenderedDOMComponentWithTag(node, 'strong') 533 | 534 | action.sup(1337) 535 | 536 | assert.isDefined(strong, 'component exists') 537 | assert(strong.props.x === 1337, 'and we have props from TestStore') 538 | }, 539 | 540 | 'nested components and context'() { 541 | const flux = new Flux() 542 | 543 | const View = React.createClass({ 544 | render() { 545 | return 546 | } 547 | }) 548 | 549 | const SubView = React.createClass({ render() { 550 | return ( 551 | 552 | 553 | 554 | ) 555 | } }) 556 | 557 | const InsideComponent = React.createClass({ 558 | render() { 559 | return 560 | } 561 | }) 562 | 563 | const foo = sinon.spy() 564 | 565 | const App = React.createClass({ 566 | render() { 567 | return ( 568 | 569 | 570 | 571 | ) 572 | } 573 | }) 574 | 575 | const node = TestUtils.renderIntoDocument() 576 | const span = TestUtils.findRenderedDOMComponentWithTag(node, 'span') 577 | 578 | assert.instanceOf(span.props.flux, Flux) 579 | 580 | assert.ok(foo.calledOnce, 'onMount hook was called') 581 | }, 582 | } 583 | } 584 | --------------------------------------------------------------------------------