Skip to content

Commit 15f9a46

Browse files
authored
Properly merge subscriptions and rewrite tests (#32)
* Fix an issue where adding new subscriptions completely replaced existing subscriptions Closes #30 * Rewrite test suite to correctly cover #30 * Add a small optimization to connect
1 parent dce8217 commit 15f9a46

File tree

3 files changed

+91
-59
lines changed

3 files changed

+91
-59
lines changed

src/connect.js

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,10 @@ export default (mapFirebaseToProps = defaultMapFirebaseToProps, mergeProps = def
4646
}
4747

4848
componentDidMount() {
49-
this.mounted = true
50-
5149
const subscriptions = computeSubscriptions(this.props, this.ref, this.firebaseApp)
52-
const shouldSubscribe = Object.keys(subscriptions).length > 0
5350

54-
if (shouldSubscribe) {
55-
this.subscribe(subscriptions)
56-
}
51+
this.mounted = true
52+
this.subscribe(subscriptions)
5753
}
5854

5955
componentWillReceiveProps(nextProps) {
@@ -78,6 +74,10 @@ export default (mapFirebaseToProps = defaultMapFirebaseToProps, mergeProps = def
7874
}
7975

8076
subscribe(subscriptions) {
77+
if (Object.keys(subscriptions).length < 1) {
78+
return
79+
}
80+
8181
const queries = mapSubscriptionsToQueries(subscriptions)
8282
const nextListeners = mapValues(queries, ({ path, ...query }, key) => {
8383
const subscriptionRef = createQueryRef(this.ref(path), query)
@@ -100,10 +100,14 @@ export default (mapFirebaseToProps = defaultMapFirebaseToProps, mergeProps = def
100100
}
101101
})
102102

103-
this.listeners = nextListeners
103+
this.listeners = { ...this.listeners, ...nextListeners }
104104
}
105105

106106
unsubscribe(subscriptions) {
107+
if (Object.keys(subscriptions).length < 1) {
108+
return
109+
}
110+
107111
const nextListeners = { ...this.listeners }
108112
const nextSubscriptionsState = { ...this.state.subscriptionsState }
109113

tests/connect-test.js

Lines changed: 79 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,41 @@ import { findRenderedComponentWithType, renderIntoDocument } from 'react-addons-
99
import connect from '../src/connect'
1010
import { createMockApp, createMockSnapshot } from './helpers'
1111

12-
const renderStub = ({ mapFirebaseToProps, mergeProps, firebaseApp }, props) => {
13-
class Passthrough extends Component { // eslint-disable-line react/prefer-stateless-function
12+
const renderStub = ({ mapFirebaseToProps, mergeProps, firebaseApp }, initialProps) => {
13+
class WrappedComponent extends Component { // eslint-disable-line react/prefer-stateless-function
1414
render() {
1515
return <div />
1616
}
1717
}
1818

19-
const WrappedComponent = connect(mapFirebaseToProps, mergeProps)(Passthrough)
20-
const container = renderIntoDocument(<WrappedComponent {...props} firebaseApp={firebaseApp} />)
21-
const stub = findRenderedComponentWithType(container, Passthrough)
19+
const ConnectedComponent = connect(mapFirebaseToProps, mergeProps)(WrappedComponent)
20+
21+
class ParentComponent extends Component { // eslint-disable-line react/no-multi-comp
22+
state = {
23+
childProps: initialProps,
24+
}
25+
26+
render() {
27+
return (
28+
<ConnectedComponent
29+
{...this.state.childProps}
30+
ref={ref => (this.connectedComponent = ref)}
31+
firebaseApp={firebaseApp}
32+
/>
33+
)
34+
}
35+
}
36+
37+
const parentComponent = renderIntoDocument(<ParentComponent />)
38+
const connectedComponent = parentComponent.connectedComponent
39+
const wrappedComponent = findRenderedComponentWithType(parentComponent, WrappedComponent)
2240

2341
return {
24-
container,
25-
props: stub.props,
26-
state: container.state.subscriptionsState,
42+
getSubscriptionState: () => connectedComponent.state.subscriptionsState,
43+
getProps: () => wrappedComponent.props,
44+
getListeners: () => connectedComponent.listeners,
45+
setProps: props => parentComponent.setState({ childProps: props }),
46+
unmount: () => unmountComponentAtNode(findDOMNode(parentComponent).parentNode),
2747
}
2848
}
2949

@@ -34,8 +54,8 @@ test('Should throw if no initialized Firebase app instance was found', assert =>
3454
assert.doesNotThrow(() => {
3555
const defaultApp = firebase.initializeApp({})
3656
const WrappedComponent = connect()(() => <div />)
37-
const container = renderIntoDocument(<WrappedComponent />)
38-
const stub = findRenderedComponentWithType(container, WrappedComponent)
57+
const connectedComponent = renderIntoDocument(<WrappedComponent />)
58+
const stub = findRenderedComponentWithType(connectedComponent, WrappedComponent)
3959

4060
assert.equal(stub.firebaseApp, defaultApp)
4161

@@ -72,10 +92,10 @@ test('Should subscribe to a single path', assert => {
7292

7393
const mapFirebaseToProps = () => ({ foo: 'foo' })
7494
const firebaseApp = createMockApp(mockDatabase)
75-
const { state, props } = renderStub({ mapFirebaseToProps, firebaseApp })
95+
const stub = renderStub({ mapFirebaseToProps, firebaseApp })
7696

77-
assert.deepEqual(state, { foo: { bar: 'bar' } })
78-
assert.deepEqual(props.foo, { bar: 'bar' })
97+
assert.deepEqual(stub.getSubscriptionState(), { foo: { bar: 'bar' } })
98+
assert.deepEqual(stub.getProps().foo, { bar: 'bar' })
7999
assert.end()
80100
})
81101

@@ -94,10 +114,10 @@ test('Should return null if a subscribed path does not exist', assert => {
94114

95115
const mapFirebaseToProps = () => ({ foo: 'foo' })
96116
const firebaseApp = createMockApp(mockDatabase)
97-
const { state, props } = renderStub({ mapFirebaseToProps, firebaseApp })
117+
const stub = renderStub({ mapFirebaseToProps, firebaseApp })
98118

99-
assert.deepEqual(state, { foo: null })
100-
assert.equal(props.foo, null)
119+
assert.deepEqual(stub.getSubscriptionState(), { foo: null })
120+
assert.equal(stub.getProps().foo, null)
101121
assert.end()
102122
})
103123

@@ -115,10 +135,10 @@ test('Should not pass unresolved subscriptions from result of mapFirebaseToProps
115135

116136
const mapFirebaseToProps = () => ({ foo: 'foo' })
117137
const firebaseApp = createMockApp(mockDatabase)
118-
const first = renderStub({ mapFirebaseToProps, firebaseApp })
138+
const stub = renderStub({ mapFirebaseToProps, firebaseApp })
119139

120-
assert.equal(first.state, null)
121-
assert.equal(first.props.foo, undefined)
140+
assert.equal(stub.getSubscriptionState(), null)
141+
assert.equal(stub.getProps().foo, undefined)
122142
assert.end()
123143
})
124144

@@ -155,10 +175,10 @@ test('Should subscribe to a query', assert => {
155175
})
156176

157177
const firebaseApp = createMockApp(mockDatabase)
158-
const { state, props } = renderStub({ mapFirebaseToProps, firebaseApp })
178+
const stub = renderStub({ mapFirebaseToProps, firebaseApp })
159179

160-
assert.deepEqual(state, { bar: 'bar value' })
161-
assert.equal(props.bar, 'bar value')
180+
assert.deepEqual(stub.getSubscriptionState(), { bar: 'bar value' })
181+
assert.equal(stub.getProps().bar, 'bar value')
162182
assert.end()
163183
})
164184

@@ -169,11 +189,11 @@ test('Should not subscribe to functions', assert => {
169189
})
170190

171191
const firebaseApp = createMockApp()
172-
const { state, props } = renderStub({ mapFirebaseToProps, firebaseApp })
192+
const stub = renderStub({ mapFirebaseToProps, firebaseApp })
173193

174-
assert.deepEqual(state, { foo: 'foo value' })
175-
assert.equal(props.foo, 'foo value')
176-
assert.equal(typeof props.addFoo, 'function')
194+
assert.deepEqual(stub.getSubscriptionState(), { foo: 'foo value' })
195+
assert.equal(stub.getProps().foo, 'foo value')
196+
assert.equal(typeof stub.getProps().addFoo, 'function')
177197
assert.end()
178198
})
179199

@@ -195,12 +215,11 @@ test('Should unsubscribe when component unmounts', assert => {
195215

196216
const mapFirebaseToProps = () => ({ baz: 'baz' })
197217
const firebaseApp = createMockApp(mockDatabase)
198-
const { container } = renderStub({ mapFirebaseToProps, firebaseApp })
199-
200-
assert.notEqual(container.listeners.baz, undefined)
201-
unmountComponentAtNode(findDOMNode(container).parentNode)
202-
assert.equal(container.listeners.baz, undefined)
218+
const stub = renderStub({ mapFirebaseToProps, firebaseApp })
203219

220+
assert.notEqual(stub.getListeners().baz, undefined)
221+
assert.equal(stub.unmount(), true)
222+
assert.equal(stub.getListeners().baz, undefined)
204223
assert.end()
205224
})
206225

@@ -214,32 +233,40 @@ test('Should pass props, ref and firebaseApp to mapFirebaseToProps', assert => {
214233
}
215234

216235
const firebaseApp = createMockApp()
217-
const { props } = renderStub({ mapFirebaseToProps, firebaseApp }, { foo: 'foo prop' })
236+
const stub = renderStub({ mapFirebaseToProps, firebaseApp }, { foo: 'foo prop' })
218237

219-
assert.equal(props.foo, 'foo value')
238+
assert.equal(stub.getProps().foo, 'foo value')
220239
assert.end()
221240
})
222241

223242
test('Should update subscriptions when props change', assert => {
224243
const mapFirebaseToProps = props => ({ foo: props.foo, bar: props.bar })
225244
const firebaseApp = createMockApp()
226-
const stubOptions = { mapFirebaseToProps, firebaseApp }
227-
228-
const initial = renderStub(stubOptions, { foo: 'foo' })
229-
assert.equal(initial.props.foo, 'foo value')
230-
assert.equal(initial.props.bar, undefined)
231-
232-
const added = renderStub(stubOptions, { foo: 'foo', bar: 'bar' })
233-
assert.equal(added.props.foo, 'foo value')
234-
assert.equal(added.props.bar, 'bar value')
235-
236-
const changed = renderStub(stubOptions, { foo: 'foo', bar: 'baz' })
237-
assert.equal(changed.props.foo, 'foo value')
238-
assert.equal(changed.props.bar, 'baz value')
239-
240-
const removed = renderStub(stubOptions, { bar: 'baz' })
241-
assert.equal(removed.props.foo, undefined)
242-
assert.equal(removed.props.bar, 'baz value')
245+
const stub = renderStub({ mapFirebaseToProps, firebaseApp })
246+
247+
stub.setProps({ foo: 'foo' })
248+
assert.equal(stub.getProps().foo, 'foo value')
249+
assert.equal(stub.getProps().bar, undefined)
250+
assert.equal(stub.getListeners().foo.path, 'foo')
251+
assert.equal(stub.getListeners().bar, undefined)
252+
253+
stub.setProps({ foo: 'foo', bar: 'bar' })
254+
assert.equal(stub.getProps().foo, 'foo value')
255+
assert.equal(stub.getProps().bar, 'bar value')
256+
assert.equal(stub.getListeners().foo.path, 'foo')
257+
assert.equal(stub.getListeners().bar.path, 'bar')
258+
259+
stub.setProps({ foo: 'foo', bar: 'baz' })
260+
assert.equal(stub.getProps().foo, 'foo value')
261+
assert.equal(stub.getProps().bar, 'baz value')
262+
assert.equal(stub.getListeners().foo.path, 'foo')
263+
assert.equal(stub.getListeners().bar.path, 'baz')
264+
265+
stub.setProps({ bar: 'baz' })
266+
assert.equal(stub.getProps().foo, undefined)
267+
assert.equal(stub.getProps().bar, 'baz value')
268+
assert.equal(stub.getListeners().foo, undefined)
269+
assert.equal(stub.getListeners().bar.path, 'baz')
243270

244271
assert.end()
245272
})
@@ -249,8 +276,8 @@ test('Should use custom mergeProps function if provided', assert => {
249276
const mergeProps = () => ({ bar: 'bar merge props' })
250277

251278
const firebaseApp = createMockApp()
252-
const { props } = renderStub({ mapFirebaseToProps, mergeProps, firebaseApp }, { foo: 'foo prop' })
279+
const stub = renderStub({ mapFirebaseToProps, mergeProps, firebaseApp }, { foo: 'foo prop' })
253280

254-
assert.deepEqual(props, { bar: 'bar merge props' })
281+
assert.deepEqual(stub.getProps(), { bar: 'bar merge props' })
255282
assert.end()
256283
})

tests/helpers.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const defaultDatabaseProps = {
88
on: (event, callback) => (
99
callback(createMockSnapshot(`${path} value`))
1010
),
11+
off: () => {},
1112
}),
1213
}
1314

0 commit comments

Comments
 (0)