mirror of
https://github.com/zhigang1992/reactfire.git
synced 2026-04-22 11:17:10 +08:00
Merge pull request #43 from firebase/jw-array-fixes
Fixed ordering issues with bindAsArray() and beefed up test suite
This commit is contained in:
@@ -46,7 +46,9 @@
|
||||
"gulp-mocha": "^2.1.2",
|
||||
"gulp-uglify": "^1.2.0",
|
||||
"jsdom": "3.x.x",
|
||||
"run-sequence": "^1.1.1"
|
||||
"run-sequence": "^1.1.1",
|
||||
"sinon": "^1.15.4",
|
||||
"sinon-chai": "^2.8.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "gulp test",
|
||||
|
||||
390
src/reactfire.js
390
src/reactfire.js
@@ -27,21 +27,272 @@
|
||||
}(this, function() {
|
||||
'use strict';
|
||||
|
||||
/*************/
|
||||
/* HELPERS */
|
||||
/*************/
|
||||
/**
|
||||
* Returns the index of the key in the list. If an item with the key is not in the list, -1 is
|
||||
* returned.
|
||||
*
|
||||
* @param {Array<any>} list A list of items.
|
||||
* @param {string} key The key for which to search.
|
||||
* @return {number} The index of the item which has the provided key or -1 if no items have the
|
||||
* provided key.
|
||||
*/
|
||||
function _indexForKey(list, key) {
|
||||
for (var i = 0, length = list.length; i < length; ++i) {
|
||||
if (list[i].$key === key) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws a formatted error message.
|
||||
*
|
||||
* @param {string} message The error message to throw.
|
||||
*/
|
||||
function _throwError(message) {
|
||||
throw new Error('ReactFire: ' + message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the name of the variable which is being bound.
|
||||
*
|
||||
* @param {string} bindVar The variable which is being bound.
|
||||
*/
|
||||
function _validateBindVar(bindVar) {
|
||||
var errorMessage;
|
||||
|
||||
if (typeof bindVar !== 'string') {
|
||||
errorMessage = 'Bind variable must be a string. Got: ' + bindVar;
|
||||
} else if (bindVar.length === 0) {
|
||||
errorMessage = 'Bind variable must be a non-empty string. Got: ""';
|
||||
} else if (bindVar.length > 768) {
|
||||
// Firebase can only stored child paths up to 768 characters
|
||||
errorMessage = 'Bind variable is too long to be stored in Firebase. Got: ' + bindVar;
|
||||
} else if (/[\[\].#$\/\u0000-\u001F\u007F]/.test(bindVar)) {
|
||||
// Firebase does not allow node keys to contain the following characters
|
||||
errorMessage = 'Bind variable cannot contain any of the following characters: . # $ ] [ /. Got: ' + bindVar;
|
||||
}
|
||||
|
||||
if (typeof errorMessage !== 'undefined') {
|
||||
_throwError(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/******************************/
|
||||
/* BIND AS OBJECT LISTENERS */
|
||||
/******************************/
|
||||
/**
|
||||
* 'value' listener which updates the value of the bound state variable.
|
||||
*
|
||||
* @param {string} bindVar The state variable to which the data is being bound.
|
||||
* @param {Firebase.DataSnapshot} snapshot A snapshot of the data being bound.
|
||||
*/
|
||||
function _objectValue(bindVar, snapshot) {
|
||||
this.data[bindVar] = snapshot.val();
|
||||
this.setState(this.data);
|
||||
}
|
||||
|
||||
|
||||
/*****************************/
|
||||
/* BIND AS ARRAY LISTENERS */
|
||||
/*****************************/
|
||||
/**
|
||||
* Creates a new array record record given a key-value pair.
|
||||
*
|
||||
* @param {string} key The new record's key.
|
||||
* @param {any} value The new record's value.
|
||||
* @return {Object} The new record.
|
||||
*/
|
||||
function _createRecord(key, value) {
|
||||
var record = {};
|
||||
if (typeof value === 'object') {
|
||||
record = value;
|
||||
} else {
|
||||
record.$value = value;
|
||||
}
|
||||
record.$key = key;
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
/**
|
||||
* 'child_added' listener which adds a new record to the bound array.
|
||||
*
|
||||
* @param {string} bindVar The state variable to which the data is being bound.
|
||||
* @param {Firebase.DataSnapshot} snapshot A snapshot of the data being bound.
|
||||
* @param {string|null} previousChildKey The key of the child after which the provided snapshot
|
||||
* is positioned; null if the provided snapshot is in the first position.
|
||||
*/
|
||||
function _arrayChildAdded(bindVar, snapshot, previousChildKey) {
|
||||
var key = snapshot.key();
|
||||
var value = snapshot.val();
|
||||
var array = this.data[bindVar];
|
||||
|
||||
// Determine where to insert the new record
|
||||
var insertionIndex;
|
||||
if (previousChildKey === null) {
|
||||
insertionIndex = 0;
|
||||
} else {
|
||||
var previousChildIndex = _indexForKey(array, previousChildKey);
|
||||
insertionIndex = previousChildIndex + 1;
|
||||
}
|
||||
|
||||
// Add the new record to the array
|
||||
array.splice(insertionIndex, 0, _createRecord(key, value));
|
||||
|
||||
// Update state
|
||||
this.setState(this.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'child_removed' listener which removes a record from the bound array.
|
||||
*
|
||||
* @param {string} bindVar The state variable to which the data is bound.
|
||||
* @param {Firebase.DataSnapshot} snapshot A snapshot of the bound data.
|
||||
*/
|
||||
function _arrayChildRemoved(bindVar, snapshot) {
|
||||
var array = this.data[bindVar];
|
||||
|
||||
// Look up the record's index in the array
|
||||
var index = _indexForKey(array, snapshot.key());
|
||||
|
||||
// Splice out the record from the array
|
||||
array.splice(index, 1);
|
||||
|
||||
// Update state
|
||||
this.setState(this.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'child_changed' listener which updates a record's value in the bound array.
|
||||
*
|
||||
* @param {string} bindVar The state variable to which the data is bound.
|
||||
* @param {Firebase.DataSnapshot} snapshot A snapshot of the data to bind.
|
||||
*/
|
||||
function _arrayChildChanged(bindVar, snapshot) {
|
||||
var key = snapshot.key();
|
||||
var value = snapshot.val();
|
||||
var array = this.data[bindVar];
|
||||
|
||||
// Look up the record's index in the array
|
||||
var index = _indexForKey(array, key);
|
||||
|
||||
// Update the record's value in the array
|
||||
array[index] = _createRecord(key, value);
|
||||
|
||||
// Update state
|
||||
this.setState(this.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'child_moved' listener which updates a record's position in the bound array.
|
||||
*
|
||||
* @param {string} bindVar The state variable to which the data is bound.
|
||||
* @param {Firebase.DataSnapshot} snapshot A snapshot of the bound data.
|
||||
* @param {string|null} previousChildKey The key of the child after which the provided snapshot
|
||||
* is positioned; null if the provided snapshot is in the first position.
|
||||
*/
|
||||
function _arrayChildMoved(bindVar, snapshot, previousChildKey) {
|
||||
var key = snapshot.key();
|
||||
var array = this.data[bindVar];
|
||||
|
||||
// Look up the record's index in the array
|
||||
var currentIndex = _indexForKey(array, key);
|
||||
|
||||
// Splice out the record from the array
|
||||
var record = array.splice(currentIndex, 1)[0];
|
||||
|
||||
// Determine where to re-insert the record
|
||||
var insertionIndex;
|
||||
if (previousChildKey === null) {
|
||||
insertionIndex = 0;
|
||||
} else {
|
||||
var previousChildIndex = _indexForKey(array, previousChildKey);
|
||||
insertionIndex = previousChildIndex + 1;
|
||||
}
|
||||
|
||||
// Re-insert the record into the array
|
||||
array.splice(insertionIndex, 0, record);
|
||||
|
||||
// Update state
|
||||
this.setState(this.data);
|
||||
}
|
||||
|
||||
|
||||
/*************/
|
||||
/* BINDING */
|
||||
/*************/
|
||||
/**
|
||||
* Creates a binding between Firebase and the inputted bind variable as either an array or
|
||||
* an object.
|
||||
*
|
||||
* @param {Firebase} firebaseRef The Firebase ref whose data to bind.
|
||||
* @param {string} bindVar The state variable to which to bind the data.
|
||||
* @param {function} cancelCallback The Firebase reference's cancel callback.
|
||||
* @param {boolean} bindAsArray Whether or not to bind as an array or object.
|
||||
*/
|
||||
function _bind(firebaseRef, bindVar, cancelCallback, bindAsArray) {
|
||||
if (Object.prototype.toString.call(firebaseRef) !== '[object Object]') {
|
||||
_throwError('Invalid Firebase reference');
|
||||
}
|
||||
|
||||
_validateBindVar(bindVar);
|
||||
|
||||
if (typeof this.firebaseRefs[bindVar] !== 'undefined') {
|
||||
_throwError('this.state.' + bindVar + ' is already bound to a Firebase reference');
|
||||
}
|
||||
|
||||
// Keep track of the Firebase reference we are setting up listeners on
|
||||
this.firebaseRefs[bindVar] = firebaseRef.ref();
|
||||
|
||||
if (bindAsArray) {
|
||||
// Set initial state to an empty array
|
||||
this.data[bindVar] = [];
|
||||
this.setState(this.data);
|
||||
|
||||
// Add listeners for all 'child_*' events
|
||||
this.firebaseListeners[bindVar] = {
|
||||
child_added: firebaseRef.on('child_added', _arrayChildAdded.bind(this, bindVar), cancelCallback),
|
||||
child_removed: firebaseRef.on('child_removed', _arrayChildRemoved.bind(this, bindVar), cancelCallback),
|
||||
child_changed: firebaseRef.on('child_changed', _arrayChildChanged.bind(this, bindVar), cancelCallback),
|
||||
child_moved: firebaseRef.on('child_moved', _arrayChildMoved.bind(this, bindVar), cancelCallback)
|
||||
};
|
||||
} else {
|
||||
// Add listener for 'value' event
|
||||
this.firebaseListeners[bindVar] = {
|
||||
value: firebaseRef.on('value', _objectValue.bind(this, bindVar), cancelCallback)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var ReactFireMixin = {
|
||||
/********************/
|
||||
/* MIXIN LIFETIME */
|
||||
/********************/
|
||||
/* Initializes the Firebase binding refs array */
|
||||
/**
|
||||
* Initializes the Firebase refs and listeners arrays.
|
||||
**/
|
||||
componentWillMount: function() {
|
||||
this.data = {};
|
||||
this.firebaseRefs = {};
|
||||
this.firebaseListeners = {};
|
||||
},
|
||||
|
||||
/* Removes any remaining Firebase bindings */
|
||||
/**
|
||||
* Unbinds any remaining Firebase listeners.
|
||||
*/
|
||||
componentWillUnmount: function() {
|
||||
for (var key in this.firebaseRefs) {
|
||||
if (this.firebaseRefs.hasOwnProperty(key)) {
|
||||
this.unbind(key);
|
||||
for (var bindVar in this.firebaseRefs) {
|
||||
if (this.firebaseRefs.hasOwnProperty(bindVar)) {
|
||||
this.unbind(bindVar);
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -50,114 +301,57 @@
|
||||
/*************/
|
||||
/* BINDING */
|
||||
/*************/
|
||||
/* Creates a binding between Firebase and the inputted bind variable as an array */
|
||||
/**
|
||||
* Creates a binding between Firebase and the inputted bind variable as an array.
|
||||
*
|
||||
* @param {Firebase} firebaseRef The Firebase ref whose data to bind.
|
||||
* @param {string} bindVar The state variable to which to bind the data.
|
||||
* @param {function} cancelCallback The Firebase reference's cancel callback.
|
||||
*/
|
||||
bindAsArray: function(firebaseRef, bindVar, cancelCallback) {
|
||||
this._bind(firebaseRef, bindVar, cancelCallback, true);
|
||||
var bindPartial = _bind.bind(this);
|
||||
bindPartial(firebaseRef, bindVar, cancelCallback, /* bindAsArray */ true);
|
||||
},
|
||||
|
||||
/* Creates a binding between Firebase and the inputted bind variable as an object */
|
||||
/**
|
||||
* Creates a binding between Firebase and the inputted bind variable as an object.
|
||||
*
|
||||
* @param {Firebase} firebaseRef The Firebase ref whose data to bind.
|
||||
* @param {string} bindVar The state variable to which to bind the data.
|
||||
* @param {function} cancelCallback The Firebase reference's cancel callback.
|
||||
*/
|
||||
bindAsObject: function(firebaseRef, bindVar, cancelCallback) {
|
||||
this._bind(firebaseRef, bindVar, cancelCallback, false);
|
||||
var bindPartial = _bind.bind(this);
|
||||
bindPartial(firebaseRef, bindVar, cancelCallback, /* bindAsArray */ false);
|
||||
},
|
||||
|
||||
/* Throw a formatted error message */
|
||||
_throwError: function(message) {
|
||||
throw new Error('ReactFire: ' + message);
|
||||
},
|
||||
|
||||
/* Creates a binding between Firebase and the inputted bind variable as either an array or object */
|
||||
_bind: function(firebaseRef, bindVar, cancelCallback, bindAsArray) {
|
||||
this._validateBindVar(bindVar);
|
||||
|
||||
if (Object.prototype.toString.call(firebaseRef) !== '[object Object]') {
|
||||
this._throwError('firebaseRef must be an instance of Firebase');
|
||||
}
|
||||
|
||||
this.firebaseRefs[bindVar] = firebaseRef.ref();
|
||||
this.firebaseListeners[bindVar] = firebaseRef.on('value', function(dataSnapshot) {
|
||||
var newState = {};
|
||||
if (bindAsArray) {
|
||||
newState[bindVar] = this._toArray(dataSnapshot.val());
|
||||
} else {
|
||||
newState[bindVar] = dataSnapshot.val();
|
||||
}
|
||||
this.setState(newState);
|
||||
}.bind(this), cancelCallback);
|
||||
},
|
||||
|
||||
/* Removes the binding between Firebase and the inputted bind variable */
|
||||
/**
|
||||
* Removes the binding between Firebase and the inputted bind variable.
|
||||
*
|
||||
* @param {string} bindVar The state variable to which the data is bound.
|
||||
* @param {function} callback Called when the data is unbound and the state has been updated.
|
||||
*/
|
||||
unbind: function(bindVar, callback) {
|
||||
this._validateBindVar(bindVar);
|
||||
_validateBindVar(bindVar);
|
||||
|
||||
if (typeof this.firebaseRefs[bindVar] === 'undefined') {
|
||||
this._throwError('unexpected value for bindVar. "' + bindVar + '" was either never bound or has already been unbound');
|
||||
_throwError('this.state.' + bindVar + ' is not bound to a Firebase reference');
|
||||
}
|
||||
|
||||
this.firebaseRefs[bindVar].off('value', this.firebaseListeners[bindVar]);
|
||||
delete this.firebaseRefs[bindVar];
|
||||
delete this.firebaseListeners[bindVar];
|
||||
// Turn off all Firebase listeners
|
||||
for (var event in this.firebaseListeners[bindVar]) {
|
||||
if (this.firebaseListeners[bindVar].hasOwnProperty(event)) {
|
||||
var offListener = this.firebaseListeners[bindVar][event];
|
||||
this.firebaseRefs[bindVar].off(event, offListener);
|
||||
}
|
||||
}
|
||||
this.firebaseRefs[bindVar] = undefined;
|
||||
this.firebaseListeners[bindVar] = undefined;
|
||||
|
||||
// Update state
|
||||
var newState = {};
|
||||
newState[bindVar] = undefined;
|
||||
this.setState(newState, callback);
|
||||
},
|
||||
|
||||
|
||||
/*************/
|
||||
/* HELPERS */
|
||||
/*************/
|
||||
/* Validates the name of the variable which is being bound */
|
||||
_validateBindVar: function(bindVar) {
|
||||
var errorMessage;
|
||||
|
||||
if (typeof bindVar !== 'string') {
|
||||
errorMessage = 'bindVar must be a string. Got: ' + bindVar;
|
||||
} else if (bindVar.length === 0) {
|
||||
errorMessage = 'bindVar must be a non-empty string. Got: ""';
|
||||
} else if (bindVar.length > 768) {
|
||||
// Firebase can only stored child paths up to 768 characters
|
||||
errorMessage = 'bindVar is too long to be stored in Firebase. Got: ' + bindVar;
|
||||
} else if (/[\[\].#$\/\u0000-\u001F\u007F]/.test(bindVar)) {
|
||||
// Firebase does not allow node keys to contain the following characters
|
||||
errorMessage = 'bindVar cannot contain any of the following characters: . # $ ] [ /. Got: ' + bindVar;
|
||||
}
|
||||
|
||||
if (typeof errorMessage !== 'undefined') {
|
||||
this._throwError(errorMessage);
|
||||
}
|
||||
},
|
||||
|
||||
/* Returns true if the inputted object is a JavaScript array */
|
||||
_isArray: function(obj) {
|
||||
return (Object.prototype.toString.call(obj) === '[object Array]');
|
||||
},
|
||||
|
||||
/* Converts a Firebase object to a JavaScript array */
|
||||
_toArray: function(obj) {
|
||||
var item;
|
||||
var out = [];
|
||||
if (obj) {
|
||||
if (this._isArray(obj)) {
|
||||
for (var i = 0, length = obj.length; i < length; i++) {
|
||||
item = obj[i];
|
||||
if (item !== undefined && item !== null) {
|
||||
out.push({ $key: i, $value: item });
|
||||
}
|
||||
}
|
||||
} else if (typeof obj === 'object') {
|
||||
for (var key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
item = obj[key];
|
||||
if (typeof item !== 'object') {
|
||||
item = { $value: item };
|
||||
}
|
||||
item.$key = key;
|
||||
out.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
// Mocha / Chai
|
||||
// Mocha / Chai / Sinon
|
||||
var chai = require('chai');
|
||||
var expect = chai.expect;
|
||||
var sinon = require('sinon');
|
||||
chai.use(require('sinon-chai'));
|
||||
|
||||
// React
|
||||
var React = require('react/addons');
|
||||
@@ -54,7 +56,7 @@ describe('ReactFire', function() {
|
||||
TH.invalidFirebaseRefs.forEach(function(invalidFirebaseRef) {
|
||||
expect(function() {
|
||||
_this.bindAsArray(invalidFirebaseRef, 'items');
|
||||
}).to.throw('ReactFire: firebaseRef must be an instance of Firebase');
|
||||
}).to.throw('ReactFire: Invalid Firebase reference');
|
||||
});
|
||||
},
|
||||
|
||||
@@ -76,7 +78,7 @@ describe('ReactFire', function() {
|
||||
TH.invalidBindVars.forEach(function(invalidBindVar) {
|
||||
expect(function() {
|
||||
_this.bindAsArray(firebaseRef, invalidBindVar);
|
||||
}).to.throw(/bindVar/);
|
||||
}).to.throw(/Bind variable/);
|
||||
});
|
||||
},
|
||||
|
||||
@@ -88,7 +90,28 @@ describe('ReactFire', function() {
|
||||
shallowRenderer.render(React.createElement(TestComponent));
|
||||
});
|
||||
|
||||
it('binds array items which are objects', function(done) {
|
||||
it('throws error given an already bound bind variable', function() {
|
||||
var TestComponent = React.createClass({
|
||||
mixins: [ReactFireMixin],
|
||||
|
||||
componentWillMount: function() {
|
||||
var _this = this;
|
||||
|
||||
expect(function() {
|
||||
_this.bindAsArray(firebaseRef, 'items');
|
||||
_this.bindAsArray(firebaseRef, 'items');
|
||||
}).to.throw('this.state.items is already bound to a Firebase reference');
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return React.DOM.div(null);
|
||||
}
|
||||
});
|
||||
|
||||
shallowRenderer.render(React.createElement(TestComponent));
|
||||
});
|
||||
|
||||
it('binds array records which are objects', function(done) {
|
||||
var TestComponent = React.createClass({
|
||||
mixins: [ReactFireMixin],
|
||||
|
||||
@@ -118,7 +141,7 @@ describe('ReactFire', function() {
|
||||
shallowRenderer.render(React.createElement(TestComponent));
|
||||
});
|
||||
|
||||
it('binds array items which are primitives', function(done) {
|
||||
it('binds array records which are primitives', function(done) {
|
||||
var TestComponent = React.createClass({
|
||||
mixins: [ReactFireMixin],
|
||||
|
||||
@@ -127,9 +150,9 @@ describe('ReactFire', function() {
|
||||
|
||||
firebaseRef.set(['first', 'second', 'third'], function() {
|
||||
expect(this.state.items).to.deep.equal([
|
||||
{ '$key': 0, '$value': 'first' },
|
||||
{ '$key': 1, '$value': 'second' },
|
||||
{ '$key': 2, '$value': 'third' }
|
||||
{ '$key': '0', '$value': 'first' },
|
||||
{ '$key': '1', '$value': 'second' },
|
||||
{ '$key': '2', '$value': 'third' }
|
||||
]);
|
||||
|
||||
done();
|
||||
@@ -144,6 +167,58 @@ describe('ReactFire', function() {
|
||||
shallowRenderer.render(React.createElement(TestComponent));
|
||||
});
|
||||
|
||||
it('binds array records which are a mix of objects and primitives', function(done) {
|
||||
var TestComponent = React.createClass({
|
||||
mixins: [ReactFireMixin],
|
||||
|
||||
componentWillMount: function() {
|
||||
this.bindAsArray(firebaseRef, 'items');
|
||||
|
||||
firebaseRef.set({
|
||||
0: 'first',
|
||||
1: 'second',
|
||||
third: { index: 2 }
|
||||
}, function() {
|
||||
expect(this.state.items).to.deep.equal([
|
||||
{ '$key': '0', '$value': 'first' },
|
||||
{ '$key': '1', '$value': 'second' },
|
||||
{ '$key': 'third', index: 2 }
|
||||
]);
|
||||
|
||||
done();
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return React.DOM.div(null);
|
||||
}
|
||||
});
|
||||
|
||||
shallowRenderer.render(React.createElement(TestComponent));
|
||||
});
|
||||
|
||||
it('binds as an empty array for Firebase references with no data', function(done) {
|
||||
var TestComponent = React.createClass({
|
||||
mixins: [ReactFireMixin],
|
||||
|
||||
componentWillMount: function() {
|
||||
this.bindAsArray(firebaseRef, 'items');
|
||||
|
||||
firebaseRef.set(null, function() {
|
||||
expect(this.state.items).to.deep.equal([]);
|
||||
|
||||
done();
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return React.DOM.div(null);
|
||||
}
|
||||
});
|
||||
|
||||
shallowRenderer.render(React.createElement(TestComponent));
|
||||
});
|
||||
|
||||
it('binds sparse arrays', function(done) {
|
||||
var TestComponent = React.createClass({
|
||||
mixins: [ReactFireMixin],
|
||||
@@ -152,13 +227,11 @@ describe('ReactFire', function() {
|
||||
this.bindAsArray(firebaseRef, 'items');
|
||||
|
||||
firebaseRef.set({ 0: 'a', 2: 'b', 5: 'c' }, function() {
|
||||
expect(this.state).to.deep.equal({
|
||||
items: [
|
||||
{ $key: 0, $value: 'a' },
|
||||
{ $key: 2, $value: 'b' },
|
||||
{ $key: 5, $value: 'c' }
|
||||
]
|
||||
});
|
||||
expect(this.state.items).to.deep.equal([
|
||||
{ $key: '0', $value: 'a' },
|
||||
{ $key: '2', $value: 'b' },
|
||||
{ $key: '5', $value: 'c' }
|
||||
]);
|
||||
|
||||
done();
|
||||
}.bind(this));
|
||||
@@ -172,7 +245,7 @@ describe('ReactFire', function() {
|
||||
shallowRenderer.render(React.createElement(TestComponent));
|
||||
});
|
||||
|
||||
it('binds with limit queries', function(done) {
|
||||
it('binds only a subset of records when using limit queries', function(done) {
|
||||
var TestComponent = React.createClass({
|
||||
mixins: [ReactFireMixin],
|
||||
|
||||
@@ -180,12 +253,10 @@ describe('ReactFire', function() {
|
||||
this.bindAsArray(firebaseRef.limitToLast(2), 'items');
|
||||
|
||||
firebaseRef.set({ a: 1, b: 2, c: 3 }, function() {
|
||||
expect(this.state).to.deep.equal({
|
||||
items: [
|
||||
{ $key: 'b', $value: 2 },
|
||||
{ $key: 'c', $value: 3 }
|
||||
]
|
||||
});
|
||||
expect(this.state.items).to.deep.equal([
|
||||
{ $key: 'b', $value: 2 },
|
||||
{ $key: 'c', $value: 3 }
|
||||
]);
|
||||
|
||||
done();
|
||||
}.bind(this));
|
||||
@@ -198,6 +269,270 @@ describe('ReactFire', function() {
|
||||
|
||||
shallowRenderer.render(React.createElement(TestComponent));
|
||||
});
|
||||
|
||||
it('removes records when they fall outside of a limit query', function(done) {
|
||||
var TestComponent = React.createClass({
|
||||
mixins: [ReactFireMixin],
|
||||
|
||||
componentWillMount: function() {
|
||||
this.bindAsArray(firebaseRef.limitToLast(2), 'items');
|
||||
|
||||
firebaseRef.set({ a: 1, b: 2, c: 3 }, function() {
|
||||
firebaseRef.child('d').set(4, function() {
|
||||
expect(this.state.items).to.deep.equal([
|
||||
{ $key: 'c', $value: 3 },
|
||||
{ $key: 'd', $value: 4 }
|
||||
]);
|
||||
|
||||
done();
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return React.DOM.div(null);
|
||||
}
|
||||
});
|
||||
|
||||
shallowRenderer.render(React.createElement(TestComponent));
|
||||
});
|
||||
|
||||
it('adds a new record when an existing record in the limit query is removed', function(done) {
|
||||
var TestComponent = React.createClass({
|
||||
mixins: [ReactFireMixin],
|
||||
|
||||
componentWillMount: function() {
|
||||
this.bindAsArray(firebaseRef.limitToLast(2), 'items');
|
||||
|
||||
firebaseRef.set({ a: 1, b: 2, c: 3 }, function() {
|
||||
firebaseRef.child('b').remove(function() {
|
||||
expect(this.state.items).to.deep.equal([
|
||||
{ $key: 'a', $value: 1 },
|
||||
{ $key: 'c', $value: 3 }
|
||||
]);
|
||||
|
||||
done();
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return React.DOM.div(null);
|
||||
}
|
||||
});
|
||||
|
||||
shallowRenderer.render(React.createElement(TestComponent));
|
||||
});
|
||||
|
||||
it('binds records in the correct order when using ordered queries', function(done) {
|
||||
var TestComponent = React.createClass({
|
||||
mixins: [ReactFireMixin],
|
||||
|
||||
componentWillMount: function() {
|
||||
this.bindAsArray(firebaseRef.orderByValue(), 'items');
|
||||
|
||||
firebaseRef.set({ a: 2, b: 1, c: 3 }, function() {
|
||||
expect(this.state.items).to.deep.equal([
|
||||
{ $key: 'b', $value: 1 },
|
||||
{ $key: 'a', $value: 2 },
|
||||
{ $key: 'c', $value: 3 }
|
||||
]);
|
||||
|
||||
done();
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return React.DOM.div(null);
|
||||
}
|
||||
});
|
||||
|
||||
shallowRenderer.render(React.createElement(TestComponent));
|
||||
});
|
||||
|
||||
it('binds multiple Firebase references to state variables at the same time', function(done) {
|
||||
var TestComponent = React.createClass({
|
||||
mixins: [ReactFireMixin],
|
||||
|
||||
componentWillMount: function() {
|
||||
this.bindAsArray(firebaseRef.child('items0'), 'bindVar0');
|
||||
this.bindAsArray(firebaseRef.child('items1'), 'bindVar1');
|
||||
|
||||
firebaseRef.set({
|
||||
items0: {
|
||||
first: { index: 0 },
|
||||
second: { index: 1 },
|
||||
third: { index: 2 }
|
||||
},
|
||||
items1: ['first', 'second', 'third']
|
||||
}, function() {
|
||||
expect(this.state.bindVar0).to.deep.equal([
|
||||
{ '$key': 'first', index: 0 },
|
||||
{ '$key': 'second', index: 1 },
|
||||
{ '$key': 'third', index: 2 }
|
||||
]);
|
||||
|
||||
expect(this.state.bindVar1).to.deep.equal([
|
||||
{ '$key': '0', '$value': 'first' },
|
||||
{ '$key': '1', '$value': 'second' },
|
||||
{ '$key': '2', '$value': 'third' }
|
||||
]);
|
||||
|
||||
done();
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return React.DOM.div(null);
|
||||
}
|
||||
});
|
||||
|
||||
shallowRenderer.render(React.createElement(TestComponent));
|
||||
});
|
||||
|
||||
it('updates an array record when its value changes', function(done) {
|
||||
var TestComponent = React.createClass({
|
||||
mixins: [ReactFireMixin],
|
||||
|
||||
componentWillMount: function() {
|
||||
this.bindAsArray(firebaseRef, 'items');
|
||||
|
||||
var _this = this;
|
||||
firebaseRef.set({ a: 1, b: 2, c: 3 }, function() {
|
||||
firebaseRef.child('b').set({ foo: 'bar' }, function() {
|
||||
expect(_this.state.items).to.deep.equal([
|
||||
{ $key: 'a', $value: 1 },
|
||||
{ $key: 'b', foo: 'bar' },
|
||||
{ $key: 'c', $value: 3 }
|
||||
]);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return React.DOM.div(null);
|
||||
}
|
||||
});
|
||||
|
||||
shallowRenderer.render(React.createElement(TestComponent));
|
||||
});
|
||||
|
||||
it('removes an array record when it is deleted', function(done) {
|
||||
var TestComponent = React.createClass({
|
||||
mixins: [ReactFireMixin],
|
||||
|
||||
componentWillMount: function() {
|
||||
this.bindAsArray(firebaseRef, 'items');
|
||||
|
||||
var _this = this;
|
||||
firebaseRef.set({ a: 1, b: 2, c: 3 }, function() {
|
||||
firebaseRef.child('b').remove(function() {
|
||||
expect(_this.state.items).to.deep.equal([
|
||||
{ $key: 'a', $value: 1 },
|
||||
{ $key: 'c', $value: 3 }
|
||||
]);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return React.DOM.div(null);
|
||||
}
|
||||
});
|
||||
|
||||
shallowRenderer.render(React.createElement(TestComponent));
|
||||
});
|
||||
|
||||
it('moves an array record when it\'s order changes (moved to start of array)', function(done) {
|
||||
var TestComponent = React.createClass({
|
||||
mixins: [ReactFireMixin],
|
||||
|
||||
componentWillMount: function() {
|
||||
this.bindAsArray(firebaseRef.orderByValue(), 'items');
|
||||
|
||||
var _this = this;
|
||||
firebaseRef.set({ a: 2, b: 3, c: 2 }, function() {
|
||||
firebaseRef.child('b').set(1, function() {
|
||||
expect(_this.state.items).to.deep.equal([
|
||||
{ $key: 'b', $value: 1 },
|
||||
{ $key: 'a', $value: 2 },
|
||||
{ $key: 'c', $value: 2 }
|
||||
]);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return React.DOM.div(null);
|
||||
}
|
||||
});
|
||||
|
||||
shallowRenderer.render(React.createElement(TestComponent));
|
||||
});
|
||||
|
||||
it('moves an array record when it\'s order changes (moved to middle of array)', function(done) {
|
||||
var TestComponent = React.createClass({
|
||||
mixins: [ReactFireMixin],
|
||||
|
||||
componentWillMount: function() {
|
||||
this.bindAsArray(firebaseRef.orderByValue(), 'items');
|
||||
|
||||
var _this = this;
|
||||
firebaseRef.set({ a: 2, b: 1, c: 4 }, function() {
|
||||
firebaseRef.child('b').set(3, function() {
|
||||
expect(_this.state.items).to.deep.equal([
|
||||
{ $key: 'a', $value: 2 },
|
||||
{ $key: 'b', $value: 3 },
|
||||
{ $key: 'c', $value: 4 }
|
||||
]);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return React.DOM.div(null);
|
||||
}
|
||||
});
|
||||
|
||||
shallowRenderer.render(React.createElement(TestComponent));
|
||||
});
|
||||
|
||||
it('moves an array record when it\'s order changes (moved to end of array)', function(done) {
|
||||
var TestComponent = React.createClass({
|
||||
mixins: [ReactFireMixin],
|
||||
|
||||
componentWillMount: function() {
|
||||
this.bindAsArray(firebaseRef.orderByValue(), 'items');
|
||||
|
||||
var _this = this;
|
||||
firebaseRef.set({ a: 2, b: 1, c: 3 }, function() {
|
||||
firebaseRef.child('b').set(4, function() {
|
||||
expect(_this.state.items).to.deep.equal([
|
||||
{ $key: 'a', $value: 2 },
|
||||
{ $key: 'c', $value: 3 },
|
||||
{ $key: 'b', $value: 4 }
|
||||
]);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return React.DOM.div(null);
|
||||
}
|
||||
});
|
||||
|
||||
shallowRenderer.render(React.createElement(TestComponent));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -212,7 +547,7 @@ describe('ReactFire', function() {
|
||||
TH.invalidFirebaseRefs.forEach(function(invalidFirebaseRef) {
|
||||
expect(function() {
|
||||
_this.bindAsObject(invalidFirebaseRef, 'items');
|
||||
}).to.throw('ReactFire: firebaseRef must be an instance of Firebase');
|
||||
}).to.throw('ReactFire: Invalid Firebase reference');
|
||||
});
|
||||
},
|
||||
|
||||
@@ -234,7 +569,7 @@ describe('ReactFire', function() {
|
||||
TH.invalidBindVars.forEach(function(invalidBindVar) {
|
||||
expect(function() {
|
||||
_this.bindAsObject(firebaseRef, invalidBindVar);
|
||||
}).to.throw(/bindVar/);
|
||||
}).to.throw(/Bind variable/);
|
||||
});
|
||||
},
|
||||
|
||||
@@ -246,7 +581,28 @@ describe('ReactFire', function() {
|
||||
shallowRenderer.render(React.createElement(TestComponent));
|
||||
});
|
||||
|
||||
it('binds objects', function(done) {
|
||||
it('throws error given an already bound bind variable', function() {
|
||||
var TestComponent = React.createClass({
|
||||
mixins: [ReactFireMixin],
|
||||
|
||||
componentWillMount: function() {
|
||||
var _this = this;
|
||||
|
||||
expect(function() {
|
||||
_this.bindAsObject(firebaseRef, 'items');
|
||||
_this.bindAsObject(firebaseRef, 'items');
|
||||
}).to.throw('this.state.items is already bound to a Firebase reference');
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return React.DOM.div(null);
|
||||
}
|
||||
});
|
||||
|
||||
shallowRenderer.render(React.createElement(TestComponent));
|
||||
});
|
||||
|
||||
it('binds to an object', function(done) {
|
||||
var TestComponent = React.createClass({
|
||||
mixins: [ReactFireMixin],
|
||||
|
||||
@@ -274,6 +630,50 @@ describe('ReactFire', function() {
|
||||
shallowRenderer.render(React.createElement(TestComponent));
|
||||
});
|
||||
|
||||
it('binds to a primitive', function(done) {
|
||||
var TestComponent = React.createClass({
|
||||
mixins: [ReactFireMixin],
|
||||
|
||||
componentWillMount: function() {
|
||||
this.bindAsObject(firebaseRef, 'items');
|
||||
|
||||
firebaseRef.set('foo', function() {
|
||||
expect(this.state.items).to.deep.equal('foo');
|
||||
|
||||
done();
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return React.DOM.div(null);
|
||||
}
|
||||
});
|
||||
|
||||
shallowRenderer.render(React.createElement(TestComponent));
|
||||
});
|
||||
|
||||
it('binds as null for Firebase references with no data', function(done) {
|
||||
var TestComponent = React.createClass({
|
||||
mixins: [ReactFireMixin],
|
||||
|
||||
componentWillMount: function() {
|
||||
this.bindAsObject(firebaseRef, 'items');
|
||||
|
||||
firebaseRef.set(null, function() {
|
||||
expect(this.state.items).to.be.null;
|
||||
|
||||
done();
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return React.DOM.div(null);
|
||||
}
|
||||
});
|
||||
|
||||
shallowRenderer.render(React.createElement(TestComponent));
|
||||
});
|
||||
|
||||
it('binds with limit queries', function(done) {
|
||||
var TestComponent = React.createClass({
|
||||
mixins: [ReactFireMixin],
|
||||
@@ -302,6 +702,92 @@ describe('ReactFire', function() {
|
||||
|
||||
shallowRenderer.render(React.createElement(TestComponent));
|
||||
});
|
||||
|
||||
it('binds multiple Firebase references to state variables at the same time', function(done) {
|
||||
var TestComponent = React.createClass({
|
||||
mixins: [ReactFireMixin],
|
||||
|
||||
componentWillMount: function() {
|
||||
this.bindAsObject(firebaseRef.child('items0'), 'bindVar0');
|
||||
this.bindAsObject(firebaseRef.child('items1'), 'bindVar1');
|
||||
|
||||
var items0 = {
|
||||
first: { index: 0 },
|
||||
second: { index: 1 },
|
||||
third: { index: 2 }
|
||||
};
|
||||
|
||||
var items1 = {
|
||||
bar: {
|
||||
foo: 'baz'
|
||||
},
|
||||
baz: true,
|
||||
foo: 100
|
||||
};
|
||||
|
||||
firebaseRef.set({
|
||||
items0: items0,
|
||||
items1: items1
|
||||
}, function() {
|
||||
expect(this.state.bindVar0).to.deep.equal(items0);
|
||||
expect(this.state.bindVar1).to.deep.equal(items1);
|
||||
|
||||
done();
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return React.DOM.div(null);
|
||||
}
|
||||
});
|
||||
|
||||
shallowRenderer.render(React.createElement(TestComponent));
|
||||
});
|
||||
|
||||
it('binds a mixture of arrays and objects to state variables at the same time', function(done) {
|
||||
var TestComponent = React.createClass({
|
||||
mixins: [ReactFireMixin],
|
||||
|
||||
componentWillMount: function() {
|
||||
this.bindAsObject(firebaseRef.child('items0'), 'bindVar0');
|
||||
this.bindAsArray(firebaseRef.child('items1'), 'bindVar1');
|
||||
|
||||
var items0 = {
|
||||
first: { index: 0 },
|
||||
second: { index: 1 },
|
||||
third: { index: 2 }
|
||||
};
|
||||
|
||||
var items1 = {
|
||||
bar: {
|
||||
foo: 'baz'
|
||||
},
|
||||
baz: true,
|
||||
foo: 100
|
||||
};
|
||||
|
||||
firebaseRef.set({
|
||||
items0: items0,
|
||||
items1: items1
|
||||
}, function() {
|
||||
expect(this.state.bindVar0).to.deep.equal(items0);
|
||||
expect(this.state.bindVar1).to.deep.equal([
|
||||
{ $key: 'bar', foo: 'baz' },
|
||||
{ $key: 'baz', $value: true },
|
||||
{ $key: 'foo', $value: 100 }
|
||||
]);
|
||||
|
||||
done();
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return React.DOM.div(null);
|
||||
}
|
||||
});
|
||||
|
||||
shallowRenderer.render(React.createElement(TestComponent));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -316,7 +802,7 @@ describe('ReactFire', function() {
|
||||
TH.invalidBindVars.forEach(function(invalidBindVar) {
|
||||
expect(function() {
|
||||
_this.unbind(invalidBindVar);
|
||||
}).to.throw(/bindVar/);
|
||||
}).to.throw(/Bind variable/);
|
||||
});
|
||||
},
|
||||
|
||||
@@ -337,7 +823,7 @@ describe('ReactFire', function() {
|
||||
|
||||
expect(function() {
|
||||
_this.unbind('items');
|
||||
}).to.throw(/bindVar/);
|
||||
}).to.throw('this.state.items is not bound to a Firebase reference');
|
||||
},
|
||||
|
||||
render: function() {
|
||||
@@ -407,7 +893,7 @@ describe('ReactFire', function() {
|
||||
mixins: [ReactFireMixin],
|
||||
|
||||
componentWillMount: function() {
|
||||
this.bindAsObject(firebaseRef.limitToLast(2), 'items');
|
||||
this.bindAsObject(firebaseRef, 'items');
|
||||
|
||||
firebaseRef.set({
|
||||
first: { index: 0 },
|
||||
@@ -428,5 +914,38 @@ describe('ReactFire', function() {
|
||||
|
||||
shallowRenderer.render(React.createElement(TestComponent));
|
||||
});
|
||||
|
||||
it('unbinds all bound state when the component unmounts', function(done) {
|
||||
var TestComponent = React.createClass({
|
||||
mixins: [ReactFireMixin],
|
||||
|
||||
componentWillMount: function() {
|
||||
sinon.spy(this, 'unbind');
|
||||
|
||||
this.bindAsArray(firebaseRef, 'items0');
|
||||
this.bindAsObject(firebaseRef, 'items1');
|
||||
|
||||
firebaseRef.set({
|
||||
first: { index: 0 },
|
||||
second: { index: 1 },
|
||||
third: { index: 2 }
|
||||
}, function() {
|
||||
shallowRenderer.unmount();
|
||||
|
||||
expect(this.unbind).to.have.been.calledTwice;
|
||||
expect(this.unbind.args[0][0]).to.equal('items0');
|
||||
expect(this.unbind.args[1][0]).to.equal('items1');
|
||||
|
||||
done();
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return React.DOM.div(null);
|
||||
}
|
||||
});
|
||||
|
||||
shallowRenderer.render(React.createElement(TestComponent));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user