Skip to content

Commit

Permalink
Version v1.2.3 (#52)
Browse files Browse the repository at this point in the history
* 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`
  • Loading branch information
prescottprue committed Feb 1, 2017
1 parent 366c4ff commit 8811cda
Show file tree
Hide file tree
Showing 8 changed files with 261 additions and 75 deletions.
2 changes: 1 addition & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
example/**
examples/**
coverage/**
node_modules/**
*.spec.js
4 changes: 2 additions & 2 deletions examples/snippets/decorators/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
57 changes: 57 additions & 0 deletions examples/snippets/populates/App.js
Original file line number Diff line number Diff line change
@@ -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) => (
<div>
Name: {project.name}
Owner: { owner.displayName || owner }
</div>
))
return (
<div>
<h2>react-redux-firebase populate snippet</h2>
<div>
<h4>Projects List</h4>
{projectsList}
</div>
</div>
)
}
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
32 changes: 26 additions & 6 deletions src/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -230,14 +233,28 @@ 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
return reduce(
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)
Expand All @@ -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
Expand Down
32 changes: 12 additions & 20 deletions src/utils/populate.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
})
Expand All @@ -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) => {
Expand Down Expand Up @@ -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)
)
}
})
Expand Down
147 changes: 108 additions & 39 deletions test/unit/helpers.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand All @@ -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
Expand Down Expand Up @@ -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', () => {
Expand Down

0 comments on commit 8811cda

Please sign in to comment.