Skip to content

Commit

Permalink
Move to src/utils and fix tests
Browse files Browse the repository at this point in the history
  • Loading branch information
goatslacker committed Apr 19, 2015
1 parent a568bfb commit bd59252
Show file tree
Hide file tree
Showing 5 changed files with 234 additions and 213 deletions.
95 changes: 95 additions & 0 deletions src/utils/connectToStores.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/**
* 'Higher Order Component' that controls the props of a wrapped
* component via stores.
*
* Expects the Component to have two static methods:
* - getStores(): Should return an array of stores.
* - getPropsFromStores(props): Should return the props from the stores.
*
* Example using old React.createClass() style:
*
* const MyComponent = React.createClass({
* statics: {
* getStores() {
* return [myStore]
* },
* getPropsFromStores(props) {
* return myStore.getState()
* }
* },
* render() {
* // Use this.props like normal ...
* }
* })
* MyComponent = connectToStores(MyComponent)
*
*
* Example using ES6 Class:
*
* class MyComponent extends React.Component {
* static getStores() {
* return [myStore]
* }
* static getPropsFromStores(props) {
* return myStore.getState()
* }
* render() {
* // Use this.props like normal ...
* }
* }
* MyComponent = connectToStores(MyComponent)
*
* A great explanation of the merits of higher order components can be found at
* http://bit.ly/1abPkrP
*/

import React from 'react'
import assign from 'object-assign'

function connectToStores(Component) {

// Check for required static methods.
if (typeof Component.getStores !== 'function') {
throw new Error('connectToStores() expects the wrapped component to have a static getStores() method')
}
if (typeof Component.getPropsFromStores !== 'function') {
throw new Error('connectToStores() expects the wrapped component to have a static getPropsFromStores() method')
}

// Cache stores.
const stores = Component.getStores()

// Wrapper Component.
const StoreConnection = React.createClass({
getInitialState() {
return Component.getPropsFromStores(this.props)
},

componentDidMount() {
stores.forEach((store) => {
store.listen(this.onChange)
})
},

componentWillUnmount() {
stores.forEach((store) => {
store.unlisten(this.onChange)
})
},

onChange() {
this.setState(Component.getPropsFromStores(this.props))
},

render() {
return React.createElement(
Component,
assign({}, this.props, this.state)
)
}
})

return StoreConnection
}

export default connectToStores
138 changes: 138 additions & 0 deletions test/connect-to-stores-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { jsdom } from 'jsdom'
import Alt from '../dist/alt-with-runtime'
import React from 'react/addons'
import connectToStores from '../utils/connectToStores'
import { assert } from 'chai'

const { TestUtils } = React.addons

const alt = new Alt()

const testActions = alt.generateActions('updateFoo')

const testStore = alt.createStore(
class TestStore {
constructor() {
this.bindAction(testActions.updateFoo, this.onChangeFoo)
this.foo = 'Bar'
}
onChangeFoo(newValue) {
this.foo = newValue
}
}
)

export default {
'connectToStores wrapper': {
beforeEach() {
global.document = jsdom('<!doctype html><html><body></body></html>')
global.window = global.document.parentWindow
global.navigator = global.window.navigator
require('react/lib/ExecutionEnvironment').canUseDOM = true

alt.recycle()
},

afterEach() {
delete global.document
delete global.window
delete global.navigator
},

'missing the static getStores() method should throw'() {
const BadComponentOne = React.createClass({
render() {
return React.createElement('div', null, 'Bad')
}
})

assert.throws(() => connectToStores(BadComponentOne), 'expects the wrapped component to have a static getStores() method')
},

'element mounts and unmounts'() {
const div = document.createElement('div')

const LegacyComponent = connectToStores(React.createClass({
statics: {
getStores() {
return [testStore]
},
getPropsFromStores(props) {
return testStore.getState()
}
},
render() {
return React.createElement('div', null, `Foo${this.props.delim}${this.props.foo}`)
}
}))

React.render(
<LegacyComponent />
, div)

React.unmountComponentAtNode(div)
},

'missing the static getPropsFromStores() method should throw'() {
const BadComponentTwo = React.createClass({
statics: {
getStores() {
return [testStore]
}
},
render() {
return React.createElement('div', null, 'Bad')
}
})

assert.throws(() => connectToStores(BadComponentTwo), 'expects the wrapped component to have a static getPropsFromStores() method')
},

'createClass() component can get props from stores'() {
const LegacyComponent = React.createClass({
statics: {
getStores() {
return [testStore]
},
getPropsFromStores(props) {
return testStore.getState()
}
},
render() {
return React.createElement('div', null, `Foo${this.props.delim}${this.props.foo}`)
}
})

const WrappedComponent = connectToStores(LegacyComponent)
const element = React.createElement(WrappedComponent, {delim: ': '})
const output = React.renderToStaticMarkup(element)
assert.include(output, 'Foo: Bar')
},

'ES6 class component responds to store events'() {
class ClassComponent extends React.Component {
static getStores() {
return [testStore]
}
static getPropsFromStores(props) {
return testStore.getState()
}
render() {
return <span foo={this.props.foo} />
}
}

const WrappedComponent = connectToStores(ClassComponent)

const node = TestUtils.renderIntoDocument(
<WrappedComponent />
)

testActions.updateFoo('Baz')

const span = TestUtils.findRenderedDOMComponentWithTag(node, 'span')

assert(span.props.foo === 'Baz')
}
}
}
109 changes: 0 additions & 109 deletions test/connect-to-stores.js

This file was deleted.

2 changes: 1 addition & 1 deletion test/store-listener-component-test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { jsdom } from 'jsdom'
import Alt from '../dist/alt-with-runtime'
import React from 'react/addons'
import AltContainer from '../components/AltContainer'
import withAltContext from '../utils/withAltContext'
import { assert } from 'chai'
import { jsdom } from 'jsdom'
import sinon from 'sinon'

const { TestUtils } = React.addons
Expand Down
Loading

0 comments on commit bd59252

Please sign in to comment.