Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading