Skip to content

Commit 8811cda

Browse files
authored
Version v1.2.3 (#52)
* Population of list with items containing a child param that is Firebase list (`key: true`) using `populatedDataToJS` (#42) * `populatedDataToJS` supports childParam option (#48) * `populatedDataToJS` returns null for empty lists instead of `undefined` (#50) * Unit tests added to cover all cases within `populatedDataToJS`
1 parent 366c4ff commit 8811cda

File tree

8 files changed

+261
-75
lines changed

8 files changed

+261
-75
lines changed

.eslintignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
example/**
1+
examples/**
22
coverage/**
33
node_modules/**
44
*.spec.js

examples/snippets/decorators/App.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ import TodoItem from './TodoItem'
44

55
// redux/firebase
66
import { connect } from 'react-redux'
7-
import { firebase, helpers } from 'react-redux-firebase'
7+
import { firebaseConnect, helpers } from 'react-redux-firebase'
88
const { isLoaded, isEmpty, pathToJS, dataToJS } = helpers
99

10-
@firebase([
10+
@firebaseConnect([
1111
'/todos'
1212
])
1313
@connect(

examples/snippets/populates/App.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import React, { PropTypes, Component } from 'react'
2+
import { map } from 'lodash'
3+
import TodoItem from './TodoItem'
4+
5+
// redux/firebase
6+
import { connect } from 'react-redux'
7+
import { firebaseConnect, helpers } from 'react-redux-firebase'
8+
const { populatedDataToJS, isLoaded, pathToJS, dataToJS } = helpers
9+
const populates = [
10+
{ child: 'owner', root: 'users' },
11+
// or if you want a param of the populate child such as user's display name
12+
// { child: 'owner', root: 'users', childParam: 'displayName' }
13+
]
14+
15+
@firebaseConnect([
16+
{ path: '/projects', populates },
17+
])
18+
@connect(
19+
({firebase}) => ({
20+
projects: populatedDataToJS(firebase, '/projects', populates),
21+
})
22+
)
23+
export default class App extends Component {
24+
static propTypes = {
25+
projects: PropTypes.shape({
26+
name: PropTypes.string,
27+
owner: PropTypes.object // string if using childParam: 'displayName'
28+
}),
29+
firebase: PropTypes.shape({
30+
set: PropTypes.func.isRequired,
31+
remove: PropTypes.func.isRequired
32+
})
33+
}
34+
render () {
35+
const { firebase, projects } = this.props
36+
37+
const projectsList = (!isLoaded(projects))
38+
? 'Loading'
39+
: (isEmpty(projects))
40+
? 'Todo list is empty'
41+
: map(projects, (todo, id) => (
42+
<div>
43+
Name: {project.name}
44+
Owner: { owner.displayName || owner }
45+
</div>
46+
))
47+
return (
48+
<div>
49+
<h2>react-redux-firebase populate snippet</h2>
50+
<div>
51+
<h4>Projects List</h4>
52+
{projectsList}
53+
</div>
54+
</div>
55+
)
56+
}
57+
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-redux-firebase",
3-
"version": "1.2.2",
3+
"version": "1.2.3",
44
"description": "Redux integration for Firebase. Comes with a Higher Order Component for use with React.",
55
"main": "dist/index.js",
66
"module": "src/index.js",

src/helpers.js

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -184,16 +184,19 @@ export const dataToJS = (data, path, notSetValue) => {
184184
* @param {Object} list - Path of parameter to load
185185
* @param {Object} populate - Object with population settings
186186
*/
187-
export const buildChildList = (data, list, populate) =>
187+
export const buildChildList = (data, list, p) =>
188188
mapValues(list, (val, key) => {
189189
let getKey = val
190190
// Handle key: true lists
191191
if (val === true) {
192192
getKey = key
193193
}
194+
const pathString = p.childParam
195+
? `${p.root}/${getKey}/${p.childParam}`
196+
: `${p.root}/${getKey}`
194197
// Set to child under key if populate child exists
195-
if (dataToJS(data, `${populate.root}/${getKey}`)) {
196-
return dataToJS(data, `${populate.root}/${getKey}`)
198+
if (dataToJS(data, pathString)) {
199+
return dataToJS(data, pathString)
197200
}
198201
// Populate child does not exist
199202
return val === true ? val : getKey
@@ -230,14 +233,28 @@ export const populatedDataToJS = (data, path, populates, notSetValue) => {
230233
}
231234
// Handle undefined child
232235
if (!dataToJS(data, path, notSetValue)) {
233-
return undefined
236+
return dataToJS(data, path, notSetValue)
234237
}
235238
const populateObjs = getPopulateObjs(populates)
236239
// reduce array of populates to object of combined populated data
237240
return reduce(
238241
map(populateObjs, (p, obj) => {
239242
// single item with iterable child
240243
if (dataToJS(data, path)[p.child]) {
244+
// populate child is key
245+
if (isString(dataToJS(data, path)[p.child])) {
246+
const pathString = p.childParam
247+
? `${p.root}/${dataToJS(data, path)[p.child]}/${p.childParam}`
248+
: `${p.root}/${dataToJS(data, path)[p.child]}`
249+
if (dataToJS(data, pathString)) {
250+
return {
251+
...dataToJS(data, path),
252+
[p.child]: dataToJS(data, pathString)
253+
}
254+
}
255+
// matching child does not exist
256+
return dataToJS(data, path)
257+
}
241258
return {
242259
...dataToJS(data, path),
243260
[p.child]: buildChildList(data, dataToJS(data, path)[p.child], p)
@@ -251,10 +268,13 @@ export const populatedDataToJS = (data, path, populates, notSetValue) => {
251268
}
252269
// populate child is key
253270
if (isString(child[p.child])) {
254-
if (dataToJS(data, `${p.root}/${child[p.child]}`)) {
271+
const pathString = p.childParam
272+
? `${p.root}/${child[p.child]}/${p.childParam}`
273+
: `${p.root}/${child[p.child]}`
274+
if (dataToJS(data, pathString)) {
255275
return {
256276
...child,
257-
[p.child]: dataToJS(data, `${p.root}/${child[p.child]}`)
277+
[p.child]: dataToJS(data, pathString)
258278
}
259279
}
260280
// matching child does not exist

src/utils/populate.js

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -77,34 +77,24 @@ export const getPopulateChild = (firebase, populate, id) =>
7777
* @param {Object} populate - Object containing populate information
7878
* @param {Object} results - Object containing results of population from other populates
7979
*/
80-
export const populateList = (firebase, originalData, p, results) => {
81-
const mainChild = p.child.split('[]')[0]
82-
const childParam = p.child.split('[]')[1]
80+
export const populateList = (firebase, list, p, results) => {
81+
// Handle root not being defined
82+
if (!results[p.root]) {
83+
set(results, p.root, {})
84+
}
8385
return Promise.all(
84-
map(get(originalData, mainChild), (id, childKey) => {
86+
map(list, (id, childKey) => {
8587
// handle list of keys
8688
const populateKey = id === true ? childKey : id
8789
return getPopulateChild(
8890
firebase,
8991
p,
90-
childParam
91-
? get(id, childParam) // get child parameter if [] notation
92-
: populateKey
92+
populateKey
9393
)
9494
.then(pc => {
9595
if (pc) {
9696
// write child to result object under root name if it is found
97-
if (!childParam) {
98-
return set(results, `${p.root}.${populateKey}`, pc)
99-
}
100-
// handle child param
101-
return ({
102-
[childKey]: set(
103-
id,
104-
childParam,
105-
Object.assign(pc, { key: get(id, childParam) })
106-
)
107-
})
97+
return set(results, `${p.root}.${populateKey}`, pc)
10898
}
10999
return results
110100
})
@@ -131,7 +121,9 @@ export const promisesForPopulate = (firebase, originalData, populatesIn) => {
131121

132122
// Single parameter with list
133123
if (has(originalData, mainChild)) {
134-
return promisesArray.push(populateList(firebase, originalData, p, results))
124+
return promisesArray.push(
125+
populateList(firebase, originalData[mainChild], p, results)
126+
)
135127
}
136128
// Loop over each object in list
137129
forEach(originalData, (d, key) => {
@@ -161,7 +153,7 @@ export const promisesForPopulate = (firebase, originalData, populatesIn) => {
161153
if (isArray(idOrList) || isObject(idOrList)) {
162154
// Create single promise that includes a promise for each child
163155
return promisesArray.push(
164-
populateList(firebase, originalData, p, results)
156+
populateList(firebase, idOrList, p, results)
165157
)
166158
}
167159
})

test/unit/helpers.spec.js

Lines changed: 108 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -127,48 +127,112 @@ describe('Helpers:', () => {
127127
.equal(exampleData.data[path])
128128
})
129129

130-
it('populates child', () => {
131-
const path = 'projects'
132-
const rootName = 'users'
133-
const valName = 'CDF'
134-
expect(helpers.populatedDataToJS(exampleState, path, [{ child: 'owner', root: rootName }])[valName].owner)
135-
.to
136-
.have
137-
.property('displayName', 'scott')
138-
})
130+
describe('single', () => {
131+
describe('single param', () => {
132+
it('populates value', () => {
133+
const path = 'projects/CDF'
134+
const rootName = 'users'
135+
console.log('--------', helpers.populatedDataToJS(exampleState, path, [{ child: 'owner', root: rootName }]).owner)
136+
expect(helpers.populatedDataToJS(exampleState, path, [{ child: 'owner', root: rootName }]).owner)
137+
.to
138+
.have
139+
.property('displayName', 'scott')
140+
})
141+
142+
it('populates childParam', () => {
143+
const path = 'projects/CDF'
144+
const rootName = 'users'
145+
expect(helpers.populatedDataToJS(exampleState, path, [{ child: 'owner', root: rootName, childParam: 'displayName' }]).owner)
146+
.to
147+
.have
148+
.equal('scott')
149+
})
150+
it('keeps non-existant children', () => {
151+
const path = 'projects/OKF'
152+
const rootName = 'users'
153+
expect(helpers.populatedDataToJS(exampleState, path, [{ child: 'owner', root: rootName }]).owner)
154+
.to
155+
.have
156+
.equal('asdfasdf')
157+
})
158+
})
159+
describe('list param', () => {
160+
it('populates values', () => {
161+
const path = 'projects/OKF'
162+
const rootName = 'users'
163+
const populates = [
164+
{ child: 'collaborators', root: rootName },
165+
]
166+
const populatedData = helpers.populatedDataToJS(exampleState, path, populates)
167+
expect(populatedData)
168+
.to
169+
.have
170+
.deep
171+
.property(`collaborators.ABC.displayName`, exampleData.data[rootName].ABC.displayName)
172+
})
173+
})
139174

140-
it('populates child list', () => {
141-
const path = 'projects'
142-
const rootName = 'users'
143-
const valName = 'OKF'
144-
const populates = [
145-
{ child: 'collaborators', root: rootName },
146-
]
147-
const populatedData = helpers.populatedDataToJS(exampleState, path, populates)
148-
expect(populatedData)
149-
.to
150-
.have
151-
.deep
152-
.property(`${valName}.collaborators.ABC.displayName`, exampleData.data[rootName].ABC.displayName)
153175
})
154176

155-
it('handles non existant children', () => {
156-
const path = 'projects'
157-
const rootName = 'users'
158-
const valName = 'OKF'
159-
const populates = [
160-
{ child: 'collaborators', root: rootName },
161-
]
162-
expect(helpers.populatedDataToJS(exampleState, path, populates))
163-
.to
164-
.have
165-
.deep
166-
.property(`${valName}.collaborators.abc`, true)
167-
expect(helpers.populatedDataToJS(exampleState, path, populates))
168-
.to
169-
.have
170-
.deep
171-
.property(`${valName}.collaborators.ABC.displayName`, exampleData.data[rootName].ABC.displayName)
177+
describe('list', () => {
178+
179+
describe('single param', () => {
180+
it('populates value', () => {
181+
const path = 'projects'
182+
const rootName = 'users'
183+
const valName = 'CDF'
184+
expect(helpers.populatedDataToJS(exampleState, path, [{ child: 'owner', root: rootName }])[valName].owner)
185+
.to
186+
.have
187+
.property('displayName', 'scott')
188+
})
189+
190+
it('populates childParam', () => {
191+
const path = 'projects'
192+
const rootName = 'users'
193+
const valName = 'CDF'
194+
expect(helpers.populatedDataToJS(exampleState, path, [{ child: 'owner', root: rootName, childParam: 'displayName' }])[valName].owner)
195+
.to
196+
.have
197+
.equal('scott')
198+
})
199+
})
200+
201+
describe('list param', () => {
202+
it('populates values', () => {
203+
const path = 'projects'
204+
const rootName = 'users'
205+
const valName = 'OKF'
206+
const populates = [
207+
{ child: 'collaborators', root: rootName },
208+
]
209+
const populatedData = helpers.populatedDataToJS(exampleState, path, populates)
210+
expect(populatedData)
211+
.to
212+
.have
213+
.deep
214+
.property(`${valName}.collaborators.ABC.displayName`, exampleData.data[rootName].ABC.displayName)
215+
})
216+
217+
it('keeps non-existant children', () => {
218+
const path = 'projects'
219+
const rootName = 'users'
220+
const valName = 'OKF'
221+
const populates = [
222+
{ child: 'collaborators', root: rootName },
223+
]
224+
expect(helpers.populatedDataToJS(exampleState, path, populates))
225+
.to
226+
.have
227+
.deep
228+
.property(`${valName}.collaborators.abc`, true)
229+
expect(helpers.populatedDataToJS(exampleState, path, populates))
230+
.to
231+
.have
232+
.deep
233+
.property(`${valName}.collaborators.ABC.displayName`, exampleData.data[rootName].ABC.displayName)
234+
})
235+
})
172236
})
173237

174238
it('populates multiple children', () => {
@@ -179,6 +243,7 @@ describe('Helpers:', () => {
179243
{ child: 'owner', root: rootName },
180244
{ child: 'collaborators', root: rootName },
181245
]
246+
// TODO: Test both children are populated
182247
expect(helpers.populatedDataToJS(exampleState, path, populates))
183248
.to
184249
.have
@@ -223,6 +288,10 @@ describe('Helpers:', () => {
223288
it('returns true when is loaded', () => {
224289
expect(helpers.isLoaded('some')).to.be.true
225290
})
291+
292+
it('returns false when on argument is not loaded', () => {
293+
expect(helpers.isLoaded(undefined, {})).to.be.false
294+
})
226295
})
227296

228297
describe('isEmpty', () => {

0 commit comments

Comments
 (0)