diff --git a/.eslintignore b/.eslintignore
index 741fce2d2..d1a2d76c8 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,4 +1,4 @@
-example/**
+examples/**
coverage/**
node_modules/**
*.spec.js
diff --git a/examples/snippets/decorators/App.js b/examples/snippets/decorators/App.js
index d9c67ff63..d50960f99 100644
--- a/examples/snippets/decorators/App.js
+++ b/examples/snippets/decorators/App.js
@@ -4,10 +4,10 @@ import TodoItem from './TodoItem'
// redux/firebase
import { connect } from 'react-redux'
-import { firebase, helpers } from 'react-redux-firebase'
+import { firebaseConnect, helpers } from 'react-redux-firebase'
const { isLoaded, isEmpty, pathToJS, dataToJS } = helpers
-@firebase([
+@firebaseConnect([
'/todos'
])
@connect(
diff --git a/examples/snippets/populates/App.js b/examples/snippets/populates/App.js
new file mode 100644
index 000000000..2ac3c6213
--- /dev/null
+++ b/examples/snippets/populates/App.js
@@ -0,0 +1,57 @@
+import React, { PropTypes, Component } from 'react'
+import { map } from 'lodash'
+import TodoItem from './TodoItem'
+
+// redux/firebase
+import { connect } from 'react-redux'
+import { firebaseConnect, helpers } from 'react-redux-firebase'
+const { populatedDataToJS, isLoaded, pathToJS, dataToJS } = helpers
+const populates = [
+ { child: 'owner', root: 'users' },
+ // or if you want a param of the populate child such as user's display name
+ // { child: 'owner', root: 'users', childParam: 'displayName' }
+]
+
+@firebaseConnect([
+ { path: '/projects', populates },
+])
+@connect(
+ ({firebase}) => ({
+ projects: populatedDataToJS(firebase, '/projects', populates),
+ })
+)
+export default class App extends Component {
+ static propTypes = {
+ projects: PropTypes.shape({
+ name: PropTypes.string,
+ owner: PropTypes.object // string if using childParam: 'displayName'
+ }),
+ firebase: PropTypes.shape({
+ set: PropTypes.func.isRequired,
+ remove: PropTypes.func.isRequired
+ })
+ }
+ render () {
+ const { firebase, projects } = this.props
+
+ const projectsList = (!isLoaded(projects))
+ ? 'Loading'
+ : (isEmpty(projects))
+ ? 'Todo list is empty'
+ : map(projects, (todo, id) => (
+
+ Name: {project.name}
+ Owner: { owner.displayName || owner }
+
+ ))
+ return (
+
+
react-redux-firebase populate snippet
+
+
Projects List
+ {projectsList}
+
+
+ )
+ }
+}
diff --git a/package.json b/package.json
index 3f4260d20..d100a15ce 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "react-redux-firebase",
- "version": "1.2.2",
+ "version": "1.2.3",
"description": "Redux integration for Firebase. Comes with a Higher Order Component for use with React.",
"main": "dist/index.js",
"module": "src/index.js",
diff --git a/src/helpers.js b/src/helpers.js
index 890ef027e..3bc764d0f 100644
--- a/src/helpers.js
+++ b/src/helpers.js
@@ -184,16 +184,19 @@ export const dataToJS = (data, path, notSetValue) => {
* @param {Object} list - Path of parameter to load
* @param {Object} populate - Object with population settings
*/
-export const buildChildList = (data, list, populate) =>
+export const buildChildList = (data, list, p) =>
mapValues(list, (val, key) => {
let getKey = val
// Handle key: true lists
if (val === true) {
getKey = key
}
+ const pathString = p.childParam
+ ? `${p.root}/${getKey}/${p.childParam}`
+ : `${p.root}/${getKey}`
// Set to child under key if populate child exists
- if (dataToJS(data, `${populate.root}/${getKey}`)) {
- return dataToJS(data, `${populate.root}/${getKey}`)
+ if (dataToJS(data, pathString)) {
+ return dataToJS(data, pathString)
}
// Populate child does not exist
return val === true ? val : getKey
@@ -230,7 +233,7 @@ export const populatedDataToJS = (data, path, populates, notSetValue) => {
}
// Handle undefined child
if (!dataToJS(data, path, notSetValue)) {
- return undefined
+ return dataToJS(data, path, notSetValue)
}
const populateObjs = getPopulateObjs(populates)
// reduce array of populates to object of combined populated data
@@ -238,6 +241,20 @@ export const populatedDataToJS = (data, path, populates, notSetValue) => {
map(populateObjs, (p, obj) => {
// single item with iterable child
if (dataToJS(data, path)[p.child]) {
+ // populate child is key
+ if (isString(dataToJS(data, path)[p.child])) {
+ const pathString = p.childParam
+ ? `${p.root}/${dataToJS(data, path)[p.child]}/${p.childParam}`
+ : `${p.root}/${dataToJS(data, path)[p.child]}`
+ if (dataToJS(data, pathString)) {
+ return {
+ ...dataToJS(data, path),
+ [p.child]: dataToJS(data, pathString)
+ }
+ }
+ // matching child does not exist
+ return dataToJS(data, path)
+ }
return {
...dataToJS(data, path),
[p.child]: buildChildList(data, dataToJS(data, path)[p.child], p)
@@ -251,10 +268,13 @@ export const populatedDataToJS = (data, path, populates, notSetValue) => {
}
// populate child is key
if (isString(child[p.child])) {
- if (dataToJS(data, `${p.root}/${child[p.child]}`)) {
+ const pathString = p.childParam
+ ? `${p.root}/${child[p.child]}/${p.childParam}`
+ : `${p.root}/${child[p.child]}`
+ if (dataToJS(data, pathString)) {
return {
...child,
- [p.child]: dataToJS(data, `${p.root}/${child[p.child]}`)
+ [p.child]: dataToJS(data, pathString)
}
}
// matching child does not exist
diff --git a/src/utils/populate.js b/src/utils/populate.js
index 4e639b524..9ab9b0125 100644
--- a/src/utils/populate.js
+++ b/src/utils/populate.js
@@ -77,34 +77,24 @@ export const getPopulateChild = (firebase, populate, id) =>
* @param {Object} populate - Object containing populate information
* @param {Object} results - Object containing results of population from other populates
*/
-export const populateList = (firebase, originalData, p, results) => {
- const mainChild = p.child.split('[]')[0]
- const childParam = p.child.split('[]')[1]
+export const populateList = (firebase, list, p, results) => {
+ // Handle root not being defined
+ if (!results[p.root]) {
+ set(results, p.root, {})
+ }
return Promise.all(
- map(get(originalData, mainChild), (id, childKey) => {
+ map(list, (id, childKey) => {
// handle list of keys
const populateKey = id === true ? childKey : id
return getPopulateChild(
firebase,
p,
- childParam
- ? get(id, childParam) // get child parameter if [] notation
- : populateKey
+ populateKey
)
.then(pc => {
if (pc) {
// write child to result object under root name if it is found
- if (!childParam) {
- return set(results, `${p.root}.${populateKey}`, pc)
- }
- // handle child param
- return ({
- [childKey]: set(
- id,
- childParam,
- Object.assign(pc, { key: get(id, childParam) })
- )
- })
+ return set(results, `${p.root}.${populateKey}`, pc)
}
return results
})
@@ -131,7 +121,9 @@ export const promisesForPopulate = (firebase, originalData, populatesIn) => {
// Single parameter with list
if (has(originalData, mainChild)) {
- return promisesArray.push(populateList(firebase, originalData, p, results))
+ return promisesArray.push(
+ populateList(firebase, originalData[mainChild], p, results)
+ )
}
// Loop over each object in list
forEach(originalData, (d, key) => {
@@ -161,7 +153,7 @@ export const promisesForPopulate = (firebase, originalData, populatesIn) => {
if (isArray(idOrList) || isObject(idOrList)) {
// Create single promise that includes a promise for each child
return promisesArray.push(
- populateList(firebase, originalData, p, results)
+ populateList(firebase, idOrList, p, results)
)
}
})
diff --git a/test/unit/helpers.spec.js b/test/unit/helpers.spec.js
index 34525d47e..652f827b2 100644
--- a/test/unit/helpers.spec.js
+++ b/test/unit/helpers.spec.js
@@ -127,48 +127,112 @@ describe('Helpers:', () => {
.equal(exampleData.data[path])
})
- it('populates child', () => {
- const path = 'projects'
- const rootName = 'users'
- const valName = 'CDF'
- expect(helpers.populatedDataToJS(exampleState, path, [{ child: 'owner', root: rootName }])[valName].owner)
- .to
- .have
- .property('displayName', 'scott')
- })
+ describe('single', () => {
+ describe('single param', () => {
+ it('populates value', () => {
+ const path = 'projects/CDF'
+ const rootName = 'users'
+ console.log('--------', helpers.populatedDataToJS(exampleState, path, [{ child: 'owner', root: rootName }]).owner)
+ expect(helpers.populatedDataToJS(exampleState, path, [{ child: 'owner', root: rootName }]).owner)
+ .to
+ .have
+ .property('displayName', 'scott')
+ })
+
+ it('populates childParam', () => {
+ const path = 'projects/CDF'
+ const rootName = 'users'
+ expect(helpers.populatedDataToJS(exampleState, path, [{ child: 'owner', root: rootName, childParam: 'displayName' }]).owner)
+ .to
+ .have
+ .equal('scott')
+ })
+ it('keeps non-existant children', () => {
+ const path = 'projects/OKF'
+ const rootName = 'users'
+ expect(helpers.populatedDataToJS(exampleState, path, [{ child: 'owner', root: rootName }]).owner)
+ .to
+ .have
+ .equal('asdfasdf')
+ })
+ })
+ describe('list param', () => {
+ it('populates values', () => {
+ const path = 'projects/OKF'
+ const rootName = 'users'
+ const populates = [
+ { child: 'collaborators', root: rootName },
+ ]
+ const populatedData = helpers.populatedDataToJS(exampleState, path, populates)
+ expect(populatedData)
+ .to
+ .have
+ .deep
+ .property(`collaborators.ABC.displayName`, exampleData.data[rootName].ABC.displayName)
+ })
+ })
- it('populates child list', () => {
- const path = 'projects'
- const rootName = 'users'
- const valName = 'OKF'
- const populates = [
- { child: 'collaborators', root: rootName },
- ]
- const populatedData = helpers.populatedDataToJS(exampleState, path, populates)
- expect(populatedData)
- .to
- .have
- .deep
- .property(`${valName}.collaborators.ABC.displayName`, exampleData.data[rootName].ABC.displayName)
})
- it('handles non existant children', () => {
- const path = 'projects'
- const rootName = 'users'
- const valName = 'OKF'
- const populates = [
- { child: 'collaborators', root: rootName },
- ]
- expect(helpers.populatedDataToJS(exampleState, path, populates))
- .to
- .have
- .deep
- .property(`${valName}.collaborators.abc`, true)
- expect(helpers.populatedDataToJS(exampleState, path, populates))
- .to
- .have
- .deep
- .property(`${valName}.collaborators.ABC.displayName`, exampleData.data[rootName].ABC.displayName)
+ describe('list', () => {
+
+ describe('single param', () => {
+ it('populates value', () => {
+ const path = 'projects'
+ const rootName = 'users'
+ const valName = 'CDF'
+ expect(helpers.populatedDataToJS(exampleState, path, [{ child: 'owner', root: rootName }])[valName].owner)
+ .to
+ .have
+ .property('displayName', 'scott')
+ })
+
+ it('populates childParam', () => {
+ const path = 'projects'
+ const rootName = 'users'
+ const valName = 'CDF'
+ expect(helpers.populatedDataToJS(exampleState, path, [{ child: 'owner', root: rootName, childParam: 'displayName' }])[valName].owner)
+ .to
+ .have
+ .equal('scott')
+ })
+ })
+
+ describe('list param', () => {
+ it('populates values', () => {
+ const path = 'projects'
+ const rootName = 'users'
+ const valName = 'OKF'
+ const populates = [
+ { child: 'collaborators', root: rootName },
+ ]
+ const populatedData = helpers.populatedDataToJS(exampleState, path, populates)
+ expect(populatedData)
+ .to
+ .have
+ .deep
+ .property(`${valName}.collaborators.ABC.displayName`, exampleData.data[rootName].ABC.displayName)
+ })
+
+ it('keeps non-existant children', () => {
+ const path = 'projects'
+ const rootName = 'users'
+ const valName = 'OKF'
+ const populates = [
+ { child: 'collaborators', root: rootName },
+ ]
+ expect(helpers.populatedDataToJS(exampleState, path, populates))
+ .to
+ .have
+ .deep
+ .property(`${valName}.collaborators.abc`, true)
+ expect(helpers.populatedDataToJS(exampleState, path, populates))
+ .to
+ .have
+ .deep
+ .property(`${valName}.collaborators.ABC.displayName`, exampleData.data[rootName].ABC.displayName)
+ })
+ })
})
it('populates multiple children', () => {
@@ -179,6 +243,7 @@ describe('Helpers:', () => {
{ child: 'owner', root: rootName },
{ child: 'collaborators', root: rootName },
]
+ // TODO: Test both children are populated
expect(helpers.populatedDataToJS(exampleState, path, populates))
.to
.have
@@ -223,6 +288,10 @@ describe('Helpers:', () => {
it('returns true when is loaded', () => {
expect(helpers.isLoaded('some')).to.be.true
})
+
+ it('returns false when on argument is not loaded', () => {
+ expect(helpers.isLoaded(undefined, {})).to.be.false
+ })
})
describe('isEmpty', () => {
diff --git a/test/unit/utils/populate.spec.js b/test/unit/utils/populate.spec.js
index fbddf0074..4a8109167 100644
--- a/test/unit/utils/populate.spec.js
+++ b/test/unit/utils/populate.spec.js
@@ -3,6 +3,7 @@ import {
getPopulateObj,
getPopulates,
getPopulateChild,
+ getPopulateObjs,
promisesForPopulate
} from '../../../src/utils/populate'
@@ -11,15 +12,30 @@ describe('Utils: Populate', () => {
it('returns object with child and root', () => {
expect(getPopulateObj('some:value')).to.have.keys('child', 'root')
})
+ it('returns object if passed', () => {
+ const inputObj = { child: 'some', root: 'some' }
+ expect(getPopulateObj(inputObj)).to.equal(inputObj)
+ })
+ })
+
+ describe('getPopulateObj', () => {
+ it('returns object with child and root', () => {
+ expect(getPopulateObjs(['some:value'])[0]).to.have.keys('child', 'root')
+ })
+ it('handles basic populates', () => {
+ const inputString = 'populate=uid:users'
+ expect(getPopulateObjs(inputString)).to.equal(inputString)
+ })
})
describe('getPopulates', () => {
- it('no populates', () => {
+ it('handles no populates', () => {
expect(getPopulates(['orderByPriority']))
})
- it('basic populates', () => {
+ it('handles basic populates', () => {
expect(getPopulates(['populate=uid:users']))
})
+
})
describe('getPopulateChild', () => {
@@ -30,22 +46,54 @@ describe('Utils: Populate', () => {
})
describe('promisesForPopulate', () => {
- it('none existant child', () =>
- promisesForPopulate(Firebase, {uid: '123123'}, [{child: 'random', root: 'users'}])
+ it('handles non-existant single child', () =>
+ promisesForPopulate(Firebase, { uid: '123123' }, [{child: 'random', root: 'users'}])
.then((v) => {
expect(JSON.stringify(v)).to.equal(JSON.stringify({}))
})
)
- it('string populate', () =>
+
+ it('populates single property containing a list', () =>
+ promisesForPopulate(Firebase, { collaborators: { 'Iq5b0qK2NtgggT6U3bU6iZRGyma2': true, '123': true } }, [{child: 'collaborators', root: 'users'}])
+ .then((v) => {
+ expect(v).to.exist
+ expect(v).to.have.keys('users')
+ expect(v.users['Iq5b0qK2NtgggT6U3bU6iZRGyma2']).to.be.an.object
+ })
+ )
+
+ it('populates list with single property populate', () =>
promisesForPopulate(Firebase, { 1: { owner: 'Iq5b0qK2NtgggT6U3bU6iZRGyma2' } }, [{child: 'owner', root: 'users'}])
.then((v) => {
expect(v).to.have.keys('users')
+ expect(v.users['Iq5b0qK2NtgggT6U3bU6iZRGyma2']).to.be.an.object
})
)
- it('array populate', () =>
+
+ it('populates list with property containing array property', () =>
promisesForPopulate(Firebase, { 1: { collaborators: ['Iq5b0qK2NtgggT6U3bU6iZRGyma2', '123'] } }, [{child: 'collaborators', root: 'users'}])
.then((v) => {
expect(v).to.exist
+ expect(v).to.have.keys('users')
+ expect(v.users['Iq5b0qK2NtgggT6U3bU6iZRGyma2']).to.be.an.object
+ })
+ )
+
+ it('populates list with property containing firebase list', () =>
+ promisesForPopulate(Firebase, { 1: { collaborators: { 'Iq5b0qK2NtgggT6U3bU6iZRGyma2': true, '123': true } } }, [{child: 'collaborators', root: 'users'}])
+ .then((v) => {
+ expect(v).to.exist
+ expect(v).to.have.keys('users')
+ expect(v.users['Iq5b0qK2NtgggT6U3bU6iZRGyma2']).to.be.an.object
+ })
+ )
+
+ it('populates list with property containing invalid child id', () =>
+ promisesForPopulate(Firebase, { 1: { collaborators: ['1111', '123'] } }, [{child: 'collaborators', root: 'users'}])
+ .then((v) => {
+ expect(v).to.exist
+ expect(v.users).to.have.keys('123') // sets valid child
+ expect(v.users).to.not.have.keys('111') // does not set invalid child
})
)
})