mirror of
https://github.com/hanzoai/firebase-js-sdk.git
synced 2026-09-24 16:59:10 +00:00
4715 lines
132 KiB
TypeScript
4715 lines
132 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2017 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
import { promiseWithTimeout } from '@firebase/util';
|
|
import { expect, use } from 'chai';
|
|
import chaiAsPromised from 'chai-as-promised';
|
|
import * as _ from 'lodash';
|
|
|
|
import {
|
|
INTEGER_32_MAX,
|
|
INTEGER_32_MIN
|
|
} from '../../database/src/core/util/util';
|
|
import {
|
|
EventAccumulator,
|
|
EventAccumulatorFactory
|
|
} from '../../database/test/helpers/EventAccumulator';
|
|
import { DataSnapshot, Query, Reference } from '../src/api/Reference';
|
|
|
|
import { getFreshRepo, getPath, getRandomNode, pause } from './helpers/util';
|
|
|
|
use(chaiAsPromised);
|
|
|
|
type TaskList = Array<[Query, any]>;
|
|
|
|
describe('Query Tests', () => {
|
|
// Little helper class for testing event callbacks w/ contexts.
|
|
const EventReceiver = function () {
|
|
this.gotValue = false;
|
|
this.gotChildAdded = false;
|
|
};
|
|
EventReceiver.prototype.onValue = function () {
|
|
this.gotValue = true;
|
|
};
|
|
EventReceiver.prototype.onChildAdded = function () {
|
|
this.gotChildAdded = true;
|
|
};
|
|
|
|
it('Can create basic queries.', () => {
|
|
const path = getRandomNode() as Reference;
|
|
|
|
path.limitToLast(10);
|
|
path.startAt('199').limitToFirst(10);
|
|
path.startAfter('199').limitToFirst(10);
|
|
path.startAt('199', 'test').limitToFirst(10);
|
|
path.startAfter('199', 'test').limitToFirst(10);
|
|
path.endAt('199').limitToLast(1);
|
|
path.endBefore('199').limitToLast(1);
|
|
path.startAt('50', 'test').endAt('100', 'tree');
|
|
path.startAfter('50', 'test').endAt('100', 'tree');
|
|
path.startAt('4').endAt('10');
|
|
path.startAfter('4').endAt('10');
|
|
path.startAt().limitToFirst(10);
|
|
path.startAfter().limitToFirst(10);
|
|
path.endAt().limitToLast(10);
|
|
path.orderByKey().startAt('foo');
|
|
path.orderByKey().startAfter('foo');
|
|
path.orderByKey().endAt('foo');
|
|
path.orderByKey().equalTo('foo');
|
|
path.orderByChild('child');
|
|
path.orderByChild('child/deep/path');
|
|
path.orderByValue();
|
|
path.orderByPriority();
|
|
});
|
|
|
|
it('Exposes database as read-only property', () => {
|
|
const path = getRandomNode() as Reference;
|
|
const child = path.child('child');
|
|
|
|
const db = path.database;
|
|
const dbChild = child.database;
|
|
|
|
expect(db).to.equal(dbChild);
|
|
expect(path.database).to.equal(db);
|
|
});
|
|
|
|
it('Invalid queries throw', () => {
|
|
const path = getRandomNode() as Reference;
|
|
|
|
/**
|
|
* Because we are testing invalid queries, I am casting
|
|
* to `any` to avoid the typechecking error. This can
|
|
* occur when a user uses the SDK through a pure JS
|
|
* client, rather than typescript
|
|
*/
|
|
expect(() => {
|
|
(path as any).limitToLast();
|
|
}).to.throw();
|
|
expect(() => {
|
|
(path as any).limitToLast('100');
|
|
}).to.throw();
|
|
expect(() => {
|
|
(path as any).limitToLast({ x: 5 });
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.limitToLast(100).limitToLast(100);
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.limitToLast(100).limitToFirst(100);
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.limitToLast(100).limitToLast(100);
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.limitToFirst(100).limitToLast(100);
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.limitToFirst(100).limitToFirst(100);
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.limitToFirst(100).limitToLast(100);
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.limitToLast(100).limitToLast(100);
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.limitToLast(100).limitToFirst(100);
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.limitToLast(100).limitToLast(100);
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.orderByPriority().orderByPriority();
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.orderByPriority().orderByKey();
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.orderByPriority().orderByChild('foo');
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.orderByPriority().startAt(true);
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.orderByPriority().startAfter(true);
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.orderByPriority().endAt(false);
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.orderByPriority().equalTo(true);
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.orderByKey().orderByPriority();
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.orderByKey().orderByKey();
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.orderByKey().orderByChild('foo');
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.orderByChild('foo').orderByPriority();
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.orderByChild('foo').orderByKey();
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.orderByChild('foo').orderByChild('foo');
|
|
}).to.throw();
|
|
expect(() => {
|
|
(path as any).orderByChild('foo').startAt({ a: 1 });
|
|
}).to.throw();
|
|
expect(() => {
|
|
(path as any).orderByChild('foo').startAfter({ a: 1 });
|
|
}).to.throw();
|
|
expect(() => {
|
|
(path as any).orderByChild('foo').endAt({ a: 1 });
|
|
}).to.throw();
|
|
expect(() => {
|
|
(path as any).orderByChild('foo').equalTo({ a: 1 });
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.startAt('foo').startAt('foo');
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.startAfter('foo').startAfter('foo');
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.startAt('foo').startAfter('foo');
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.startAfter('foo').startAt('foo');
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.startAt('foo').equalTo('foo');
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.startAfter('foo').equalTo('foo');
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.endAt('foo').endAt('foo');
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.endAt('foo').equalTo('foo');
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.equalTo('foo').startAt('foo');
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.equalTo('foo').startAfter('foo');
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.equalTo('foo').endAt('foo');
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.equalTo('foo').equalTo('foo');
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.orderByKey().startAt('foo', 'foo');
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.orderByKey().startAfter('foo', 'foo');
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.orderByKey().endAt('foo', 'foo');
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.orderByKey().equalTo('foo', 'foo');
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.orderByKey().startAt(1);
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.orderByKey().startAfter(1);
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.orderByKey().startAt(true);
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.orderByKey().startAfter(true);
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.orderByKey().startAt(null);
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.orderByKey().startAfter(null);
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.orderByKey().endAt(1);
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.orderByKey().endAt(true);
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.orderByKey().endAt(null);
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.orderByKey().equalTo(1);
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.orderByKey().equalTo(true);
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.orderByKey().equalTo(null);
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.startAt('foo', 'foo').orderByKey();
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.startAfter('foo', 'foo').orderByKey();
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.endAt('foo', 'foo').orderByKey();
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.equalTo('foo', 'foo').orderByKey();
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.startAt(1).orderByKey();
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.startAfter(1).orderByKey();
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.startAt(true).orderByKey();
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.startAfter(true).orderByKey();
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.endAt(1).orderByKey();
|
|
}).to.throw();
|
|
expect(() => {
|
|
path.endAt(true).orderByKey();
|
|
}).to.throw();
|
|
});
|
|
|
|
it('can produce a valid ref', () => {
|
|
const path = getRandomNode() as Reference;
|
|
|
|
const query = path.limitToLast(1);
|
|
const ref = query.ref;
|
|
|
|
expect(ref.toString()).to.equal(path.toString());
|
|
});
|
|
|
|
it('Passing invalidKeys to startAt / endAt throws.', () => {
|
|
const f = getRandomNode() as Reference;
|
|
const badKeys = [
|
|
'.test',
|
|
'test.',
|
|
'fo$o',
|
|
'[what',
|
|
'ever]',
|
|
'ha#sh',
|
|
'/thing',
|
|
'th/ing',
|
|
'thing/'
|
|
];
|
|
// Changed from basic array iteration to avoid closure issues accessing mutable state
|
|
_.each(badKeys, badKey => {
|
|
expect(() => {
|
|
f.startAt(null, badKey);
|
|
}).to.throw();
|
|
expect(() => {
|
|
f.endAt(null, badKey);
|
|
}).to.throw();
|
|
});
|
|
});
|
|
|
|
it('Passing invalidKeys to startAfter throws.', () => {
|
|
const f = getRandomNode() as Reference;
|
|
const badKeys = [
|
|
'.test',
|
|
'test.',
|
|
'fo$o',
|
|
'[what',
|
|
'ever]',
|
|
'ha#sh',
|
|
'/thing',
|
|
'th/ing',
|
|
'thing/'
|
|
];
|
|
// Changed from basic array iteration to avoid closure issues accessing mutable state
|
|
_.each(badKeys, badKey => {
|
|
expect(() => {
|
|
f.startAfter(null, badKey);
|
|
}).to.throw();
|
|
});
|
|
});
|
|
|
|
it('Passing invalid paths to orderBy throws', () => {
|
|
const ref = getRandomNode() as Reference;
|
|
expect(() => {
|
|
ref.orderByChild('$child/foo');
|
|
}).to.throw();
|
|
expect(() => {
|
|
ref.orderByChild('$key');
|
|
}).to.throw();
|
|
expect(() => {
|
|
ref.orderByChild('$priority');
|
|
}).to.throw();
|
|
});
|
|
|
|
it('Query.queryIdentifier works.', () => {
|
|
const path = getRandomNode() as Reference;
|
|
const queryId = function (query) {
|
|
return query._delegate._queryIdentifier;
|
|
};
|
|
|
|
expect(queryId(path)).to.equal('default');
|
|
|
|
expect(queryId(path.startAt('pri', 'name'))).to.equal(
|
|
'{"sin":true,"sn":"name","sp":"pri"}'
|
|
);
|
|
expect(queryId(path.startAfter('pri', 'name'))).to.equal(
|
|
'{"sin":false,"sn":"name","sp":"pri"}'
|
|
);
|
|
expect(queryId(path.endAt('pri', 'name'))).to.equal(
|
|
'{"ein":true,"en":"name","ep":"pri"}'
|
|
);
|
|
expect(queryId(path.endBefore('pri', 'name'))).to.equal(
|
|
'{"ein":false,"en":"name","ep":"pri"}'
|
|
);
|
|
|
|
expect(queryId(path.startAt('spri').endAt('epri'))).to.equal(
|
|
'{"ein":true,"ep":"epri","sin":true,"sp":"spri"}'
|
|
);
|
|
expect(queryId(path.startAt('spri').endBefore('epri'))).to.equal(
|
|
'{"ein":false,"en":"[MIN_NAME]","ep":"epri","sin":true,"sp":"spri"}'
|
|
);
|
|
expect(queryId(path.startAfter('spri').endAt('epri'))).to.equal(
|
|
'{"ein":true,"ep":"epri","sin":false,"sn":"[MAX_NAME]","sp":"spri"}'
|
|
);
|
|
expect(queryId(path.startAfter('spri').endBefore('epri'))).to.equal(
|
|
'{"ein":false,"en":"[MIN_NAME]","ep":"epri","sin":false,"sn":"[MAX_NAME]","sp":"spri"}'
|
|
);
|
|
|
|
expect(
|
|
queryId(path.startAt('spri', 'sname').endAt('epri', 'ename'))
|
|
).to.equal(
|
|
'{"ein":true,"en":"ename","ep":"epri","sin":true,"sn":"sname","sp":"spri"}'
|
|
);
|
|
expect(
|
|
queryId(path.startAt('spri', 'sname').endBefore('epri', 'ename'))
|
|
).to.equal(
|
|
'{"ein":false,"en":"ename","ep":"epri","sin":true,"sn":"sname","sp":"spri"}'
|
|
);
|
|
expect(
|
|
queryId(path.startAfter('spri', 'sname').endAt('epri', 'ename'))
|
|
).to.equal(
|
|
'{"ein":true,"en":"ename","ep":"epri","sin":false,"sn":"sname","sp":"spri"}'
|
|
);
|
|
expect(
|
|
queryId(path.startAfter('spri', 'sname').endBefore('epri', 'ename'))
|
|
).to.equal(
|
|
'{"ein":false,"en":"ename","ep":"epri","sin":false,"sn":"sname","sp":"spri"}'
|
|
);
|
|
|
|
expect(queryId(path.startAt('pri').limitToFirst(100))).to.equal(
|
|
'{"l":100,"sin":true,"sp":"pri","vf":"l"}'
|
|
);
|
|
expect(queryId(path.startAfter('pri').limitToFirst(100))).to.equal(
|
|
'{"l":100,"sin":false,"sn":"[MAX_NAME]","sp":"pri","vf":"l"}'
|
|
);
|
|
expect(queryId(path.endAt('pri').limitToLast(100))).to.equal(
|
|
'{"ein":true,"ep":"pri","l":100,"vf":"r"}'
|
|
);
|
|
expect(queryId(path.endBefore('pri').limitToLast(100))).to.equal(
|
|
'{"ein":false,"en":"[MIN_NAME]","ep":"pri","l":100,"vf":"r"}'
|
|
);
|
|
|
|
expect(queryId(path.startAt('bar').orderByChild('foo'))).to.equal(
|
|
'{"i":"foo","sin":true,"sp":"bar"}'
|
|
);
|
|
expect(queryId(path.startAfter('bar').orderByChild('foo'))).to.equal(
|
|
'{"i":"foo","sin":false,"sn":"[MAX_NAME]","sp":"bar"}'
|
|
);
|
|
expect(queryId(path.endAt('bar').orderByChild('foo'))).to.equal(
|
|
'{"ein":true,"ep":"bar","i":"foo"}'
|
|
);
|
|
expect(queryId(path.endBefore('bar').orderByChild('foo'))).to.equal(
|
|
'{"ein":false,"en":"[MIN_NAME]","ep":"bar","i":"foo"}'
|
|
);
|
|
});
|
|
|
|
it('Passing invalid queries to isEqual throws', () => {
|
|
const ref = getRandomNode() as Reference;
|
|
expect(() => {
|
|
(ref as any).isEqual();
|
|
}).to.throw();
|
|
expect(() => {
|
|
(ref as any).isEqual('');
|
|
}).to.throw();
|
|
expect(() => {
|
|
(ref as any).isEqual('foo');
|
|
}).to.throw();
|
|
expect(() => {
|
|
(ref as any).isEqual({});
|
|
}).to.throw();
|
|
expect(() => {
|
|
(ref as any).isEqual([]);
|
|
}).to.throw();
|
|
expect(() => {
|
|
(ref as any).isEqual(0);
|
|
}).to.throw();
|
|
expect(() => {
|
|
(ref as any).isEqual(1);
|
|
}).to.throw();
|
|
expect(() => {
|
|
(ref as any).isEqual(NaN);
|
|
}).to.throw();
|
|
expect(() => {
|
|
ref.isEqual(null);
|
|
}).to.throw();
|
|
expect(() => {
|
|
(ref as any).isEqual({ a: 1 });
|
|
}).to.throw();
|
|
expect(() => {
|
|
(ref as any).isEqual(ref, 'extra');
|
|
}).to.throw();
|
|
});
|
|
|
|
it('Query.isEqual works.', () => {
|
|
const path = getRandomNode() as Reference;
|
|
const rootRef = path.root;
|
|
const childRef = rootRef.child('child');
|
|
|
|
// Equivalent refs
|
|
expect(path.isEqual(path), 'Query.isEqual - 1').to.be.true;
|
|
expect(rootRef.isEqual(rootRef), 'Query.isEqual - 2').to.be.true;
|
|
expect(rootRef.isEqual(childRef.parent), 'Query.isEqual - 3').to.be.true;
|
|
expect(rootRef.child('child').isEqual(childRef), 'Query.isEqual - 4').to.be
|
|
.true;
|
|
|
|
// Refs with different repos
|
|
// NOTE: getFreshRepo() no longer takes a hostname, so this test needs to be reworked.
|
|
// Same in info.test.ts.
|
|
// var rootRefDifferentRepo = TESTS.getFreshRepo(TEST_ALT_NAMESPACE);
|
|
// rootRefDifferentRepo.database.goOffline();
|
|
|
|
// expect(rootRef.isEqual(rootRefDifferentRepo), 'Query.isEqual - 5').to.be.false;
|
|
// expect(childRef.isEqual(rootRefDifferentRepo.child('child')), 'Query.isEqual - 6').to.be.false;
|
|
|
|
// Refs with different paths
|
|
expect(rootRef.isEqual(childRef), 'Query.isEqual - 7').to.be.false;
|
|
expect(childRef.isEqual(rootRef.child('otherChild')), 'Query.isEqual - 8')
|
|
.to.be.false;
|
|
|
|
const childQueryLast25 = childRef.limitToLast(25);
|
|
const childQueryOrderedByKey = childRef.orderByKey();
|
|
const childQueryOrderedByPriority = childRef.orderByPriority();
|
|
const childQueryOrderedByTimestamp = childRef.orderByChild('timestamp');
|
|
const childQueryStartAt1 = childQueryOrderedByTimestamp.startAt(1);
|
|
const childQueryStartAt2 = childQueryOrderedByTimestamp.startAt(2);
|
|
const childQueryStartAfter1 = childQueryOrderedByTimestamp.startAfter(1);
|
|
const childQueryStartAfter2 = childQueryOrderedByTimestamp.startAfter(2);
|
|
const childQueryEndAt2 = childQueryOrderedByTimestamp.endAt(2);
|
|
const childQueryStartAt1EndAt2 = childQueryOrderedByTimestamp
|
|
.startAt(1)
|
|
.endAt(2);
|
|
const childQueryStartAfter1EndAt2 = childQueryOrderedByTimestamp
|
|
.startAfter(1)
|
|
.endAt(2);
|
|
|
|
// Equivalent queries
|
|
expect(childRef.isEqual(childQueryLast25.ref), 'Query.isEqual - 9').to.be
|
|
.true;
|
|
expect(
|
|
childQueryLast25.isEqual(childRef.limitToLast(25)),
|
|
'Query.isEqual - 10'
|
|
).to.be.true;
|
|
expect(
|
|
childQueryStartAt1EndAt2.isEqual(
|
|
childQueryOrderedByTimestamp.startAt(1).endAt(2)
|
|
),
|
|
'Query.isEqual - 11'
|
|
).to.be.true;
|
|
|
|
// Non-equivalent queries
|
|
expect(childQueryLast25.isEqual(childRef), 'Query.isEqual - 12').to.be
|
|
.false;
|
|
expect(
|
|
childQueryLast25.isEqual(childQueryOrderedByKey),
|
|
'Query.isEqual - 13'
|
|
).to.be.false;
|
|
expect(
|
|
childQueryLast25.isEqual(childQueryOrderedByPriority),
|
|
'Query.isEqual - 14'
|
|
).to.be.false;
|
|
expect(
|
|
childQueryLast25.isEqual(childQueryOrderedByTimestamp),
|
|
'Query.isEqual - 15'
|
|
).to.be.false;
|
|
expect(
|
|
childQueryOrderedByKey.isEqual(childQueryOrderedByPriority),
|
|
'Query.isEqual - 16'
|
|
).to.be.false;
|
|
expect(
|
|
childQueryOrderedByKey.isEqual(childQueryOrderedByTimestamp),
|
|
'Query.isEqual - 17'
|
|
).to.be.false;
|
|
expect(childQueryStartAt1.isEqual(childQueryStartAt2), 'Query.isEqual - 18')
|
|
.to.be.false;
|
|
expect(
|
|
childQueryStartAfter1.isEqual(childQueryStartAfter2),
|
|
'Query.isEqual - 19'
|
|
).to.be.false;
|
|
expect(
|
|
childQueryStartAt1.isEqual(childQueryStartAt1EndAt2),
|
|
'Query.isEqual - 20'
|
|
).to.be.false;
|
|
expect(
|
|
childQueryStartAfter1.isEqual(childQueryStartAfter1EndAt2),
|
|
'Query.isEqual - 21'
|
|
).to.be.false;
|
|
expect(childQueryEndAt2.isEqual(childQueryStartAt2), 'Query.isEqual - 22')
|
|
.to.be.false;
|
|
expect(
|
|
childQueryEndAt2.isEqual(childQueryStartAt1EndAt2),
|
|
'Query.isEqual - 23'
|
|
).to.be.false;
|
|
});
|
|
|
|
it('Query.off can be called on the default query.', () => {
|
|
const path = getRandomNode() as Reference;
|
|
let eventFired = false;
|
|
|
|
const callback = function () {
|
|
eventFired = true;
|
|
};
|
|
path.limitToLast(5).on('value', callback);
|
|
|
|
path.set({ a: 5, b: 6 });
|
|
expect(eventFired).to.be.true;
|
|
eventFired = false;
|
|
|
|
path.off('value', callback);
|
|
path.set({ a: 6, b: 5 });
|
|
expect(eventFired).to.be.false;
|
|
});
|
|
|
|
it('Query.off can be called on the specific query.', () => {
|
|
const path = getRandomNode() as Reference;
|
|
let eventFired = false;
|
|
|
|
const callback = function () {
|
|
eventFired = true;
|
|
};
|
|
path.limitToLast(5).on('value', callback);
|
|
|
|
path.set({ a: 5, b: 6 });
|
|
expect(eventFired).to.be.true;
|
|
eventFired = false;
|
|
|
|
path.limitToLast(5).off('value', callback);
|
|
path.set({ a: 6, b: 5 });
|
|
expect(eventFired).to.be.false;
|
|
});
|
|
|
|
it('Query.off can be called without a callback specified.', () => {
|
|
const path = getRandomNode() as Reference;
|
|
let eventFired = false;
|
|
|
|
const callback1 = function () {
|
|
eventFired = true;
|
|
};
|
|
const callback2 = function () {
|
|
eventFired = true;
|
|
};
|
|
path.on('value', callback1);
|
|
path.limitToLast(5).on('value', callback2);
|
|
|
|
path.set({ a: 5, b: 6 });
|
|
expect(eventFired).to.be.true;
|
|
eventFired = false;
|
|
|
|
path.off('value');
|
|
path.set({ a: 6, b: 5 });
|
|
expect(eventFired).to.be.false;
|
|
});
|
|
|
|
it('Query.off can be called without an event type or callback specified.', () => {
|
|
const path = getRandomNode() as Reference;
|
|
let eventFired = false;
|
|
|
|
const callback1 = function () {
|
|
eventFired = true;
|
|
};
|
|
const callback2 = function () {
|
|
eventFired = true;
|
|
};
|
|
path.on('value', callback1);
|
|
path.limitToLast(5).on('value', callback2);
|
|
|
|
path.set({ a: 5, b: 6 });
|
|
expect(eventFired).to.be.true;
|
|
eventFired = false;
|
|
|
|
path.off();
|
|
path.set({ a: 6, b: 5 });
|
|
expect(eventFired).to.be.false;
|
|
});
|
|
|
|
it('Query.off respects provided context (for value events).', () => {
|
|
const ref = getRandomNode() as Reference;
|
|
|
|
const a = new EventReceiver(),
|
|
b = new EventReceiver();
|
|
|
|
ref.on('value', a.onValue, a);
|
|
ref.on('value', b.onValue, b);
|
|
|
|
ref.set('hello!');
|
|
expect(a.gotValue).to.be.true;
|
|
expect(b.gotValue).to.be.true;
|
|
a.gotValue = b.gotValue = false;
|
|
|
|
// unsubscribe b
|
|
ref.off('value', b.onValue, b);
|
|
|
|
// Only a should get this event.
|
|
ref.set(42);
|
|
expect(a.gotValue).to.be.true;
|
|
expect(b.gotValue).to.be.false;
|
|
|
|
ref.off('value', a.onValue, a);
|
|
});
|
|
|
|
it('Query.off respects provided context (for child events).', () => {
|
|
const ref = getRandomNode() as Reference;
|
|
|
|
const a = new EventReceiver(),
|
|
b = new EventReceiver();
|
|
|
|
ref.on('child_added', a.onChildAdded, a);
|
|
ref.on('child_added', b.onChildAdded, b);
|
|
|
|
ref.push('hello!');
|
|
expect(a.gotChildAdded).to.be.true;
|
|
expect(b.gotChildAdded).to.be.true;
|
|
a.gotChildAdded = b.gotChildAdded = false;
|
|
|
|
// unsubscribe b.
|
|
ref.off('child_added', b.onChildAdded, b);
|
|
|
|
// Only a should get this event.
|
|
ref.push(42);
|
|
expect(a.gotChildAdded).to.be.true;
|
|
expect(b.gotChildAdded).to.be.false;
|
|
|
|
ref.off('child_added', a.onChildAdded, a);
|
|
});
|
|
|
|
it('Query.off with no callback/context removes all callbacks, even with contexts (for value events).', () => {
|
|
const ref = getRandomNode() as Reference;
|
|
|
|
const a = new EventReceiver(),
|
|
b = new EventReceiver();
|
|
|
|
ref.on('value', a.onValue, a);
|
|
ref.on('value', b.onValue, b);
|
|
|
|
ref.set('hello!');
|
|
expect(a.gotValue).to.be.true;
|
|
expect(b.gotValue).to.be.true;
|
|
a.gotValue = b.gotValue = false;
|
|
|
|
// unsubscribe value events.
|
|
ref.off('value');
|
|
|
|
// Should get no events.
|
|
ref.set(42);
|
|
expect(a.gotValue).to.be.false;
|
|
expect(b.gotValue).to.be.false;
|
|
});
|
|
|
|
it('Query.off with no callback/context removes all callbacks, even with contexts (for child events).', () => {
|
|
const ref = getRandomNode() as Reference;
|
|
|
|
const a = new EventReceiver(),
|
|
b = new EventReceiver();
|
|
|
|
ref.on('child_added', a.onChildAdded, a);
|
|
ref.on('child_added', b.onChildAdded, b);
|
|
|
|
ref.push('hello!');
|
|
expect(a.gotChildAdded).to.be.true;
|
|
expect(b.gotChildAdded).to.be.true;
|
|
a.gotChildAdded = b.gotChildAdded = false;
|
|
|
|
// unsubscribe child_added.
|
|
ref.off('child_added');
|
|
|
|
// Should get no events.
|
|
ref.push(42);
|
|
expect(a.gotChildAdded).to.be.false;
|
|
expect(b.gotChildAdded).to.be.false;
|
|
});
|
|
|
|
it('Query.off with no event type / callback removes all callbacks (even those with contexts).', () => {
|
|
const ref = getRandomNode() as Reference;
|
|
|
|
const a = new EventReceiver(),
|
|
b = new EventReceiver();
|
|
|
|
ref.on('value', a.onValue, a);
|
|
ref.on('value', b.onValue, b);
|
|
ref.on('child_added', a.onChildAdded, a);
|
|
ref.on('child_added', b.onChildAdded, b);
|
|
|
|
ref.set(null);
|
|
ref.push('hello!');
|
|
expect(a.gotChildAdded).to.be.true;
|
|
expect(a.gotValue).to.be.true;
|
|
expect(b.gotChildAdded).to.be.true;
|
|
expect(b.gotValue).to.be.true;
|
|
a.gotValue = b.gotValue = a.gotChildAdded = b.gotChildAdded = false;
|
|
|
|
// unsubscribe all events.
|
|
ref.off();
|
|
|
|
// We should get no events.
|
|
ref.push(42);
|
|
expect(a.gotChildAdded).to.be.false;
|
|
expect(b.gotChildAdded).to.be.false;
|
|
expect(a.gotValue).to.be.false;
|
|
expect(b.gotValue).to.be.false;
|
|
});
|
|
|
|
it('Set a limit of 5, add a bunch of nodes, ensure only last 5 items are kept.', () => {
|
|
const node = getRandomNode() as Reference;
|
|
let snap = null;
|
|
node.limitToLast(5).on('value', s => {
|
|
snap = s;
|
|
});
|
|
|
|
node.set({});
|
|
for (let i = 0; i < 10; i++) {
|
|
node.push().set(i);
|
|
}
|
|
|
|
let expected = 5;
|
|
snap.forEach(child => {
|
|
expect(child.val()).to.equal(expected);
|
|
expected++;
|
|
});
|
|
|
|
expect(expected).to.equal(10);
|
|
});
|
|
|
|
it('Raises snapshots synchronously', () => {
|
|
const node = getRandomNode() as Reference;
|
|
let newValue;
|
|
node.on('value', v => {
|
|
newValue = v.val();
|
|
});
|
|
node.set('foo');
|
|
expect(newValue).to.equal('foo');
|
|
});
|
|
|
|
it('Set a limit of 5, add a bunch of nodes, ensure only last 5 items are sent from server.', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
await node.set({});
|
|
|
|
const pushPromises = [];
|
|
|
|
for (let i = 0; i < 10; i++) {
|
|
const promise = node.push().set(i);
|
|
pushPromises.push(promise);
|
|
}
|
|
|
|
await Promise.all(pushPromises);
|
|
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
|
|
node.limitToLast(5).on('value', snap => {
|
|
ea.addEvent(snap);
|
|
});
|
|
|
|
const [snap] = await ea.promise;
|
|
|
|
let expected = 5;
|
|
|
|
snap.forEach(child => {
|
|
expect(child.val()).to.equal(expected);
|
|
expected++;
|
|
});
|
|
|
|
expect(expected).to.equal(10);
|
|
});
|
|
|
|
it('Set various limits, ensure resulting data is correct.', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
await node.set({ a: 1, b: 2, c: 3 });
|
|
|
|
const tasks: TaskList = [
|
|
[node.limitToLast(1), { c: 3 }],
|
|
[node.endAt().limitToLast(1), { c: 3 }],
|
|
[node.limitToLast(2), { b: 2, c: 3 }],
|
|
[node.limitToLast(3), { a: 1, b: 2, c: 3 }],
|
|
[node.limitToLast(4), { a: 1, b: 2, c: 3 }]
|
|
];
|
|
|
|
return Promise.all(
|
|
tasks.map(async task => {
|
|
const [query, val] = task;
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
query.on('value', snap => {
|
|
ea.addEvent(snap.val());
|
|
});
|
|
const [newVal] = await ea.promise;
|
|
expect(newVal).to.deep.equal(val);
|
|
})
|
|
);
|
|
});
|
|
|
|
it('Set various limits with a startAt name, ensure resulting data is correct.', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
await node.set({ a: 1, b: 2, c: 3 });
|
|
|
|
const tasks: TaskList = [
|
|
[node.startAt().limitToFirst(1), { a: 1 }],
|
|
[node.startAt(null, 'c').limitToFirst(1), { c: 3 }],
|
|
[node.startAt(null, 'b').limitToFirst(1), { b: 2 }],
|
|
[node.startAt(null, 'b').limitToFirst(2), { b: 2, c: 3 }],
|
|
[node.startAt(null, 'b').limitToFirst(3), { b: 2, c: 3 }],
|
|
[node.startAt(null, 'b').limitToLast(1), { c: 3 }],
|
|
[node.startAt(null, 'b').limitToLast(1), { c: 3 }],
|
|
[node.startAt(null, 'b').limitToLast(2), { b: 2, c: 3 }],
|
|
[node.startAt(null, 'b').limitToLast(3), { b: 2, c: 3 }],
|
|
[node.limitToFirst(1).startAt(null, 'c'), { c: 3 }],
|
|
[node.limitToFirst(1).startAt(null, 'b'), { b: 2 }],
|
|
[node.limitToFirst(2).startAt(null, 'b'), { b: 2, c: 3 }],
|
|
[node.limitToFirst(3).startAt(null, 'b'), { b: 2, c: 3 }],
|
|
[node.limitToLast(1).startAt(null, 'b'), { c: 3 }],
|
|
[node.limitToLast(1).startAt(null, 'b'), { c: 3 }],
|
|
[node.limitToLast(2).startAt(null, 'b'), { b: 2, c: 3 }],
|
|
[node.limitToLast(3).startAt(null, 'b'), { b: 2, c: 3 }]
|
|
];
|
|
|
|
return Promise.all(
|
|
tasks.map(async task => {
|
|
const [query, val] = task;
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
query.on('value', snap => {
|
|
ea.addEvent(snap.val());
|
|
});
|
|
const [newVal] = await ea.promise;
|
|
expect(newVal).to.deep.equal(val);
|
|
})
|
|
);
|
|
});
|
|
|
|
it('Set various limits with a startAfter name, ensure resulting data is correct.', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
await node.set({ a: 1, b: 2, c: 3, d: 4 });
|
|
|
|
const tasks: TaskList = [
|
|
// Using the priority index here, so startAfter() skips everything.
|
|
[node.startAfter().limitToFirst(1), null],
|
|
[node.startAfter(null, 'c').limitToFirst(1), { d: 4 }],
|
|
[node.startAfter(null, 'b').limitToFirst(1), { c: 3 }],
|
|
[node.startAfter(null, 'b').limitToFirst(2), { c: 3, d: 4 }],
|
|
[node.startAfter(null, 'b').limitToFirst(3), { c: 3, d: 4 }],
|
|
[node.startAfter(null, 'b').limitToLast(1), { d: 4 }],
|
|
[node.startAfter(null, 'b').limitToLast(1), { d: 4 }],
|
|
[node.startAfter(null, 'b').limitToLast(2), { c: 3, d: 4 }],
|
|
[node.startAfter(null, 'b').limitToLast(3), { c: 3, d: 4 }],
|
|
[node.limitToFirst(1).startAfter(null, 'c'), { d: 4 }],
|
|
[node.limitToFirst(1).startAfter(null, 'b'), { c: 3 }],
|
|
[node.limitToFirst(2).startAfter(null, 'b'), { c: 3, d: 4 }],
|
|
[node.limitToFirst(3).startAfter(null, 'b'), { c: 3, d: 4 }],
|
|
[node.limitToLast(1).startAfter(null, 'b'), { d: 4 }],
|
|
[node.limitToLast(1).startAfter(null, 'b'), { d: 4 }],
|
|
[node.limitToLast(2).startAfter(null, 'b'), { c: 3, d: 4 }],
|
|
[node.limitToLast(3).startAfter(null, 'b'), { c: 3, d: 4 }]
|
|
];
|
|
|
|
return Promise.all(
|
|
tasks.map(async task => {
|
|
const [query, val] = task;
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
query.on('value', snap => {
|
|
ea.addEvent(snap.val());
|
|
});
|
|
const [newVal] = await ea.promise;
|
|
expect(newVal).to.deep.equal(val);
|
|
})
|
|
);
|
|
});
|
|
|
|
it('Set various limits with a endAt name, ensure resulting data is correct.', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
await node.set({ a: 1, b: 2, c: 3 });
|
|
|
|
const tasks: TaskList = [
|
|
[node.endAt().limitToFirst(1), { a: 1 }],
|
|
[node.endAt(null, 'c').limitToFirst(1), { a: 1 }],
|
|
[node.endAt(null, 'b').limitToFirst(1), { a: 1 }],
|
|
[node.endAt(null, 'b').limitToFirst(2), { a: 1, b: 2 }],
|
|
[node.endAt(null, 'b').limitToFirst(3), { a: 1, b: 2 }],
|
|
[node.endAt(null, 'c').limitToLast(1), { c: 3 }],
|
|
[node.endAt(null, 'b').limitToLast(1), { b: 2 }],
|
|
[node.endAt(null, 'b').limitToLast(2), { a: 1, b: 2 }],
|
|
[node.endAt(null, 'b').limitToLast(3), { a: 1, b: 2 }],
|
|
[node.limitToFirst(1).endAt(null, 'c'), { a: 1 }],
|
|
[node.limitToFirst(1).endAt(null, 'b'), { a: 1 }],
|
|
[node.limitToFirst(2).endAt(null, 'b'), { a: 1, b: 2 }],
|
|
[node.limitToFirst(3).endAt(null, 'b'), { a: 1, b: 2 }],
|
|
[node.limitToLast(1).endAt(null, 'c'), { c: 3 }],
|
|
[node.limitToLast(1).endAt(null, 'b'), { b: 2 }],
|
|
[node.limitToLast(2).endAt(null, 'b'), { a: 1, b: 2 }],
|
|
[node.limitToLast(3).endAt(null, 'b'), { a: 1, b: 2 }]
|
|
];
|
|
|
|
return Promise.all(
|
|
tasks.map(async task => {
|
|
const [query, val] = task;
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
query.on('value', snap => {
|
|
ea.addEvent(snap.val());
|
|
});
|
|
const [newVal] = await ea.promise;
|
|
expect(newVal).to.deep.equal(val);
|
|
})
|
|
);
|
|
});
|
|
|
|
it('Set various limits with a startAt name, ensure resulting data is correct from the server.', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
await node.set({ a: 1, b: 2, c: 3 });
|
|
|
|
const tasks: TaskList = [
|
|
[node.startAt().limitToFirst(1), { a: 1 }],
|
|
[node.startAt(null, 'c').limitToFirst(1), { c: 3 }],
|
|
[node.startAt(null, 'b').limitToFirst(1), { b: 2 }],
|
|
// NOTE: technically there is a race condition here. The limitToFirst(1) query will return a single value, which will be
|
|
// raised for the limitToFirst(2) callback as well, if it exists already. However, once the server gets the limitToFirst(2)
|
|
// query, it will send more data and the correct state will be returned.
|
|
[node.startAt(null, 'b').limitToFirst(2), { b: 2, c: 3 }],
|
|
[node.startAt(null, 'b').limitToFirst(3), { b: 2, c: 3 }]
|
|
];
|
|
|
|
return Promise.all(
|
|
tasks.map(async task => {
|
|
const [query, val] = task;
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
query.on('value', snap => {
|
|
ea.addEvent(snap.val());
|
|
});
|
|
const [newVal] = await ea.promise;
|
|
expect(newVal).to.deep.equal(val);
|
|
})
|
|
);
|
|
});
|
|
|
|
it('Set limit, ensure child_removed and child_added events are fired when limit is hit.', () => {
|
|
const node = getRandomNode() as Reference;
|
|
let added = '',
|
|
removed = '';
|
|
node.limitToLast(2).on('child_added', snap => {
|
|
added += snap.key + ' ';
|
|
});
|
|
node.limitToLast(2).on('child_removed', snap => {
|
|
removed += snap.key + ' ';
|
|
});
|
|
node.set({ a: 1, b: 2, c: 3 });
|
|
|
|
expect(added).to.equal('b c ');
|
|
expect(removed).to.equal('');
|
|
|
|
added = '';
|
|
node.child('d').set(4);
|
|
expect(added).to.equal('d ');
|
|
expect(removed).to.equal('b ');
|
|
});
|
|
|
|
it('Set limit, ensure child_removed and child_added events are fired when limit is hit, using server data', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
await node.set({ a: 1, b: 2, c: 3 });
|
|
|
|
const ea = EventAccumulatorFactory.waitsForCount(2);
|
|
|
|
let added = '',
|
|
removed = '';
|
|
node.limitToLast(2).on('child_added', snap => {
|
|
added += snap.key + ' ';
|
|
ea.addEvent();
|
|
});
|
|
node.limitToLast(2).on('child_removed', snap => {
|
|
removed += snap.key + ' ';
|
|
});
|
|
|
|
await ea.promise;
|
|
|
|
expect(added).to.equal('b c ');
|
|
expect(removed).to.equal('');
|
|
|
|
added = '';
|
|
await node.child('d').set(4);
|
|
|
|
expect(added).to.equal('d ');
|
|
expect(removed).to.equal('b ');
|
|
});
|
|
|
|
it('Set start and limit, ensure child_removed and child_added events are fired when limit is hit.', () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
let added = '',
|
|
removed = '';
|
|
node
|
|
.startAt(null, 'a')
|
|
.limitToFirst(2)
|
|
.on('child_added', snap => {
|
|
added += snap.key + ' ';
|
|
});
|
|
node
|
|
.startAt(null, 'a')
|
|
.limitToFirst(2)
|
|
.on('child_removed', snap => {
|
|
removed += snap.key + ' ';
|
|
});
|
|
node.set({ a: 1, b: 2, c: 3 });
|
|
expect(added).to.equal('a b ');
|
|
expect(removed).to.equal('');
|
|
|
|
added = '';
|
|
node.child('aa').set(4);
|
|
expect(added).to.equal('aa ');
|
|
expect(removed).to.equal('b ');
|
|
});
|
|
|
|
it('Set startAfter and limit, ensure child_removed and child_added events are fired when limit is hit.', () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
let added = '',
|
|
removed = '';
|
|
node
|
|
.startAfter(null, 'a')
|
|
.limitToFirst(2)
|
|
.on('child_added', snap => {
|
|
added += snap.key + ' ';
|
|
});
|
|
node
|
|
.startAfter(null, 'a')
|
|
.limitToFirst(2)
|
|
.on('child_removed', snap => {
|
|
removed += snap.key + ' ';
|
|
});
|
|
node.set({ a: 1, b: 2, c: 3 });
|
|
expect(added).to.equal('b c ');
|
|
expect(removed).to.equal('');
|
|
|
|
added = '';
|
|
node.child('aa').set(4);
|
|
expect(added).to.equal('aa ');
|
|
expect(removed).to.equal('c ');
|
|
});
|
|
|
|
it('Set start and limit, ensure child_removed and child_added events are fired when limit is hit, using server data', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
await node.set({ a: 1, b: 2, c: 3 });
|
|
const ea = EventAccumulatorFactory.waitsForCount(2);
|
|
|
|
let added = '',
|
|
removed = '';
|
|
node
|
|
.startAt(null, 'a')
|
|
.limitToFirst(2)
|
|
.on('child_added', snap => {
|
|
added += snap.key + ' ';
|
|
ea.addEvent();
|
|
});
|
|
node
|
|
.startAt(null, 'a')
|
|
.limitToFirst(2)
|
|
.on('child_removed', snap => {
|
|
removed += snap.key + ' ';
|
|
});
|
|
|
|
await ea.promise;
|
|
|
|
expect(added).to.equal('a b ');
|
|
expect(removed).to.equal('');
|
|
|
|
added = '';
|
|
await node.child('aa').set(4);
|
|
|
|
expect(added).to.equal('aa ');
|
|
expect(removed).to.equal('b ');
|
|
});
|
|
|
|
it('Set start and limit, ensure child_removed and child_added events are fired when limit is hit, using server data', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
await node.set({ a: 1, b: 2, c: 3 });
|
|
const ea = EventAccumulatorFactory.waitsForCount(2);
|
|
|
|
let added = '',
|
|
removed = '';
|
|
node
|
|
.startAfter(null, 'a')
|
|
.limitToFirst(2)
|
|
.on('child_added', snap => {
|
|
added += snap.key + ' ';
|
|
ea.addEvent();
|
|
});
|
|
node
|
|
.startAfter(null, 'a')
|
|
.limitToFirst(2)
|
|
.on('child_removed', snap => {
|
|
removed += snap.key + ' ';
|
|
});
|
|
|
|
await ea.promise;
|
|
|
|
expect(added).to.equal('b c ');
|
|
expect(removed).to.equal('');
|
|
|
|
added = '';
|
|
await node.child('bb').set(4);
|
|
|
|
expect(added).to.equal('bb ');
|
|
expect(removed).to.equal('c ');
|
|
});
|
|
|
|
it("Set start and limit, ensure child_added events are fired when limit isn't hit yet.", () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
let added = '',
|
|
removed = '';
|
|
node
|
|
.startAt(null, 'a')
|
|
.limitToFirst(2)
|
|
.on('child_added', snap => {
|
|
added += snap.key + ' ';
|
|
});
|
|
node
|
|
.startAt(null, 'a')
|
|
.limitToFirst(2)
|
|
.on('child_removed', snap => {
|
|
removed += snap.key + ' ';
|
|
});
|
|
node.set({ c: 3 });
|
|
expect(added).to.equal('c ');
|
|
expect(removed).to.equal('');
|
|
|
|
added = '';
|
|
node.child('b').set(4);
|
|
expect(added).to.equal('b ');
|
|
expect(removed).to.equal('');
|
|
});
|
|
|
|
it("Set startAfter and limit, ensure child_added events are fired when limit isn't hit yet.", () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
let added = '',
|
|
removed = '';
|
|
node
|
|
.startAfter(null, 'a')
|
|
.limitToFirst(2)
|
|
.on('child_added', snap => {
|
|
added += snap.key + ' ';
|
|
});
|
|
node
|
|
.startAfter(null, 'a')
|
|
.limitToFirst(2)
|
|
.on('child_removed', snap => {
|
|
removed += snap.key + ' ';
|
|
});
|
|
node.set({ c: 3 });
|
|
expect(added).to.equal('c ');
|
|
expect(removed).to.equal('');
|
|
|
|
added = '';
|
|
node.child('b').set(4);
|
|
expect(added).to.equal('b ');
|
|
expect(removed).to.equal('');
|
|
});
|
|
|
|
it("Set start and limit, ensure child_added events are fired when limit isn't hit yet, using server data", async () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
await node.set({ c: 3 });
|
|
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
|
|
let added = '';
|
|
let removed = '';
|
|
node
|
|
.startAt(null, 'a')
|
|
.limitToFirst(2)
|
|
.on('child_added', snap => {
|
|
added += snap.key + ' ';
|
|
ea.addEvent();
|
|
});
|
|
node
|
|
.startAt(null, 'a')
|
|
.limitToFirst(2)
|
|
.on('child_removed', snap => {
|
|
removed += snap.key + ' ';
|
|
});
|
|
|
|
await ea.promise;
|
|
|
|
expect(added).to.equal('c ');
|
|
expect(removed).to.equal('');
|
|
|
|
added = '';
|
|
await node.child('b').set(4);
|
|
|
|
expect(added).to.equal('b ');
|
|
expect(removed).to.equal('');
|
|
});
|
|
|
|
it("Set startAfter and limit, ensure child_added events are fired when limit isn't hit yet, using server data", async () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
await node.set({ c: 3 });
|
|
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
|
|
let added = '';
|
|
let removed = '';
|
|
node
|
|
.startAfter(null, 'a')
|
|
.limitToFirst(2)
|
|
.on('child_added', snap => {
|
|
added += snap.key + ' ';
|
|
ea.addEvent();
|
|
});
|
|
node
|
|
.startAfter(null, 'a')
|
|
.limitToFirst(2)
|
|
.on('child_removed', snap => {
|
|
removed += snap.key + ' ';
|
|
});
|
|
|
|
await ea.promise;
|
|
|
|
expect(added).to.equal('c ');
|
|
expect(removed).to.equal('');
|
|
|
|
added = '';
|
|
await node.child('b').set(4);
|
|
|
|
expect(added).to.equal('b ');
|
|
expect(removed).to.equal('');
|
|
});
|
|
|
|
it('Set a limit, ensure child_removed and child_added events are fired when limit is satisfied and you remove an item.', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
|
|
let added = '',
|
|
removed = '';
|
|
node.limitToLast(2).on('child_added', snap => {
|
|
added += snap.key + ' ';
|
|
ea.addEvent();
|
|
});
|
|
node.limitToLast(2).on('child_removed', snap => {
|
|
removed += snap.key + ' ';
|
|
});
|
|
node.set({ a: 1, b: 2, c: 3 });
|
|
expect(added).to.equal('b c ');
|
|
expect(removed).to.equal('');
|
|
|
|
added = '';
|
|
node.child('b').remove();
|
|
expect(removed).to.equal('b ');
|
|
|
|
await ea.promise;
|
|
});
|
|
|
|
it('Set a limit, ensure child_removed and child_added events are fired when limit is satisfied and you remove an item. Using server data', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
await node.set({ a: 1, b: 2, c: 3 });
|
|
|
|
let ea = EventAccumulatorFactory.waitsForCount(2);
|
|
let added = '',
|
|
removed = '';
|
|
node.limitToLast(2).on('child_added', snap => {
|
|
added += snap.key + ' ';
|
|
ea.addEvent();
|
|
});
|
|
node.limitToLast(2).on('child_removed', snap => {
|
|
removed += snap.key + ' ';
|
|
});
|
|
|
|
await ea.promise;
|
|
|
|
expect(added).to.equal('b c ');
|
|
expect(removed).to.equal('');
|
|
|
|
// We are going to wait for one more event before closing
|
|
ea = EventAccumulatorFactory.waitsForCount(1);
|
|
added = '';
|
|
await node.child('b').remove();
|
|
|
|
expect(removed).to.equal('b ');
|
|
|
|
await ea.promise;
|
|
expect(added).to.equal('a ');
|
|
});
|
|
|
|
it('Set a limit, ensure child_removed events are fired when limit is satisfied, you remove an item, and there are no more.', () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
let added = '',
|
|
removed = '';
|
|
node.limitToLast(2).on('child_added', snap => {
|
|
added += snap.key + ' ';
|
|
});
|
|
node.limitToLast(2).on('child_removed', snap => {
|
|
removed += snap.key + ' ';
|
|
});
|
|
node.set({ b: 2, c: 3 });
|
|
expect(added).to.equal('b c ');
|
|
expect(removed).to.equal('');
|
|
|
|
added = '';
|
|
node.child('b').remove();
|
|
expect(added).to.equal('');
|
|
expect(removed).to.equal('b ');
|
|
node.child('c').remove();
|
|
expect(removed).to.equal('b c ');
|
|
});
|
|
|
|
it('Set a limit, ensure child_removed events are fired when limit is satisfied, you remove an item, and there are no more. Using server data', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
const ea = EventAccumulatorFactory.waitsForCount(2);
|
|
let added = '';
|
|
let removed = '';
|
|
await node.set({ b: 2, c: 3 });
|
|
|
|
node.limitToLast(2).on('child_added', snap => {
|
|
added += snap.key + ' ';
|
|
ea.addEvent();
|
|
});
|
|
node.limitToLast(2).on('child_removed', snap => {
|
|
removed += snap.key + ' ';
|
|
});
|
|
|
|
await ea.promise;
|
|
|
|
expect(added).to.equal('b c ');
|
|
expect(removed).to.equal('');
|
|
|
|
added = '';
|
|
|
|
await node.child('b').remove();
|
|
|
|
expect(added).to.equal('');
|
|
expect(removed).to.equal('b ');
|
|
});
|
|
|
|
it('Ensure startAfter on key index works', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
const childOne = node.push();
|
|
const childTwo = node.push();
|
|
await childOne.set(1);
|
|
await childTwo.set(2);
|
|
const snap = await node.orderByKey().startAfter(childOne.key).get();
|
|
expect(Object.keys(snap.val())).to.deep.equal([childTwo.key]);
|
|
expect(Object.values(snap.val())).to.deep.equal([snap.val()[childTwo.key]]);
|
|
});
|
|
|
|
it('Ensure endBefore on key index works', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
const childOne = node.push();
|
|
const childTwo = node.push();
|
|
await childOne.set(1);
|
|
await childTwo.set(2);
|
|
const snap = await node.orderByKey().endBefore(childTwo.key).get();
|
|
expect(Object.keys(snap.val())).to.deep.equal([childOne.key]);
|
|
expect(Object.values(snap.val())).to.deep.equal([snap.val()[childOne.key]]);
|
|
});
|
|
|
|
it('Ensure startAfter on key index works with overlapping listener', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
const childOne = node.push();
|
|
const childTwo = node.push();
|
|
// Create a server synced and a latency-compensated write
|
|
await childOne.set(1);
|
|
childTwo.set(2);
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
node.on('value', snap => {
|
|
ea.addEvent(snap.val());
|
|
});
|
|
await ea.promise;
|
|
const snap = await node.orderByKey().startAfter(childOne.key).get();
|
|
expect(Object.keys(snap.val())).to.deep.equal([childTwo.key]);
|
|
expect(Object.values(snap.val())).to.deep.equal([snap.val()[childTwo.key]]);
|
|
});
|
|
|
|
it('Ensure endBefore on key index works with overlapping listener', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
const childOne = node.push();
|
|
const childTwo = node.push();
|
|
// Create a server synced and a latency-compensated write
|
|
await childOne.set(1);
|
|
childTwo.set(2);
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
node.on('value', snap => {
|
|
ea.addEvent(snap.val());
|
|
});
|
|
await ea.promise;
|
|
const snap = await node.orderByKey().endBefore(childTwo.key).get();
|
|
expect(Object.keys(snap.val())).to.deep.equal([childOne.key]);
|
|
expect(Object.values(snap.val())).to.deep.equal([snap.val()[childOne.key]]);
|
|
});
|
|
|
|
it('Ensure startAt / endAt with priority works.', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
const tasks: TaskList = [
|
|
[node.startAt('w').endAt('y'), { b: 2, c: 3, d: 4 }],
|
|
[node.startAt('w').endAt('w'), { d: 4 }],
|
|
[node.startAt('a').endAt('c'), null]
|
|
];
|
|
|
|
await node.set({
|
|
a: { '.value': 1, '.priority': 'z' },
|
|
b: { '.value': 2, '.priority': 'y' },
|
|
c: { '.value': 3, '.priority': 'x' },
|
|
d: { '.value': 4, '.priority': 'w' }
|
|
});
|
|
|
|
return Promise.all(
|
|
tasks.map(async task => {
|
|
const [query, val] = task;
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
query.on('value', snap => {
|
|
ea.addEvent(snap.val());
|
|
});
|
|
const [newVal] = await ea.promise;
|
|
expect(newVal).to.deep.equal(val);
|
|
})
|
|
);
|
|
});
|
|
|
|
it('Ensure startAfter / endAt with priority works.', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
const tasks: TaskList = [
|
|
[node.startAfter('w').endAt('y'), { b: 2, c: 3 }],
|
|
[node.startAfter('w').endAt('x'), { c: 3 }],
|
|
[node.startAfter('a').endAt('c'), null]
|
|
];
|
|
|
|
await node.set({
|
|
a: { '.value': 1, '.priority': 'z' },
|
|
b: { '.value': 2, '.priority': 'y' },
|
|
c: { '.value': 3, '.priority': 'x' },
|
|
d: { '.value': 4, '.priority': 'w' }
|
|
});
|
|
|
|
return Promise.all(
|
|
tasks.map(async task => {
|
|
const [query, val] = task;
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
query.on('value', snap => {
|
|
ea.addEvent(snap.val());
|
|
});
|
|
const [newVal] = await ea.promise;
|
|
expect(newVal).to.deep.equal(val);
|
|
})
|
|
);
|
|
});
|
|
|
|
it('Ensure startAt / endBefore with priority works.', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
const tasks: TaskList = [
|
|
[node.startAt('w').endBefore('y'), { c: 3, d: 4 }],
|
|
[node.startAt('w').endBefore('x'), { d: 4 }],
|
|
[node.startAt('a').endBefore('c'), null]
|
|
];
|
|
|
|
await node.set({
|
|
a: { '.value': 1, '.priority': 'z' },
|
|
b: { '.value': 2, '.priority': 'y' },
|
|
c: { '.value': 3, '.priority': 'x' },
|
|
d: { '.value': 4, '.priority': 'w' }
|
|
});
|
|
|
|
return Promise.all(
|
|
tasks.map(async task => {
|
|
const [query, val] = task;
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
query.on('value', snap => {
|
|
ea.addEvent(snap.val());
|
|
});
|
|
const [newVal] = await ea.promise;
|
|
expect(newVal).to.deep.equal(val);
|
|
})
|
|
);
|
|
});
|
|
|
|
it('Ensure startAfter / endBefore with priority works.', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
const tasks: TaskList = [
|
|
[node.startAfter('w').endBefore('z'), { b: 2, c: 3 }],
|
|
[node.startAfter('w').endBefore('y'), { c: 3 }],
|
|
[node.startAfter('w').endBefore('w'), null]
|
|
];
|
|
|
|
await node.set({
|
|
a: { '.value': 1, '.priority': 'z' },
|
|
b: { '.value': 2, '.priority': 'y' },
|
|
c: { '.value': 3, '.priority': 'x' },
|
|
d: { '.value': 4, '.priority': 'w' }
|
|
});
|
|
|
|
return Promise.all(
|
|
tasks.map(async task => {
|
|
const [query, val] = task;
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
query.on('value', snap => {
|
|
ea.addEvent(snap.val());
|
|
});
|
|
const [newVal] = await ea.promise;
|
|
expect(newVal).to.deep.equal(val);
|
|
})
|
|
);
|
|
});
|
|
|
|
it('Ensure startAt / endAt with priority work with server data.', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
await node.set({
|
|
a: { '.value': 1, '.priority': 'z' },
|
|
b: { '.value': 2, '.priority': 'y' },
|
|
c: { '.value': 3, '.priority': 'x' },
|
|
d: { '.value': 4, '.priority': 'w' }
|
|
});
|
|
|
|
const tasks: TaskList = [
|
|
[node.startAt('w').endAt('y'), { b: 2, c: 3, d: 4 }],
|
|
[node.startAt('w').endAt('w'), { d: 4 }],
|
|
[node.startAt('a').endAt('c'), null]
|
|
];
|
|
|
|
return Promise.all(
|
|
tasks.map(async task => {
|
|
const [query, val] = task;
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
query.on('value', snap => {
|
|
ea.addEvent(snap.val());
|
|
});
|
|
const [newVal] = await ea.promise;
|
|
expect(newVal).to.deep.equal(val);
|
|
})
|
|
);
|
|
});
|
|
|
|
it('Ensure startAfter / endAt with priority work with server data.', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
await node.set({
|
|
a: { '.value': 1, '.priority': 'z' },
|
|
b: { '.value': 2, '.priority': 'y' },
|
|
c: { '.value': 3, '.priority': 'x' },
|
|
d: { '.value': 4, '.priority': 'w' }
|
|
});
|
|
|
|
const tasks: TaskList = [
|
|
[node.startAfter('w').endAt('y'), { b: 2, c: 3 }],
|
|
[node.startAfter('w').endAt('x'), { c: 3 }],
|
|
[node.startAfter('a').endAt('c'), null]
|
|
];
|
|
|
|
return Promise.all(
|
|
tasks.map(async task => {
|
|
const [query, val] = task;
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
query.on('value', snap => {
|
|
ea.addEvent(snap.val());
|
|
});
|
|
const [newVal] = await ea.promise;
|
|
expect(newVal).to.deep.equal(val);
|
|
})
|
|
);
|
|
});
|
|
|
|
it('Ensure startAt / endBefore with priority work with server data.', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
await node.set({
|
|
a: { '.value': 1, '.priority': 'z' },
|
|
b: { '.value': 2, '.priority': 'y' },
|
|
c: { '.value': 3, '.priority': 'x' },
|
|
d: { '.value': 4, '.priority': 'w' }
|
|
});
|
|
|
|
const tasks: TaskList = [
|
|
[node.startAt('w').endBefore('y'), { c: 3, d: 4 }],
|
|
[node.startAt('w').endBefore('x'), { d: 4 }],
|
|
[node.startAt('a').endBefore('c'), null]
|
|
];
|
|
|
|
return Promise.all(
|
|
tasks.map(async task => {
|
|
const [query, val] = task;
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
query.on('value', snap => {
|
|
ea.addEvent(snap.val());
|
|
});
|
|
const [newVal] = await ea.promise;
|
|
expect(newVal).to.deep.equal(val);
|
|
})
|
|
);
|
|
});
|
|
|
|
it('Ensure startAfter / endBefore with priority work with server data.', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
await node.set({
|
|
a: { '.value': 1, '.priority': 'z' },
|
|
b: { '.value': 2, '.priority': 'y' },
|
|
c: { '.value': 3, '.priority': 'x' },
|
|
d: { '.value': 4, '.priority': 'w' }
|
|
});
|
|
|
|
const tasks: TaskList = [
|
|
[node.startAfter('w').endBefore('z'), { b: 2, c: 3 }],
|
|
[node.startAfter('w').endBefore('y'), { c: 3 }],
|
|
[node.startAfter('w').endBefore('w'), null]
|
|
];
|
|
|
|
return Promise.all(
|
|
tasks.map(async task => {
|
|
const [query, val] = task;
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
query.on('value', snap => {
|
|
ea.addEvent(snap.val());
|
|
});
|
|
const [newVal] = await ea.promise;
|
|
expect(newVal).to.deep.equal(val);
|
|
})
|
|
);
|
|
});
|
|
|
|
it('Ensure startAt / endAt with priority and name works.', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
await node.set({
|
|
a: { '.value': 1, '.priority': 1 },
|
|
b: { '.value': 2, '.priority': 1 },
|
|
c: { '.value': 3, '.priority': 2 },
|
|
d: { '.value': 4, '.priority': 2 }
|
|
});
|
|
|
|
const tasks: TaskList = [
|
|
[node.startAt(1, 'a').endAt(2, 'd'), { a: 1, b: 2, c: 3, d: 4 }],
|
|
[node.startAt(1, 'b').endAt(2, 'c'), { b: 2, c: 3 }],
|
|
[node.startAt(1, 'c').endAt(2), { c: 3, d: 4 }]
|
|
];
|
|
|
|
return Promise.all(
|
|
tasks.map(async task => {
|
|
const [query, val] = task;
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
query.on('value', snap => {
|
|
ea.addEvent(snap.val());
|
|
});
|
|
const [newVal] = await ea.promise;
|
|
expect(newVal).to.deep.equal(val);
|
|
})
|
|
);
|
|
});
|
|
|
|
it('Ensure startAfter / endAt with priority and name works.', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
await node.set({
|
|
a: { '.value': 1, '.priority': 1 },
|
|
b: { '.value': 2, '.priority': 1 },
|
|
c: { '.value': 3, '.priority': 2 },
|
|
d: { '.value': 4, '.priority': 2 }
|
|
});
|
|
|
|
const tasks: TaskList = [
|
|
[node.startAfter(1, 'a').endAt(2, 'd'), { b: 2, c: 3, d: 4 }],
|
|
[node.startAfter(1, 'b').endAt(2, 'c'), { c: 3 }],
|
|
[node.startAfter(1, 'c').endAt(2), { c: 3, d: 4 }]
|
|
];
|
|
|
|
return Promise.all(
|
|
tasks.map(async task => {
|
|
const [query, val] = task;
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
query.on('value', snap => {
|
|
ea.addEvent(snap.val());
|
|
});
|
|
const [newVal] = await ea.promise;
|
|
expect(newVal).to.deep.equal(val);
|
|
})
|
|
);
|
|
});
|
|
|
|
it('Ensure startAt / endBefore with priority and name works.', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
await node.set({
|
|
a: { '.value': 1, '.priority': 1 },
|
|
b: { '.value': 2, '.priority': 1 },
|
|
c: { '.value': 3, '.priority': 2 },
|
|
d: { '.value': 4, '.priority': 2 }
|
|
});
|
|
|
|
const tasks: TaskList = [
|
|
[node.startAt(1, 'a').endBefore(2, 'd'), { a: 1, b: 2, c: 3 }],
|
|
[node.startAt(1, 'b').endBefore(2, 'c'), { b: 2 }],
|
|
[node.startAt(1, 'c').endBefore(2), null]
|
|
];
|
|
|
|
return Promise.all(
|
|
tasks.map(async task => {
|
|
const [query, val] = task;
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
query.on('value', snap => {
|
|
ea.addEvent(snap.val());
|
|
});
|
|
const [newVal] = await ea.promise;
|
|
expect(newVal).to.deep.equal(val);
|
|
})
|
|
);
|
|
});
|
|
|
|
it('Ensure startAfter / endBefore with priority and name works.', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
await node.set({
|
|
a: { '.value': 1, '.priority': 1 },
|
|
b: { '.value': 2, '.priority': 1 },
|
|
c: { '.value': 3, '.priority': 2 },
|
|
d: { '.value': 4, '.priority': 2 }
|
|
});
|
|
|
|
const tasks: TaskList = [
|
|
[node.startAfter(1, 'a').endBefore(2, 'd'), { b: 2, c: 3 }],
|
|
[node.startAfter(1, 'b').endBefore(2, 'c'), null],
|
|
[node.startAfter(1, 'c').endBefore(2), null]
|
|
];
|
|
|
|
return Promise.all(
|
|
tasks.map(async task => {
|
|
const [query, val] = task;
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
query.on('value', snap => {
|
|
ea.addEvent(snap.val());
|
|
});
|
|
const [newVal] = await ea.promise;
|
|
expect(newVal).to.deep.equal(val);
|
|
})
|
|
);
|
|
});
|
|
|
|
it('Ensure startAt / endAt with priority and name work with server data', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
await node.set({
|
|
a: { '.value': 1, '.priority': 1 },
|
|
b: { '.value': 2, '.priority': 1 },
|
|
c: { '.value': 3, '.priority': 2 },
|
|
d: { '.value': 4, '.priority': 2 }
|
|
});
|
|
const tasks: TaskList = [
|
|
[node.startAt(1, 'a').endAt(2, 'd'), { a: 1, b: 2, c: 3, d: 4 }],
|
|
[node.startAt(1, 'b').endAt(2, 'c'), { b: 2, c: 3 }],
|
|
[node.startAt(1, 'c').endAt(2), { c: 3, d: 4 }]
|
|
];
|
|
return Promise.all(
|
|
tasks.map(async task => {
|
|
const [query, val] = task;
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
query.on('value', snap => {
|
|
ea.addEvent(snap.val());
|
|
});
|
|
const [newVal] = await ea.promise;
|
|
expect(newVal).to.deep.equal(val);
|
|
})
|
|
);
|
|
});
|
|
|
|
it('Ensure startAfter / endAt with priority and name work with server data', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
await node.set({
|
|
a: { '.value': 1, '.priority': 1 },
|
|
b: { '.value': 2, '.priority': 1 },
|
|
c: { '.value': 3, '.priority': 2 },
|
|
d: { '.value': 4, '.priority': 2 }
|
|
});
|
|
const tasks: TaskList = [
|
|
[node.startAfter(1, 'a').endAt(2, 'd'), { b: 2, c: 3, d: 4 }],
|
|
[node.startAfter(1, 'b').endAt(2, 'c'), { c: 3 }],
|
|
[node.startAfter(1, 'c').endAt(2), { c: 3, d: 4 }]
|
|
];
|
|
return Promise.all(
|
|
tasks.map(async task => {
|
|
const [query, val] = task;
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
query.on('value', snap => {
|
|
ea.addEvent(snap.val());
|
|
});
|
|
const [newVal] = await ea.promise;
|
|
expect(newVal).to.deep.equal(val);
|
|
})
|
|
);
|
|
});
|
|
|
|
it('Ensure startAt / endBefore with priority and name work with server data', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
await node.set({
|
|
a: { '.value': 1, '.priority': 1 },
|
|
b: { '.value': 2, '.priority': 1 },
|
|
c: { '.value': 3, '.priority': 2 },
|
|
d: { '.value': 4, '.priority': 2 }
|
|
});
|
|
const tasks: TaskList = [
|
|
[node.startAt(1, 'a').endBefore(2, 'd'), { a: 1, b: 2, c: 3 }],
|
|
[node.startAt(1, 'b').endBefore(2, 'c'), { b: 2 }],
|
|
[node.startAt(1, 'c').endBefore(2), null]
|
|
];
|
|
return Promise.all(
|
|
tasks.map(async task => {
|
|
const [query, val] = task;
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
query.on('value', snap => {
|
|
ea.addEvent(snap.val());
|
|
});
|
|
const [newVal] = await ea.promise;
|
|
expect(newVal).to.deep.equal(val);
|
|
})
|
|
);
|
|
});
|
|
|
|
it('Ensure startAfter / endBefore with priority and name work with server data', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
await node.set({
|
|
a: { '.value': 1, '.priority': 1 },
|
|
b: { '.value': 2, '.priority': 1 },
|
|
c: { '.value': 3, '.priority': 2 },
|
|
d: { '.value': 4, '.priority': 2 }
|
|
});
|
|
const tasks: TaskList = [
|
|
[node.startAfter(1, 'a').endBefore(2, 'd'), { b: 2, c: 3 }],
|
|
[node.startAfter(1, 'b').endBefore(2, 'c'), null],
|
|
[node.startAfter(1, 'c').endBefore(2), null]
|
|
];
|
|
return Promise.all(
|
|
tasks.map(async task => {
|
|
const [query, val] = task;
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
query.on('value', snap => {
|
|
ea.addEvent(snap.val());
|
|
});
|
|
const [newVal] = await ea.promise;
|
|
expect(newVal).to.deep.equal(val);
|
|
})
|
|
);
|
|
});
|
|
|
|
it('Ensure startAt / endAt with priority and name works (2).', () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
const tasks: TaskList = [
|
|
[node.startAt(1, 'c').endAt(2, 'b'), { a: 1, b: 2, c: 3, d: 4 }],
|
|
[node.startAt(1, 'd').endAt(2, 'a'), { d: 4, a: 1 }],
|
|
[node.startAt(1, 'e').endAt(2), { a: 1, b: 2 }]
|
|
];
|
|
|
|
node.set({
|
|
c: { '.value': 3, '.priority': 1 },
|
|
d: { '.value': 4, '.priority': 1 },
|
|
a: { '.value': 1, '.priority': 2 },
|
|
b: { '.value': 2, '.priority': 2 }
|
|
});
|
|
|
|
return Promise.all(
|
|
tasks.map(async task => {
|
|
const [query, val] = task;
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
query.on('value', snap => {
|
|
ea.addEvent(snap.val());
|
|
});
|
|
const [newVal] = await ea.promise;
|
|
expect(newVal).to.deep.equal(val);
|
|
})
|
|
);
|
|
});
|
|
|
|
it('Ensure startAfter / endAt with priority and name works (2).', () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
const tasks: TaskList = [
|
|
[node.startAfter(1, 'c').endAt(2, 'b'), { a: 1, b: 2, d: 4 }],
|
|
[node.startAfter(1, 'd').endAt(2, 'a'), { a: 1 }],
|
|
[node.startAfter(1, 'e').endAt(2), { a: 1, b: 2 }]
|
|
];
|
|
|
|
node.set({
|
|
c: { '.value': 3, '.priority': 1 },
|
|
d: { '.value': 4, '.priority': 1 },
|
|
a: { '.value': 1, '.priority': 2 },
|
|
b: { '.value': 2, '.priority': 2 }
|
|
});
|
|
|
|
return Promise.all(
|
|
tasks.map(async task => {
|
|
const [query, val] = task;
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
query.on('value', snap => {
|
|
ea.addEvent(snap.val());
|
|
});
|
|
const [newVal] = await ea.promise;
|
|
expect(newVal).to.deep.equal(val);
|
|
})
|
|
);
|
|
});
|
|
|
|
it('Ensure startAt / endBefore with priority and name works with queries before set.', () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
const tasks: TaskList = [
|
|
[node.startAt(1, 'c').endBefore(2, 'b'), { a: 1, c: 3, d: 4 }],
|
|
[node.startAt(1, 'd').endBefore(2, 'a'), { d: 4 }],
|
|
[node.startAt(1, 'e').endBefore(2), null]
|
|
];
|
|
|
|
node.set({
|
|
c: { '.value': 3, '.priority': 1 },
|
|
d: { '.value': 4, '.priority': 1 },
|
|
a: { '.value': 1, '.priority': 2 },
|
|
b: { '.value': 2, '.priority': 2 }
|
|
});
|
|
|
|
return Promise.all(
|
|
tasks.map(async task => {
|
|
const [query, val] = task;
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
query.on('value', snap => {
|
|
ea.addEvent(snap.val());
|
|
});
|
|
const [newVal] = await ea.promise;
|
|
expect(newVal).to.deep.equal(val);
|
|
})
|
|
);
|
|
});
|
|
|
|
it('Ensure startAfter / endBefore with priority and name works (2).', () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
const tasks: TaskList = [
|
|
[node.startAfter(1, 'c').endBefore(2, 'b'), { a: 1, d: 4 }],
|
|
[node.startAfter(1, 'd').endBefore(2, 'a'), null],
|
|
[node.startAfter(1, 'e').endBefore(2), null]
|
|
];
|
|
|
|
node.set({
|
|
c: { '.value': 3, '.priority': 1 },
|
|
d: { '.value': 4, '.priority': 1 },
|
|
a: { '.value': 1, '.priority': 2 },
|
|
b: { '.value': 2, '.priority': 2 }
|
|
});
|
|
|
|
return Promise.all(
|
|
tasks.map(async task => {
|
|
const [query, val] = task;
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
query.on('value', snap => {
|
|
ea.addEvent(snap.val());
|
|
});
|
|
const [newVal] = await ea.promise;
|
|
expect(newVal).to.deep.equal(val);
|
|
})
|
|
);
|
|
});
|
|
|
|
it('Ensure startAt / endAt with priority and name works (2). With server data', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
await node.set({
|
|
c: { '.value': 3, '.priority': 1 },
|
|
d: { '.value': 4, '.priority': 1 },
|
|
a: { '.value': 1, '.priority': 2 },
|
|
b: { '.value': 2, '.priority': 2 }
|
|
});
|
|
|
|
const tasks: TaskList = [
|
|
[node.startAt(1, 'c').endAt(2, 'b'), { a: 1, b: 2, c: 3, d: 4 }],
|
|
[node.startAt(1, 'd').endAt(2, 'a'), { d: 4, a: 1 }],
|
|
[node.startAt(1, 'e').endAt(2), { a: 1, b: 2 }]
|
|
];
|
|
|
|
return Promise.all(
|
|
tasks.map(async task => {
|
|
const [query, val] = task;
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
query.on('value', snap => {
|
|
ea.addEvent(snap.val());
|
|
});
|
|
const [newVal] = await ea.promise;
|
|
expect(newVal).to.deep.equal(val);
|
|
})
|
|
);
|
|
});
|
|
|
|
it('Ensure startAfter / endAt with priority and name works (2). With server data', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
await node.set({
|
|
c: { '.value': 3, '.priority': 1 },
|
|
d: { '.value': 4, '.priority': 1 },
|
|
a: { '.value': 1, '.priority': 2 },
|
|
b: { '.value': 2, '.priority': 2 }
|
|
});
|
|
|
|
const tasks: TaskList = [
|
|
[node.startAfter(1, 'c').endAt(2, 'b'), { a: 1, b: 2, d: 4 }],
|
|
[node.startAfter(1, 'd').endAt(2, 'a'), { a: 1 }],
|
|
[node.startAfter(1, 'e').endAt(2), { a: 1, b: 2 }]
|
|
];
|
|
|
|
return Promise.all(
|
|
tasks.map(async task => {
|
|
const [query, val] = task;
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
query.on('value', snap => {
|
|
ea.addEvent(snap.val());
|
|
});
|
|
const [newVal] = await ea.promise;
|
|
expect(newVal).to.deep.equal(val);
|
|
})
|
|
);
|
|
});
|
|
|
|
it('Ensure startAt / endBefore with priority and name works (2). With server data.', () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
node.set({
|
|
c: { '.value': 3, '.priority': 1 },
|
|
d: { '.value': 4, '.priority': 1 },
|
|
a: { '.value': 1, '.priority': 2 },
|
|
b: { '.value': 2, '.priority': 2 }
|
|
});
|
|
|
|
const tasks: TaskList = [
|
|
[node.startAt(1, 'c').endBefore(2, 'b'), { a: 1, c: 3, d: 4 }],
|
|
[node.startAt(1, 'd').endBefore(2, 'a'), { d: 4 }],
|
|
[node.startAt(1, 'e').endBefore(2), null]
|
|
];
|
|
|
|
return Promise.all(
|
|
tasks.map(async task => {
|
|
const [query, val] = task;
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
query.on('value', snap => {
|
|
ea.addEvent(snap.val());
|
|
});
|
|
const [newVal] = await ea.promise;
|
|
expect(newVal).to.deep.equal(val);
|
|
})
|
|
);
|
|
});
|
|
|
|
it('Ensure startAfter / endBefore with priority and name works (2). With server data.', () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
node.set({
|
|
c: { '.value': 3, '.priority': 1 },
|
|
d: { '.value': 4, '.priority': 1 },
|
|
a: { '.value': 1, '.priority': 2 },
|
|
b: { '.value': 2, '.priority': 2 }
|
|
});
|
|
|
|
const tasks: TaskList = [
|
|
[node.startAfter(1, 'c').endBefore(2, 'b'), { a: 1, d: 4 }],
|
|
[node.startAfter(1, 'd').endBefore(2, 'a'), null],
|
|
[node.startAfter(1, 'e').endBefore(2), null]
|
|
];
|
|
|
|
return Promise.all(
|
|
tasks.map(async task => {
|
|
const [query, val] = task;
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
query.on('value', snap => {
|
|
ea.addEvent(snap.val());
|
|
});
|
|
const [newVal] = await ea.promise;
|
|
expect(newVal).to.deep.equal(val);
|
|
})
|
|
);
|
|
});
|
|
|
|
it('Set a limit, add some nodes, ensure prevName works correctly.', () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
let added = '';
|
|
node.limitToLast(2).on('child_added', (snap, prevName) => {
|
|
added += snap.key + ' ' + prevName + ', ';
|
|
});
|
|
|
|
node.child('a').set(1);
|
|
expect(added).to.equal('a null, ');
|
|
|
|
added = '';
|
|
node.child('c').set(3);
|
|
expect(added).to.equal('c a, ');
|
|
|
|
added = '';
|
|
node.child('b').set(2);
|
|
expect(added).to.equal('b null, ');
|
|
|
|
added = '';
|
|
node.child('d').set(4);
|
|
expect(added).to.equal('d c, ');
|
|
});
|
|
|
|
it('Set a limit, add some nodes, ensure prevName works correctly. With server data', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
let added = '';
|
|
await node.child('a').set(1);
|
|
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
node.limitToLast(2).on('child_added', (snap, prevName) => {
|
|
added += snap.key + ' ' + prevName + ', ';
|
|
ea.addEvent();
|
|
});
|
|
|
|
await ea.promise;
|
|
|
|
expect(added).to.equal('a null, ');
|
|
|
|
added = '';
|
|
await node.child('c').set(3);
|
|
|
|
expect(added).to.equal('c a, ');
|
|
|
|
added = '';
|
|
await node.child('b').set(2);
|
|
|
|
expect(added).to.equal('b null, ');
|
|
|
|
added = '';
|
|
await node.child('d').set(4);
|
|
|
|
expect(added).to.equal('d c, ');
|
|
});
|
|
|
|
it('Set a limit, move some nodes, ensure prevName works correctly.', () => {
|
|
const node = getRandomNode() as Reference;
|
|
let moved = '';
|
|
node.limitToLast(2).on('child_moved', (snap, prevName) => {
|
|
moved += snap.key + ' ' + prevName + ', ';
|
|
});
|
|
|
|
node.child('a').setWithPriority('a', 10);
|
|
node.child('b').setWithPriority('b', 20);
|
|
node.child('c').setWithPriority('c', 30);
|
|
node.child('d').setWithPriority('d', 40);
|
|
|
|
node.child('c').setPriority(50);
|
|
expect(moved).to.equal('c d, ');
|
|
|
|
moved = '';
|
|
node.child('c').setPriority(35);
|
|
expect(moved).to.equal('c null, ');
|
|
|
|
moved = '';
|
|
node.child('b').setPriority(33);
|
|
expect(moved).to.equal('');
|
|
});
|
|
|
|
it('Set a limit, move some nodes, ensure prevName works correctly, with server data', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
let moved = '';
|
|
|
|
node.child('a').setWithPriority('a', 10);
|
|
node.child('b').setWithPriority('b', 20);
|
|
node.child('c').setWithPriority('c', 30);
|
|
await node.child('d').setWithPriority('d', 40);
|
|
|
|
node.limitToLast(2).on('child_moved', async (snap, prevName) => {
|
|
moved += snap.key + ' ' + prevName + ', ';
|
|
});
|
|
// Need to load the data before the set so we'll see the move
|
|
await node.limitToLast(2).once('value');
|
|
|
|
await node.child('c').setPriority(50);
|
|
|
|
expect(moved).to.equal('c d, ');
|
|
|
|
moved = '';
|
|
await node.child('c').setPriority(35);
|
|
|
|
expect(moved).to.equal('c null, ');
|
|
moved = '';
|
|
await node.child('b').setPriority(33);
|
|
|
|
expect(moved).to.equal('');
|
|
});
|
|
|
|
it('Numeric priorities: Set a limit, move some nodes, ensure prevName works correctly.', () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
let moved = '';
|
|
node.limitToLast(2).on('child_moved', (snap, prevName) => {
|
|
moved += snap.key + ' ' + prevName + ', ';
|
|
});
|
|
|
|
node.child('a').setWithPriority('a', 1);
|
|
node.child('b').setWithPriority('b', 2);
|
|
node.child('c').setWithPriority('c', 3);
|
|
node.child('d').setWithPriority('d', 4);
|
|
|
|
node.child('c').setPriority(10);
|
|
expect(moved).to.equal('c d, ');
|
|
});
|
|
|
|
it('Numeric priorities: Set a limit, move some nodes, ensure prevName works correctly. With server data', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
let moved = '';
|
|
|
|
node.child('a').setWithPriority('a', 1);
|
|
node.child('b').setWithPriority('b', 2);
|
|
node.child('c').setWithPriority('c', 3);
|
|
await node.child('d').setWithPriority('d', 4);
|
|
|
|
node.limitToLast(2).on('child_moved', (snap, prevName) => {
|
|
moved += snap.key + ' ' + prevName + ', ';
|
|
});
|
|
// Need to load the data before the set so we'll see the move
|
|
await node.limitToLast(2).once('value');
|
|
|
|
await node.child('c').setPriority(10);
|
|
|
|
expect(moved).to.equal('c d, ');
|
|
});
|
|
|
|
it('Set a limit, add a bunch of nodes, ensure local events are correct.', () => {
|
|
const node = getRandomNode() as Reference;
|
|
node.set({});
|
|
let eventHistory = '';
|
|
|
|
node.limitToLast(2).on('child_added', snap => {
|
|
eventHistory = eventHistory + snap.val() + ' added, ';
|
|
});
|
|
node.limitToLast(2).on('child_removed', snap => {
|
|
eventHistory = eventHistory + snap.val() + ' removed, ';
|
|
});
|
|
|
|
for (let i = 0; i < 5; i++) {
|
|
const n = node.push();
|
|
n.set(i);
|
|
}
|
|
|
|
expect(eventHistory).to.equal(
|
|
'0 added, 1 added, 0 removed, 2 added, 1 removed, 3 added, 2 removed, 4 added, '
|
|
);
|
|
});
|
|
|
|
it('Set a limit, add a bunch of nodes, ensure remote events are correct.', async () => {
|
|
const nodePair = getRandomNode(2);
|
|
const writeNode = nodePair[0];
|
|
const readNode = nodePair[1];
|
|
const ea = new EventAccumulator(() => {
|
|
try {
|
|
expect(eventHistory).to.equal('3 added, 4 added, ');
|
|
return true;
|
|
} catch (err) {
|
|
return false;
|
|
}
|
|
});
|
|
let eventHistory = '';
|
|
|
|
readNode.limitToLast(2).on('child_added', snap => {
|
|
eventHistory = eventHistory + snap.val() + ' added, ';
|
|
ea.addEvent();
|
|
});
|
|
readNode.limitToLast(2).on('child_removed', snap => {
|
|
eventHistory = eventHistory.replace(snap.val() + ' added, ', '');
|
|
/**
|
|
* This test expects this code NOT to fire, so by adding this
|
|
* I trigger the resolve early if it happens to fire and fail
|
|
* the expect at the end
|
|
*/
|
|
ea.addEvent();
|
|
});
|
|
|
|
const promises = [];
|
|
for (let i = 0; i < 5; i++) {
|
|
const n = writeNode.push();
|
|
n.set(i);
|
|
}
|
|
|
|
await ea.promise;
|
|
});
|
|
|
|
it('Query.once() only fires once', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
let count = 1;
|
|
node.set(count);
|
|
|
|
const valueEvent = EventAccumulatorFactory.waitsForCount(3);
|
|
node.on('value', () => {
|
|
if (count < 3) {
|
|
++count;
|
|
node.set(count);
|
|
}
|
|
valueEvent.addEvent();
|
|
});
|
|
|
|
const onceEvent = EventAccumulatorFactory.waitsForExactCount(1);
|
|
node.once('value', () => {
|
|
++count;
|
|
node.set(count);
|
|
onceEvent.addEvent();
|
|
});
|
|
|
|
await valueEvent.promise;
|
|
await onceEvent.promise;
|
|
});
|
|
|
|
it('Ensure on() returns callback function.', () => {
|
|
const node = getRandomNode() as Reference;
|
|
const callback = function () {};
|
|
const ret = node.on('value', callback);
|
|
expect(ret).to.equal(callback);
|
|
});
|
|
|
|
it("Limit on unsynced node fires 'value'.", done => {
|
|
const f = getRandomNode() as Reference;
|
|
f.limitToLast(1).on('value', () => {
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('Filtering to only null priorities works.', async () => {
|
|
const f = getRandomNode() as Reference;
|
|
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
f.root.child('.info/connected').on('value', snap => {
|
|
ea.addEvent();
|
|
});
|
|
|
|
await ea.promise;
|
|
|
|
f.set({
|
|
a: { '.priority': null, '.value': 0 },
|
|
b: { '.priority': null, '.value': 1 },
|
|
c: { '.priority': '2', '.value': 2 },
|
|
d: { '.priority': 3, '.value': 3 },
|
|
e: { '.priority': 'hi', '.value': 4 }
|
|
});
|
|
|
|
const snapAcc = EventAccumulatorFactory.waitsForCount(1);
|
|
f.startAt(null)
|
|
.endAt(null)
|
|
.on('value', snap => {
|
|
snapAcc.addEvent(snap.val());
|
|
});
|
|
|
|
const [val] = await snapAcc.promise;
|
|
expect(val).to.deep.equal({ a: 0, b: 1 });
|
|
});
|
|
|
|
it('Null priorities not included in startAfter().', async () => {
|
|
const f = getRandomNode() as Reference;
|
|
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
f.root.child('.info/connected').on('value', snap => {
|
|
ea.addEvent();
|
|
});
|
|
|
|
await ea.promise;
|
|
|
|
f.set({
|
|
a: { '.priority': null, '.value': 0 },
|
|
b: { '.priority': null, '.value': 1 },
|
|
c: { '.priority': '2', '.value': 2 },
|
|
d: { '.priority': 3, '.value': 3 },
|
|
e: { '.priority': 'hi', '.value': 4 }
|
|
});
|
|
|
|
const snapAcc = EventAccumulatorFactory.waitsForCount(1);
|
|
f.startAfter(null)
|
|
.endAt(null)
|
|
.on('value', snap => {
|
|
snapAcc.addEvent(snap.val());
|
|
});
|
|
|
|
const [val] = await snapAcc.promise;
|
|
expect(val).to.deep.equal(null);
|
|
});
|
|
|
|
it('null priorities included in endAt(2).', async () => {
|
|
const f = getRandomNode() as Reference;
|
|
|
|
f.set({
|
|
a: { '.priority': null, '.value': 0 },
|
|
b: { '.priority': null, '.value': 1 },
|
|
c: { '.priority': 2, '.value': 2 },
|
|
d: { '.priority': 3, '.value': 3 },
|
|
e: { '.priority': 'hi', '.value': 4 }
|
|
});
|
|
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
f.endAt(2).on('value', snap => {
|
|
ea.addEvent(snap.val());
|
|
});
|
|
|
|
const [val] = await ea.promise;
|
|
expect(val).to.deep.equal({ a: 0, b: 1, c: 2 });
|
|
});
|
|
|
|
it('null priorities included in endBefore.', async () => {
|
|
const f = getRandomNode() as Reference;
|
|
|
|
f.set({
|
|
a: { '.priority': null, '.value': 0 },
|
|
b: { '.priority': null, '.value': 1 },
|
|
c: { '.priority': 2, '.value': 2 },
|
|
d: { '.priority': 3, '.value': 3 },
|
|
e: { '.priority': 'hi', '.value': 4 }
|
|
});
|
|
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
f.endBefore(2).on('value', snap => {
|
|
ea.addEvent(snap.val());
|
|
});
|
|
|
|
const [val] = await ea.promise;
|
|
expect(val).to.deep.equal({ a: 0, b: 1 });
|
|
});
|
|
|
|
it('null priorities not included in startAt(2).', async () => {
|
|
const f = getRandomNode() as Reference;
|
|
|
|
f.set({
|
|
a: { '.priority': null, '.value': 0 },
|
|
b: { '.priority': null, '.value': 1 },
|
|
c: { '.priority': 2, '.value': 2 },
|
|
d: { '.priority': 3, '.value': 3 },
|
|
e: { '.priority': 'hi', '.value': 4 }
|
|
});
|
|
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
|
|
f.startAt(2).on('value', snap => {
|
|
ea.addEvent(snap.val());
|
|
});
|
|
|
|
const [val] = await ea.promise;
|
|
expect(val).to.deep.equal({ c: 2, d: 3, e: 4 });
|
|
});
|
|
|
|
it('null priorities not included in startAfter(2).', async () => {
|
|
const f = getRandomNode() as Reference;
|
|
|
|
f.set({
|
|
a: { '.priority': null, '.value': 0 },
|
|
b: { '.priority': null, '.value': 1 },
|
|
c: { '.priority': 2, '.value': 2 },
|
|
d: { '.priority': 3, '.value': 3 },
|
|
e: { '.priority': 'hi', '.value': 4 }
|
|
});
|
|
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
|
|
f.startAfter(2).on('value', snap => {
|
|
ea.addEvent(snap.val());
|
|
});
|
|
|
|
const [val] = await ea.promise;
|
|
expect(val).to.deep.equal({ d: 3, e: 4 });
|
|
});
|
|
|
|
function dumpListens(node: Query) {
|
|
const listens: Map<string, Map<string, unknown>> = (
|
|
node._delegate._repo.persistentConnection_ as any
|
|
).listens;
|
|
const nodePath = getPath(node);
|
|
const listenPaths = [];
|
|
for (const path of listens.keys()) {
|
|
if (path.substring(0, nodePath.length) === nodePath) {
|
|
listenPaths.push(path);
|
|
}
|
|
}
|
|
|
|
listenPaths.sort();
|
|
const dumpPieces = [];
|
|
for (let i = 0; i < listenPaths.length; i++) {
|
|
const queryIds = [];
|
|
for (const queryId of listens.get(listenPaths[i]).keys()) {
|
|
queryIds.push(queryId);
|
|
}
|
|
queryIds.sort();
|
|
if (queryIds.length > 0) {
|
|
dumpPieces.push(
|
|
listenPaths[i].substring(nodePath.length) + ':' + queryIds.join(',')
|
|
);
|
|
}
|
|
}
|
|
|
|
return dumpPieces.join(';');
|
|
}
|
|
|
|
it('Dedupe listens: listen on parent.', () => {
|
|
const node = getRandomNode() as Reference;
|
|
expect(dumpListens(node)).to.equal('');
|
|
|
|
const aOn = node.child('a').on('value', () => {});
|
|
expect(dumpListens(node)).to.equal('/a:default');
|
|
|
|
const rootOn = node.on('value', () => {});
|
|
expect(dumpListens(node)).to.equal(':default');
|
|
|
|
node.off('value', rootOn);
|
|
expect(dumpListens(node)).to.equal('/a:default');
|
|
|
|
node.child('a').off('value', aOn);
|
|
expect(dumpListens(node)).to.equal('');
|
|
});
|
|
|
|
it('Dedupe listens: listen on grandchild.', () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
const rootOn = node.on('value', () => {});
|
|
expect(dumpListens(node)).to.equal(':default');
|
|
|
|
const aaOn = node.child('a/aa').on('value', () => {});
|
|
expect(dumpListens(node)).to.equal(':default');
|
|
|
|
node.off('value', rootOn);
|
|
node.child('a/aa').off('value', aaOn);
|
|
expect(dumpListens(node)).to.equal('');
|
|
});
|
|
|
|
it('Dedupe listens: listen on grandparent of two children.', () => {
|
|
const node = getRandomNode() as Reference;
|
|
expect(dumpListens(node)).to.equal('');
|
|
|
|
const aaOn = node.child('a/aa').on('value', () => {});
|
|
expect(dumpListens(node)).to.equal('/a/aa:default');
|
|
|
|
const bbOn = node.child('a/bb').on('value', () => {});
|
|
expect(dumpListens(node)).to.equal('/a/aa:default;/a/bb:default');
|
|
|
|
const rootOn = node.on('value', () => {});
|
|
expect(dumpListens(node)).to.equal(':default');
|
|
|
|
node.off('value', rootOn);
|
|
expect(dumpListens(node)).to.equal('/a/aa:default;/a/bb:default');
|
|
|
|
node.child('a/aa').off('value', aaOn);
|
|
expect(dumpListens(node)).to.equal('/a/bb:default');
|
|
|
|
node.child('a/bb').off('value', bbOn);
|
|
expect(dumpListens(node)).to.equal('');
|
|
});
|
|
|
|
it('Dedupe queried listens: multiple queried listens; no dupes', () => {
|
|
const node = getRandomNode() as Reference;
|
|
expect(dumpListens(node)).to.equal('');
|
|
|
|
const aLim1On = node
|
|
.child('a')
|
|
.limitToLast(1)
|
|
.on('value', () => {});
|
|
expect(dumpListens(node)).to.equal('/a:{"l":1,"vf":"r"}');
|
|
|
|
const rootLim1On = node.limitToLast(1).on('value', () => {});
|
|
expect(dumpListens(node)).to.equal(':{"l":1,"vf":"r"};/a:{"l":1,"vf":"r"}');
|
|
|
|
const aLim5On = node
|
|
.child('a')
|
|
.limitToLast(5)
|
|
.on('value', () => {});
|
|
expect(dumpListens(node)).to.equal(
|
|
':{"l":1,"vf":"r"};/a:{"l":1,"vf":"r"},{"l":5,"vf":"r"}'
|
|
);
|
|
|
|
node.limitToLast(1).off('value', rootLim1On);
|
|
expect(dumpListens(node)).to.equal('/a:{"l":1,"vf":"r"},{"l":5,"vf":"r"}');
|
|
|
|
node.child('a').limitToLast(1).off('value', aLim1On);
|
|
node.child('a').limitToLast(5).off('value', aLim5On);
|
|
expect(dumpListens(node)).to.equal('');
|
|
});
|
|
|
|
it('Dedupe queried listens: listen on parent of queried children.', () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
const aLim1On = node
|
|
.child('a')
|
|
.limitToLast(1)
|
|
.on('value', () => {});
|
|
expect(dumpListens(node)).to.equal('/a:{"l":1,"vf":"r"}');
|
|
|
|
const bLim1On = node
|
|
.child('b')
|
|
.limitToLast(1)
|
|
.on('value', () => {});
|
|
expect(dumpListens(node)).to.equal(
|
|
'/a:{"l":1,"vf":"r"};/b:{"l":1,"vf":"r"}'
|
|
);
|
|
|
|
const rootOn = node.on('value', () => {});
|
|
expect(dumpListens(node)).to.equal(':default');
|
|
|
|
// remove in slightly random order.
|
|
node.child('a').limitToLast(1).off('value', aLim1On);
|
|
expect(dumpListens(node)).to.equal(':default');
|
|
|
|
node.off('value', rootOn);
|
|
expect(dumpListens(node)).to.equal('/b:{"l":1,"vf":"r"}');
|
|
|
|
node.child('b').limitToLast(1).off('value', bLim1On);
|
|
expect(dumpListens(node)).to.equal('');
|
|
});
|
|
|
|
it('Limit with mix of null and non-null priorities.', () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
const children = [];
|
|
node.limitToLast(5).on('child_added', childSnap => {
|
|
children.push(childSnap.key);
|
|
});
|
|
|
|
node.set({
|
|
Vikrum: { '.priority': 1000, score: 1000, name: 'Vikrum' },
|
|
Mike: { '.priority': 500, score: 500, name: 'Mike' },
|
|
Andrew: { '.priority': 50, score: 50, name: 'Andrew' },
|
|
James: { '.priority': 7, score: 7, name: 'James' },
|
|
Sally: { '.priority': -7, score: -7, name: 'Sally' },
|
|
Fred: { score: 0, name: 'Fred' }
|
|
});
|
|
|
|
expect(children.join(',')).to.equal('Sally,James,Andrew,Mike,Vikrum');
|
|
});
|
|
|
|
it('Limit with mix of null and non-null priorities using server data', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
|
|
const children = [];
|
|
await node.set({
|
|
Vikrum: { '.priority': 1000, score: 1000, name: 'Vikrum' },
|
|
Mike: { '.priority': 500, score: 500, name: 'Mike' },
|
|
Andrew: { '.priority': 50, score: 50, name: 'Andrew' },
|
|
James: { '.priority': 7, score: 7, name: 'James' },
|
|
Sally: { '.priority': -7, score: -7, name: 'Sally' },
|
|
Fred: { score: 0, name: 'Fred' }
|
|
});
|
|
|
|
const ea = EventAccumulatorFactory.waitsForCount(5);
|
|
node.limitToLast(5).on('child_added', childSnap => {
|
|
children.push(childSnap.key);
|
|
ea.addEvent();
|
|
});
|
|
|
|
await ea.promise;
|
|
|
|
expect(children.join(',')).to.equal('Sally,James,Andrew,Mike,Vikrum');
|
|
});
|
|
|
|
it('.on() with a context works.', () => {
|
|
const ref = getRandomNode() as Reference;
|
|
|
|
const ListenerDoohickey = function () {
|
|
this.snap = null;
|
|
};
|
|
ListenerDoohickey.prototype.onEvent = function (snap) {
|
|
this.snap = snap;
|
|
};
|
|
|
|
const l = new ListenerDoohickey();
|
|
ref.on('value', l.onEvent, l);
|
|
|
|
ref.set('test');
|
|
expect(l.snap.val()).to.equal('test');
|
|
|
|
ref.off('value', l.onEvent, l);
|
|
|
|
// Ensure we don't get any more events.
|
|
ref.set('blah');
|
|
expect(l.snap.val()).to.equal('test');
|
|
});
|
|
|
|
it('.once() with a context works.', () => {
|
|
const ref = getRandomNode() as Reference;
|
|
|
|
const ListenerDoohickey = function () {
|
|
this.snap = null;
|
|
};
|
|
ListenerDoohickey.prototype.onEvent = function (snap) {
|
|
this.snap = snap;
|
|
};
|
|
|
|
const l = new ListenerDoohickey();
|
|
ref.once('value', l.onEvent, l);
|
|
|
|
ref.set('test');
|
|
expect(l.snap.val()).to.equal('test');
|
|
|
|
// Shouldn't get any more events.
|
|
ref.set('blah');
|
|
expect(l.snap.val()).to.equal('test');
|
|
});
|
|
|
|
it('handles an update that deletes the entire window in a query', () => {
|
|
const ref = getRandomNode() as Reference;
|
|
|
|
const snaps = [];
|
|
ref.limitToLast(2).on('value', snap => {
|
|
snaps.push(snap.val());
|
|
});
|
|
|
|
ref.set({
|
|
a: { '.value': 1, '.priority': 1 },
|
|
b: { '.value': 2, '.priority': 2 },
|
|
c: { '.value': 3, '.priority': 3 }
|
|
});
|
|
ref.update({
|
|
b: null,
|
|
c: null
|
|
});
|
|
|
|
expect(snaps.length).to.equal(2);
|
|
expect(snaps[0]).to.deep.equal({ b: 2, c: 3 });
|
|
// The original set is still outstanding (synchronous API), so we have a full cache to re-window against
|
|
expect(snaps[1]).to.deep.equal({ a: 1 });
|
|
});
|
|
|
|
it('handles an out-of-view query on a child', () => {
|
|
const ref = getRandomNode() as Reference;
|
|
|
|
let parent = null;
|
|
ref.limitToLast(1).on('value', snap => {
|
|
parent = snap.val();
|
|
});
|
|
|
|
let child = null;
|
|
ref.child('a').on('value', snap => {
|
|
child = snap.val();
|
|
});
|
|
|
|
ref.set({ a: 1, b: 2 });
|
|
expect(parent).to.deep.equal({ b: 2 });
|
|
expect(child).to.equal(1);
|
|
|
|
ref.update({ c: 3 });
|
|
expect(parent).to.deep.equal({ c: 3 });
|
|
expect(child).to.equal(1);
|
|
});
|
|
|
|
it('handles a child query going out of view of the parent', () => {
|
|
const ref = getRandomNode() as Reference;
|
|
|
|
let parent = null;
|
|
ref.limitToLast(1).on('value', snap => {
|
|
parent = snap.val();
|
|
});
|
|
|
|
let child = null;
|
|
ref.child('a').on('value', snap => {
|
|
child = snap.val();
|
|
});
|
|
|
|
ref.set({ a: 1 });
|
|
expect(parent).to.deep.equal({ a: 1 });
|
|
expect(child).to.equal(1);
|
|
ref.child('b').set(2);
|
|
expect(parent).to.deep.equal({ b: 2 });
|
|
expect(child).to.equal(1);
|
|
ref.child('b').remove();
|
|
expect(parent).to.deep.equal({ a: 1 });
|
|
expect(child).to.equal(1);
|
|
});
|
|
|
|
it('handles diverging views', () => {
|
|
const ref = getRandomNode() as Reference;
|
|
|
|
let c = null;
|
|
ref
|
|
.limitToLast(1)
|
|
.endAt(null, 'c')
|
|
.on('value', snap => {
|
|
c = snap.val();
|
|
});
|
|
|
|
let d = null;
|
|
ref
|
|
.limitToLast(1)
|
|
.endAt(null, 'd')
|
|
.on('value', snap => {
|
|
d = snap.val();
|
|
});
|
|
|
|
ref.set({ a: 1, b: 2, c: 3 });
|
|
expect(c).to.deep.equal({ c: 3 });
|
|
expect(d).to.deep.equal({ c: 3 });
|
|
ref.child('d').set(4);
|
|
expect(c).to.deep.equal({ c: 3 });
|
|
expect(d).to.deep.equal({ d: 4 });
|
|
});
|
|
|
|
it('handles removing a queried element', async () => {
|
|
const ref = getRandomNode() as Reference;
|
|
|
|
let val;
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
ref.limitToLast(1).on('child_added', snap => {
|
|
val = snap.val();
|
|
ea.addEvent();
|
|
});
|
|
|
|
ref.set({ a: 1, b: 2 });
|
|
expect(val).to.equal(2);
|
|
|
|
ref.child('b').remove();
|
|
|
|
await ea.promise;
|
|
|
|
expect(val).to.equal(1);
|
|
});
|
|
|
|
it('.startAt().limitToFirst(1) works.', done => {
|
|
const ref = getRandomNode() as Reference;
|
|
ref.set({ a: 1, b: 2 });
|
|
|
|
let val;
|
|
ref
|
|
.startAt()
|
|
.limitToFirst(1)
|
|
.on('child_added', snap => {
|
|
val = snap.val();
|
|
if (val === 1) {
|
|
done();
|
|
}
|
|
});
|
|
});
|
|
|
|
it('.startAt().limitToFirst(1) and then remove first child (case 1664).', async () => {
|
|
const ref = getRandomNode() as Reference;
|
|
ref.set({ a: 1, b: 2 });
|
|
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
let val;
|
|
ref
|
|
.startAt()
|
|
.limitToFirst(1)
|
|
.on('child_added', snap => {
|
|
val = snap.val();
|
|
ea.addEvent();
|
|
});
|
|
|
|
await ea.promise;
|
|
expect(val).to.equal(1);
|
|
|
|
ea.reset();
|
|
ref.child('a').remove();
|
|
|
|
await ea.promise;
|
|
expect(val).to.equal(2);
|
|
});
|
|
|
|
it('.startAt() with two arguments works properly (case 1169).', done => {
|
|
const ref = getRandomNode() as Reference;
|
|
const data = {
|
|
Walker: {
|
|
name: 'Walker',
|
|
score: 20,
|
|
'.priority': 20
|
|
},
|
|
Michael: {
|
|
name: 'Michael',
|
|
score: 100,
|
|
'.priority': 100
|
|
}
|
|
};
|
|
ref.set(data, () => {
|
|
ref
|
|
.startAt(20, 'Walker')
|
|
.limitToFirst(2)
|
|
.on('value', s => {
|
|
const childNames = [];
|
|
s.forEach(node => {
|
|
childNames.push(node.key);
|
|
});
|
|
expect(childNames).to.deep.equal(['Walker', 'Michael']);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
it('.startAfter() with two arguments works properly (case 1169).', done => {
|
|
const ref = getRandomNode() as Reference;
|
|
const data = {
|
|
Walker: {
|
|
name: 'Walker',
|
|
score: 20,
|
|
'.priority': 20
|
|
},
|
|
Michael: {
|
|
name: 'Michael',
|
|
score: 100,
|
|
'.priority': 100
|
|
}
|
|
};
|
|
ref.set(data, () => {
|
|
ref
|
|
.startAfter(20, 'Walker')
|
|
.limitToFirst(2)
|
|
.on('value', s => {
|
|
const childNames = [];
|
|
s.forEach(node => {
|
|
childNames.push(node.key);
|
|
});
|
|
expect(childNames).to.deep.equal(['Michael']);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
it('handles multiple queries on the same node', async () => {
|
|
const ref = getRandomNode() as Reference;
|
|
|
|
await ref.set({
|
|
a: 1,
|
|
b: 2,
|
|
c: 3,
|
|
d: 4,
|
|
e: 5,
|
|
f: 6
|
|
});
|
|
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
|
|
let firstListen = false;
|
|
ref.limitToLast(2).on('value', snap => {
|
|
// This shouldn't get called twice, we don't update the values here
|
|
expect(firstListen).to.be.false;
|
|
firstListen = true;
|
|
ea.addEvent();
|
|
});
|
|
|
|
await ea.promise;
|
|
|
|
// now do consecutive once calls
|
|
await ref.limitToLast(1).once('value');
|
|
const snap = await ref.limitToLast(1).once('value');
|
|
const val = snap.val();
|
|
expect(val).to.deep.equal({ f: 6 });
|
|
});
|
|
|
|
it('handles once called on a node with a default listener', async () => {
|
|
const ref = getRandomNode() as Reference;
|
|
|
|
await ref.set({
|
|
a: 1,
|
|
b: 2,
|
|
c: 3,
|
|
d: 4,
|
|
e: 5,
|
|
f: 6
|
|
});
|
|
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
// Setup value listener
|
|
ref.on('value', snap => {
|
|
ea.addEvent();
|
|
});
|
|
|
|
await ea.promise;
|
|
|
|
// now do the once call
|
|
const snap = await ref.limitToLast(1).once('child_added');
|
|
const val = snap.val();
|
|
expect(val).to.equal(6);
|
|
});
|
|
|
|
it('handles once called on a node with a default listener and non-complete limit', async () => {
|
|
const ref = getRandomNode() as Reference;
|
|
|
|
await ref.set({
|
|
a: 1,
|
|
b: 2,
|
|
c: 3
|
|
});
|
|
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
// Setup value listener
|
|
ref.on('value', snap => {
|
|
ea.addEvent();
|
|
});
|
|
|
|
await ea.promise;
|
|
|
|
// now do the once call
|
|
const snap = await ref.limitToLast(5).once('value');
|
|
const val = snap.val();
|
|
expect(val).to.deep.equal({ a: 1, b: 2, c: 3 });
|
|
});
|
|
|
|
it('Remote remove triggers events.', done => {
|
|
const refPair = getRandomNode(2),
|
|
writeRef = refPair[0],
|
|
readRef = refPair[1];
|
|
|
|
writeRef.set({ a: 'a', b: 'b', c: 'c', d: 'd', e: 'e' }, () => {
|
|
// Wait to get the initial data, and then remove 'c' remotely and wait for new data.
|
|
let count = 0;
|
|
readRef.limitToLast(5).on('value', s => {
|
|
count++;
|
|
if (count === 1) {
|
|
expect(s.val()).to.deep.equal({
|
|
a: 'a',
|
|
b: 'b',
|
|
c: 'c',
|
|
d: 'd',
|
|
e: 'e'
|
|
});
|
|
writeRef.child('c').remove();
|
|
} else {
|
|
expect(count).to.equal(2);
|
|
expect(s.val()).to.deep.equal({ a: 'a', b: 'b', d: 'd', e: 'e' });
|
|
done();
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
it(".endAt(null, 'f').limitToLast(5) returns the right set of children.", done => {
|
|
const ref = getRandomNode() as Reference;
|
|
ref.set(
|
|
{ a: 'a', b: 'b', c: 'c', d: 'd', e: 'e', f: 'f', g: 'g', h: 'h' },
|
|
() => {
|
|
ref
|
|
.endAt(null, 'f')
|
|
.limitToLast(5)
|
|
.on('value', s => {
|
|
expect(s.val()).to.deep.equal({
|
|
b: 'b',
|
|
c: 'c',
|
|
d: 'd',
|
|
e: 'e',
|
|
f: 'f'
|
|
});
|
|
done();
|
|
});
|
|
}
|
|
);
|
|
});
|
|
|
|
it(".endBefore(null, 'f').limitToLast(5) returns the right set of children.", done => {
|
|
const ref = getRandomNode() as Reference;
|
|
ref.set(
|
|
{ a: 'a', b: 'b', c: 'c', d: 'd', e: 'e', f: 'f', g: 'g', h: 'h' },
|
|
() => {
|
|
ref
|
|
.endBefore(null, 'f')
|
|
.limitToLast(5)
|
|
.on('value', s => {
|
|
expect(s.val()).to.deep.equal({
|
|
a: 'a',
|
|
b: 'b',
|
|
c: 'c',
|
|
d: 'd',
|
|
e: 'e'
|
|
});
|
|
done();
|
|
});
|
|
}
|
|
);
|
|
});
|
|
|
|
it('complex update() at query root raises correct value event', done => {
|
|
const nodePair = getRandomNode(2);
|
|
const writer = nodePair[0];
|
|
const reader = nodePair[1];
|
|
|
|
let readerLoaded = false;
|
|
writer
|
|
.child('foo')
|
|
.set({ a: 1, b: 2, c: 3, d: 4, e: 5 }, (error, dummy) => {
|
|
reader
|
|
.child('foo')
|
|
.startAt()
|
|
.limitToFirst(4)
|
|
.on('value', snapshot => {
|
|
const val = snapshot.val();
|
|
if (!readerLoaded) {
|
|
readerLoaded = true;
|
|
expect(val).to.deep.equal({ a: 1, b: 2, c: 3, d: 4 });
|
|
|
|
// This update causes the following to happen:
|
|
// 1. An in-view child is set to null (b)
|
|
// 2. An in-view child has its value changed (c)
|
|
// 3. An in-view child is changed and bumped out-of-view (d)
|
|
// We expect to get null values for b and d, along with the new children and updated value for c
|
|
writer
|
|
.child('foo')
|
|
.update({ b: null, c: 'a', cc: 'new', cd: 'new2', d: 'gone' });
|
|
} else {
|
|
done();
|
|
expect(val).to.deep.equal({
|
|
a: 1,
|
|
c: 'a',
|
|
cc: 'new',
|
|
cd: 'new2'
|
|
});
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
it('update() at query root raises correct value event', done => {
|
|
const nodePair = getRandomNode(2);
|
|
const writer = nodePair[0];
|
|
const reader = nodePair[1];
|
|
|
|
let readerLoaded = false;
|
|
writer
|
|
.child('foo')
|
|
.set({ bar: 'a', baz: 'b', bam: 'c' }, (error, dummy) => {
|
|
reader
|
|
.child('foo')
|
|
.limitToLast(10)
|
|
.on('value', snapshot => {
|
|
const val = snapshot.val();
|
|
if (!readerLoaded) {
|
|
readerLoaded = true;
|
|
expect(val.bar).to.equal('a');
|
|
expect(val.baz).to.equal('b');
|
|
expect(val.bam).to.equal('c');
|
|
writer.child('foo').update({ bar: 'd', bam: null, bat: 'e' });
|
|
} else {
|
|
expect(val.bar).to.equal('d');
|
|
expect(val.baz).to.equal('b');
|
|
expect(val.bat).to.equal('e');
|
|
expect(val.bam).to.equal(undefined);
|
|
done();
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
it('get at empty node is null', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
expect((await node.get()).val()).to.equal(null);
|
|
});
|
|
|
|
it('get at node returns correct value', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
const expected = { foo: 'a', bar: 'b' };
|
|
await node.set(expected);
|
|
const snapshot = await node.get();
|
|
expect(snapshot.val()).to.deep.equal(expected);
|
|
});
|
|
|
|
it('get for child returns correct value', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
await node.set({ foo: 'a', bar: 'b', baz: 'c' });
|
|
const snapshot = await node.child('baz').get();
|
|
expect(snapshot.val()).to.deep.equal('c');
|
|
});
|
|
|
|
it('get for parent returns correct value', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
const child = node.child('child');
|
|
await child.set(1);
|
|
expect((await node.get()).val()).to.deep.equal({ child: 1 });
|
|
});
|
|
|
|
it('get for removed node returns correct value', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
const expected = { foo: 'a', bar: 'b' };
|
|
await node.set(expected);
|
|
let snapshot = await node.get();
|
|
const val = snapshot.val();
|
|
expect(val).to.deep.equal(expected);
|
|
await node.remove();
|
|
snapshot = await node.get();
|
|
expect(snapshot.val()).to.be.null;
|
|
});
|
|
|
|
it('get for missing node while offline is rejected', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
node.database.goOffline();
|
|
try {
|
|
const getPromise = promiseWithTimeout(node.get());
|
|
await expect(getPromise).to.eventually.be.rejected;
|
|
} finally {
|
|
node.database.goOnline();
|
|
}
|
|
});
|
|
|
|
it('get reads node from cache when not connected', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
const node2 = getFreshRepo(node._delegate._path);
|
|
try {
|
|
await node2.set({ foo: 'bar' });
|
|
const onSnapshot = await new Promise((resolve, _) => {
|
|
node.on('value', snap => {
|
|
resolve(snap);
|
|
});
|
|
});
|
|
node.database.goOffline();
|
|
const getSnapshot = await node.get();
|
|
// node's cache dropped here.
|
|
node.off();
|
|
expect(getSnapshot.val()).to.deep.equal(
|
|
(onSnapshot as DataSnapshot).val()
|
|
);
|
|
} finally {
|
|
node.database.goOnline();
|
|
}
|
|
});
|
|
|
|
it('get reads child node from cache when not connected', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
const node2 = getFreshRepo(node._delegate._path);
|
|
try {
|
|
await node2.set({ foo: 'bar' });
|
|
const onSnapshot = await new Promise((resolve, _) => {
|
|
node.on('value', snap => {
|
|
resolve(snap);
|
|
});
|
|
});
|
|
node.database.goOffline();
|
|
const getSnapshot = await node.child('foo').get();
|
|
// node's cache dropped here.
|
|
node.off();
|
|
expect(getSnapshot.val()).to.deep.equal('bar');
|
|
} finally {
|
|
node.database.goOnline();
|
|
}
|
|
});
|
|
|
|
it('get reads parent node from cache when not connected', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
const node2 = getFreshRepo(node._delegate._path);
|
|
try {
|
|
await node2.set({ foo: 'bar' });
|
|
await node2.child('baz').set(1);
|
|
const onSnapshot = await new Promise((resolve, _) => {
|
|
node.on('value', snap => {
|
|
resolve(snap);
|
|
});
|
|
});
|
|
node.database.goOffline();
|
|
const getSnapshot = await node.get();
|
|
// node's cache dropped here.
|
|
node.off();
|
|
expect(getSnapshot.val()).to.deep.equal({ foo: 'bar', baz: 1 });
|
|
} finally {
|
|
node.database.goOnline();
|
|
}
|
|
});
|
|
|
|
it('get with pending node writes when not connected', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
const node2 = getFreshRepo(node._delegate._path);
|
|
try {
|
|
await node2.set({ foo: 'bar' });
|
|
const onSnapshot = await new Promise((resolve, _) => {
|
|
node.on('value', snap => {
|
|
resolve(snap);
|
|
});
|
|
});
|
|
node.database.goOffline();
|
|
node.set({ foo: 'baz' });
|
|
const getSnapshot = await node.get();
|
|
// node's cache dropped here.
|
|
node.off();
|
|
expect(getSnapshot.val()).to.deep.equal({ foo: 'baz' });
|
|
} finally {
|
|
node.database.goOnline();
|
|
}
|
|
});
|
|
|
|
it('get with pending child writes when not connected', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
const node2 = getFreshRepo(node._delegate._path);
|
|
try {
|
|
await node2.set({ foo: 'bar' });
|
|
const onSnapshot = await new Promise((resolve, _) => {
|
|
node.on('value', snap => {
|
|
resolve(snap);
|
|
});
|
|
});
|
|
node.database.goOffline();
|
|
node.child('baz').set(true);
|
|
const getSnapshot = await node.get();
|
|
// node's cache dropped here.
|
|
node.off();
|
|
expect(getSnapshot.val()).to.deep.equal({ foo: 'bar', baz: true });
|
|
} finally {
|
|
node.database.goOnline();
|
|
}
|
|
});
|
|
|
|
it('get with pending parent writes when not connected', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
const node2 = getFreshRepo(node._delegate._path);
|
|
try {
|
|
await node2.set({ foo: 'bar' });
|
|
const onSnapshot = await new Promise((resolve, _) => {
|
|
node.on('value', snap => {
|
|
resolve(snap);
|
|
});
|
|
});
|
|
node.database.goOffline();
|
|
node.set({ foo: 'baz' });
|
|
const getSnapshot = await node.child('foo').get();
|
|
// node's cache dropped here.
|
|
node.off();
|
|
expect(getSnapshot.val()).to.deep.equal('baz');
|
|
} finally {
|
|
node.database.goOnline();
|
|
}
|
|
});
|
|
|
|
it('get with pending writes', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
node.database.goOffline();
|
|
try {
|
|
node.set({ foo: 'bar' });
|
|
const snap = await node.get();
|
|
expect(snap.val()).to.deep.equal({ foo: 'bar' });
|
|
} finally {
|
|
node.database.goOnline();
|
|
}
|
|
});
|
|
|
|
it('get child of pending writes', async () => {
|
|
const node = getRandomNode() as Reference;
|
|
node.database.goOffline();
|
|
try {
|
|
node.set({ foo: 'bar' });
|
|
const snap = await node.child('foo').get();
|
|
expect(snap.val()).to.deep.equal('bar');
|
|
} finally {
|
|
node.database.goOnline();
|
|
}
|
|
});
|
|
|
|
it('get does not cache sibling data', async () => {
|
|
const reader = getRandomNode() as Reference;
|
|
const writer = getFreshRepo(reader._delegate._path);
|
|
await writer.set({
|
|
foo: { cached: { data: '1' }, notCached: { data: '2' } }
|
|
});
|
|
const snapshot = await reader.child('foo/cached').get();
|
|
expect(snapshot.val()).to.deep.equal({ data: '1' });
|
|
reader.database.goOffline();
|
|
try {
|
|
await expect(promiseWithTimeout(reader.child('foo/notCached').get())).to
|
|
.eventually.be.rejected;
|
|
} finally {
|
|
reader.database.goOnline();
|
|
}
|
|
});
|
|
|
|
it('set() at query root raises correct value event', done => {
|
|
const nodePair = getRandomNode(2);
|
|
const writer = nodePair[0];
|
|
const reader = nodePair[1];
|
|
|
|
let readerLoaded = false;
|
|
writer
|
|
.child('foo')
|
|
.set({ bar: 'a', baz: 'b', bam: 'c' }, (error, dummy) => {
|
|
reader
|
|
.child('foo')
|
|
.limitToLast(10)
|
|
.on('value', snapshot => {
|
|
const val = snapshot.val();
|
|
if (!readerLoaded) {
|
|
readerLoaded = true;
|
|
expect(val.bar).to.equal('a');
|
|
expect(val.baz).to.equal('b');
|
|
expect(val.bam).to.equal('c');
|
|
writer.child('foo').set({ bar: 'd', baz: 'b', bat: 'e' });
|
|
} else {
|
|
expect(val.bar).to.equal('d');
|
|
expect(val.baz).to.equal('b');
|
|
expect(val.bat).to.equal('e');
|
|
expect(val.bam).to.equal(undefined);
|
|
done();
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
it('listen for child_added events with limit and different types fires properly', done => {
|
|
const nodePair = getRandomNode(2);
|
|
const writer = nodePair[0];
|
|
const reader = nodePair[1];
|
|
|
|
let numEventsReceived = 0,
|
|
gotA = false,
|
|
gotB = false,
|
|
gotC = false;
|
|
writer.child('a').set(1, (error, dummy) => {
|
|
writer.child('b').set('b', (error, dummy) => {
|
|
writer
|
|
.child('c')
|
|
.set({ deep: 'path', of: { stuff: true } }, (error, dummy) => {
|
|
reader.limitToLast(3).on('child_added', snap => {
|
|
const val = snap.val();
|
|
switch (snap.key) {
|
|
case 'a':
|
|
gotA = true;
|
|
expect(val).to.equal(1);
|
|
break;
|
|
case 'b':
|
|
gotB = true;
|
|
expect(val).to.equal('b');
|
|
break;
|
|
case 'c':
|
|
gotC = true;
|
|
expect(val.deep).to.equal('path');
|
|
expect(val.of.stuff).to.be.true;
|
|
break;
|
|
default:
|
|
expect(false).to.be.true;
|
|
}
|
|
numEventsReceived += 1;
|
|
expect(numEventsReceived).to.be.lessThan(4);
|
|
if (gotA && gotB && gotC) {
|
|
done();
|
|
}
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
it('listen for child_changed events with limit and different types fires properly', done => {
|
|
const nodePair = getRandomNode(2);
|
|
const writer = nodePair[0];
|
|
const reader = nodePair[1];
|
|
|
|
let numEventsReceived = 0,
|
|
gotA = false,
|
|
gotB = false,
|
|
gotC = false,
|
|
readerLoaded = false;
|
|
writer.set(
|
|
{ a: 'something', b: "we'll", c: 'overwrite ' },
|
|
(error, dummy) => {
|
|
reader.limitToLast(3).on('value', snapshot => {
|
|
if (!readerLoaded) {
|
|
readerLoaded = true;
|
|
// Set up listener for upcoming change events
|
|
reader.limitToLast(3).on('child_changed', snap => {
|
|
const val = snap.val();
|
|
switch (snap.key) {
|
|
case 'a':
|
|
gotA = true;
|
|
expect(val).to.equal(1);
|
|
break;
|
|
case 'b':
|
|
gotB = true;
|
|
expect(val).to.equal('b');
|
|
break;
|
|
case 'c':
|
|
gotC = true;
|
|
expect(val.deep).to.equal('path');
|
|
expect(val.of.stuff).to.be.true;
|
|
break;
|
|
default:
|
|
expect(false).to.be.true;
|
|
}
|
|
numEventsReceived += 1;
|
|
expect(numEventsReceived).to.be.lessThan(4);
|
|
if (gotA && gotB && gotC) {
|
|
done();
|
|
}
|
|
});
|
|
|
|
// Begin changing every key
|
|
writer.child('a').set(1);
|
|
writer.child('b').set('b');
|
|
writer.child('c').set({ deep: 'path', of: { stuff: true } });
|
|
}
|
|
});
|
|
}
|
|
);
|
|
});
|
|
|
|
it('listen for child_remove events with limit and different types fires properly', done => {
|
|
const nodePair = getRandomNode(2);
|
|
const writer = nodePair[0];
|
|
const reader = nodePair[1];
|
|
|
|
let numEventsReceived = 0,
|
|
gotA = false,
|
|
gotB = false,
|
|
gotC = false,
|
|
readerLoaded = false;
|
|
writer.set(
|
|
{ a: 1, b: 'b', c: { deep: 'path', of: { stuff: true } } },
|
|
(error, dummy) => {
|
|
reader.limitToLast(3).on('value', snapshot => {
|
|
if (!readerLoaded) {
|
|
readerLoaded = true;
|
|
|
|
// Set up listener for upcoming change events
|
|
reader.limitToLast(3).on('child_removed', snap => {
|
|
const val = snap.val();
|
|
switch (snap.key) {
|
|
case 'a':
|
|
gotA = true;
|
|
expect(val).to.equal(1);
|
|
break;
|
|
case 'b':
|
|
gotB = true;
|
|
expect(val).to.equal('b');
|
|
break;
|
|
case 'c':
|
|
gotC = true;
|
|
expect(val.deep).to.equal('path');
|
|
expect(val.of.stuff).to.be.true;
|
|
break;
|
|
default:
|
|
expect(false).to.be.true;
|
|
}
|
|
numEventsReceived += 1;
|
|
expect(numEventsReceived).to.be.lessThan(4);
|
|
if (gotA && gotB && gotC) {
|
|
done();
|
|
}
|
|
});
|
|
|
|
// Begin removing every key
|
|
writer.child('a').remove();
|
|
writer.child('b').remove();
|
|
writer.child('c').remove();
|
|
}
|
|
});
|
|
}
|
|
);
|
|
});
|
|
|
|
it('listen for child_remove events when parent removed', done => {
|
|
const nodePair = getRandomNode(2);
|
|
const writer = nodePair[0];
|
|
const reader = nodePair[1];
|
|
|
|
let numEventsReceived = 0,
|
|
gotA = false,
|
|
gotB = false,
|
|
gotC = false,
|
|
readerLoaded = false;
|
|
writer.set(
|
|
{ a: 1, b: 'b', c: { deep: 'path', of: { stuff: true } } },
|
|
(error, dummy) => {
|
|
reader.limitToLast(3).on('value', snapshot => {
|
|
if (!readerLoaded) {
|
|
readerLoaded = true;
|
|
|
|
// Set up listener for upcoming change events
|
|
reader.limitToLast(3).on('child_removed', snap => {
|
|
const val = snap.val();
|
|
switch (snap.key) {
|
|
case 'a':
|
|
gotA = true;
|
|
expect(val).to.equal(1);
|
|
break;
|
|
case 'b':
|
|
gotB = true;
|
|
expect(val).to.equal('b');
|
|
break;
|
|
case 'c':
|
|
gotC = true;
|
|
expect(val.deep).to.equal('path');
|
|
expect(val.of.stuff).to.be.true;
|
|
break;
|
|
default:
|
|
expect(false).to.be.true;
|
|
}
|
|
numEventsReceived += 1;
|
|
expect(numEventsReceived).to.be.lessThan(4);
|
|
if (gotA && gotB && gotC) {
|
|
done();
|
|
}
|
|
});
|
|
|
|
// Remove the query parent
|
|
writer.remove();
|
|
}
|
|
});
|
|
}
|
|
);
|
|
});
|
|
|
|
it('listen for child_remove events when parent set to scalar', done => {
|
|
const nodePair = getRandomNode(2);
|
|
const writer = nodePair[0];
|
|
const reader = nodePair[1];
|
|
|
|
let numEventsReceived = 0,
|
|
gotA = false,
|
|
gotB = false,
|
|
gotC = false,
|
|
readerLoaded = false;
|
|
writer.set(
|
|
{ a: 1, b: 'b', c: { deep: 'path', of: { stuff: true } } },
|
|
(error, dummy) => {
|
|
reader.limitToLast(3).on('value', snapshot => {
|
|
if (!readerLoaded) {
|
|
readerLoaded = true;
|
|
|
|
// Set up listener for upcoming change events
|
|
reader.limitToLast(3).on('child_removed', snap => {
|
|
const val = snap.val();
|
|
switch (snap.key) {
|
|
case 'a':
|
|
gotA = true;
|
|
expect(val).to.equal(1);
|
|
break;
|
|
case 'b':
|
|
gotB = true;
|
|
expect(val).to.equal('b');
|
|
break;
|
|
case 'c':
|
|
gotC = true;
|
|
expect(val.deep).to.equal('path');
|
|
expect(val.of.stuff).to.be.true;
|
|
break;
|
|
default:
|
|
expect(false).to.be.true;
|
|
}
|
|
numEventsReceived += 1;
|
|
expect(numEventsReceived).to.be.lessThan(4);
|
|
if (gotA && gotB && gotC) {
|
|
done();
|
|
}
|
|
});
|
|
|
|
// Set the parent to a scalar
|
|
writer.set('scalar');
|
|
}
|
|
});
|
|
}
|
|
);
|
|
});
|
|
|
|
it('Queries behave wrong after .once().', async () => {
|
|
const refPair = getRandomNode(2),
|
|
writeRef = refPair[0],
|
|
readRef = refPair[1];
|
|
let startAtCount, defaultCount;
|
|
|
|
await writeRef.set({ a: 1, b: 2, c: 3, d: 4 });
|
|
|
|
await readRef.once('value');
|
|
|
|
const ea = EventAccumulatorFactory.waitsForCount(5);
|
|
startAtCount = 0;
|
|
readRef.startAt(null, 'd').on('child_added', () => {
|
|
startAtCount++;
|
|
ea.addEvent();
|
|
});
|
|
expect(startAtCount).to.equal(0);
|
|
|
|
defaultCount = 0;
|
|
readRef.on('child_added', () => {
|
|
defaultCount++;
|
|
ea.addEvent();
|
|
});
|
|
expect(defaultCount).to.equal(0);
|
|
|
|
readRef.on('child_removed', () => {
|
|
expect(false).to.be.true;
|
|
});
|
|
|
|
return ea.promise;
|
|
});
|
|
|
|
it('Case 2003: Correctly get events for startAt/endAt queries when priority changes.', () => {
|
|
const ref = getRandomNode() as Reference;
|
|
const addedFirst = [],
|
|
removedFirst = [],
|
|
addedSecond = [],
|
|
removedSecond = [];
|
|
ref
|
|
.startAt(0)
|
|
.endAt(10)
|
|
.on('child_added', snap => {
|
|
addedFirst.push(snap.key);
|
|
});
|
|
ref
|
|
.startAt(0)
|
|
.endAt(10)
|
|
.on('child_removed', snap => {
|
|
removedFirst.push(snap.key);
|
|
});
|
|
ref
|
|
.startAt(10)
|
|
.endAt(20)
|
|
.on('child_added', snap => {
|
|
addedSecond.push(snap.key);
|
|
});
|
|
ref
|
|
.startAt(10)
|
|
.endAt(20)
|
|
.on('child_removed', snap => {
|
|
removedSecond.push(snap.key);
|
|
});
|
|
|
|
ref.child('a').setWithPriority('a', 5);
|
|
expect(addedFirst).to.deep.equal(['a']);
|
|
ref.child('a').setWithPriority('a', 15);
|
|
expect(removedFirst).to.deep.equal(['a']);
|
|
expect(addedSecond).to.deep.equal(['a']);
|
|
|
|
ref.child('a').setWithPriority('a', 10);
|
|
expect(addedFirst).to.deep.equal(['a', 'a']);
|
|
expect(removedSecond).to.deep.equal([]);
|
|
|
|
ref.child('a').setWithPriority('a', 5);
|
|
expect(removedSecond).to.deep.equal(['a']);
|
|
});
|
|
|
|
it('Case 2003: Correctly get events for startAfter/endAt queries when priority changes.', () => {
|
|
const ref = getRandomNode() as Reference;
|
|
const addedFirst = [],
|
|
removedFirst = [],
|
|
addedSecond = [],
|
|
removedSecond = [];
|
|
ref
|
|
.startAfter(0)
|
|
.endAt(10)
|
|
.on('child_added', snap => {
|
|
addedFirst.push(snap.key);
|
|
});
|
|
ref
|
|
.startAfter(0)
|
|
.endAt(10)
|
|
.on('child_removed', snap => {
|
|
removedFirst.push(snap.key);
|
|
});
|
|
ref
|
|
.startAfter(10)
|
|
.endAt(20)
|
|
.on('child_added', snap => {
|
|
addedSecond.push(snap.key);
|
|
});
|
|
ref
|
|
.startAfter(10)
|
|
.endAt(20)
|
|
.on('child_removed', snap => {
|
|
removedSecond.push(snap.key);
|
|
});
|
|
|
|
ref.child('a').setWithPriority('a', 5);
|
|
expect(addedFirst).to.deep.equal(['a']);
|
|
ref.child('a').setWithPriority('a', 15);
|
|
expect(removedFirst).to.deep.equal(['a']);
|
|
expect(addedSecond).to.deep.equal(['a']);
|
|
|
|
ref.child('a').setWithPriority('a', 10);
|
|
ref.child('a').setWithPriority('a', 0);
|
|
expect(addedFirst).to.deep.equal(['a', 'a']);
|
|
expect(removedSecond).to.deep.equal(['a']);
|
|
|
|
ref.child('a').setWithPriority('a', 5);
|
|
expect(removedSecond).to.deep.equal(['a']);
|
|
});
|
|
|
|
it('Correctly get events for startAt/endBefore queries when priority changes.', () => {
|
|
const ref = getRandomNode() as Reference;
|
|
const addedFirst = [],
|
|
removedFirst = [],
|
|
addedSecond = [],
|
|
removedSecond = [];
|
|
ref
|
|
.startAt(0)
|
|
.endBefore(10)
|
|
.on('child_added', snap => {
|
|
addedFirst.push(snap.key);
|
|
});
|
|
ref
|
|
.startAt(0)
|
|
.endBefore(10)
|
|
.on('child_removed', snap => {
|
|
removedFirst.push(snap.key);
|
|
});
|
|
ref
|
|
.startAt(10)
|
|
.endBefore(20)
|
|
.on('child_added', snap => {
|
|
addedSecond.push(snap.key);
|
|
});
|
|
ref
|
|
.startAt(10)
|
|
.endBefore(20)
|
|
.on('child_removed', snap => {
|
|
removedSecond.push(snap.key);
|
|
});
|
|
|
|
ref.child('a').setWithPriority('a', 5);
|
|
expect(addedFirst).to.deep.equal(['a']);
|
|
ref.child('a').setWithPriority('a', 15);
|
|
expect(removedFirst).to.deep.equal(['a']);
|
|
expect(addedSecond).to.deep.equal(['a']);
|
|
|
|
ref.child('a').setWithPriority('a', 10);
|
|
ref.child('a').setWithPriority('a', 0);
|
|
expect(addedFirst).to.deep.equal(['a', 'a']);
|
|
expect(removedSecond).to.deep.equal(['a']);
|
|
|
|
ref.child('a').setWithPriority('a', 5);
|
|
expect(removedSecond).to.deep.equal(['a']);
|
|
});
|
|
|
|
it('Behaves with diverging queries', async () => {
|
|
const refs = getRandomNode(2);
|
|
const writer = refs[0];
|
|
const reader = refs[1];
|
|
|
|
await writer.set({
|
|
a: { b: 1, c: 2 },
|
|
e: 3
|
|
});
|
|
|
|
let childCount = 0;
|
|
|
|
reader.child('a/b').on('value', snap => {
|
|
const val = snap.val();
|
|
childCount++;
|
|
if (childCount === 1) {
|
|
expect(val).to.equal(1);
|
|
} else {
|
|
// fail this, nothing should have changed
|
|
expect(true).to.be.false;
|
|
}
|
|
});
|
|
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
let count = 0;
|
|
reader.limitToLast(2).on('value', snap => {
|
|
ea.addEvent();
|
|
const val = snap.val();
|
|
count++;
|
|
if (count === 1) {
|
|
expect(val).to.deep.equal({ a: { b: 1, c: 2 }, e: 3 });
|
|
} else if (count === 2) {
|
|
expect(val).to.deep.equal({ d: 4, e: 3 });
|
|
}
|
|
});
|
|
|
|
await ea.promise;
|
|
|
|
ea.reset();
|
|
writer.child('d').set(4);
|
|
|
|
return ea.promise;
|
|
});
|
|
|
|
it('Priority-only updates are processed correctly by server.', async () => {
|
|
const refPair = getRandomNode(2) as Reference[],
|
|
readRef = refPair[0],
|
|
writeRef = refPair[1];
|
|
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
let readVal;
|
|
readRef.limitToLast(2).on('value', s => {
|
|
readVal = s.val();
|
|
if (readVal) {
|
|
ea.addEvent();
|
|
}
|
|
});
|
|
writeRef.set({
|
|
a: { '.priority': 10, '.value': 1 },
|
|
b: { '.priority': 20, '.value': 2 },
|
|
c: { '.priority': 30, '.value': 3 }
|
|
});
|
|
|
|
await ea.promise;
|
|
expect(readVal).to.deep.equal({ b: 2, c: 3 });
|
|
|
|
ea.reset();
|
|
writeRef.child('a').setPriority(25);
|
|
|
|
await ea.promise;
|
|
expect(readVal).to.deep.equal({ a: 1, c: 3 });
|
|
});
|
|
|
|
it('Server: Test re-listen', done => {
|
|
const refPair = getRandomNode(2) as Reference[],
|
|
ref = refPair[0],
|
|
ref2 = refPair[1];
|
|
ref.set({
|
|
a: 'a',
|
|
b: 'b',
|
|
c: 'c',
|
|
d: 'd',
|
|
e: 'e',
|
|
f: 'f',
|
|
g: 'g'
|
|
});
|
|
|
|
let before;
|
|
ref
|
|
.startAt(null, 'a')
|
|
.endAt(null, 'b')
|
|
.on('value', b => {
|
|
before = b.val();
|
|
});
|
|
|
|
ref.child('aa').set('aa', () => {
|
|
ref2
|
|
.startAt(null, 'a')
|
|
.endAt(null, 'b')
|
|
.on('value', b => {
|
|
expect(b.val()).to.deep.equal(before);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
it('Server: Test re-listen 2', done => {
|
|
const refPair = getRandomNode(2),
|
|
ref = refPair[0],
|
|
ref2 = refPair[1];
|
|
ref.set({
|
|
a: 'a',
|
|
b: 'b',
|
|
c: 'c',
|
|
d: 'd',
|
|
e: 'e',
|
|
f: 'f',
|
|
g: 'g'
|
|
});
|
|
|
|
let before;
|
|
ref
|
|
.startAt(null, 'b')
|
|
.limitToFirst(3)
|
|
.on('value', b => {
|
|
before = b.val();
|
|
});
|
|
|
|
ref.child('aa').update({ a: 5, aa: 4, b: 7, c: 4, d: 4, dd: 3 }, () => {
|
|
ref2
|
|
.startAt(null, 'b')
|
|
.limitToFirst(3)
|
|
.on('value', b => {
|
|
expect(b.val()).to.deep.equal(before);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
it('Server: Test re-listen 3', done => {
|
|
const refPair = getRandomNode(2),
|
|
ref = refPair[0],
|
|
ref2 = refPair[1];
|
|
ref.set({
|
|
a: 'a',
|
|
b: 'b',
|
|
c: 'c',
|
|
d: 'd',
|
|
e: 'e',
|
|
f: 'f',
|
|
g: 'g'
|
|
});
|
|
|
|
let before;
|
|
ref.limitToLast(3).on('value', b => {
|
|
before = b.val();
|
|
});
|
|
|
|
ref.child('h').set('h', () => {
|
|
ref2.limitToLast(3).on('value', b => {
|
|
expect(b.val()).to.deep.equal(before);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
it('Server limit below limit works properly.', async () => {
|
|
const refPair = getRandomNode(2) as Reference[],
|
|
readRef = refPair[0],
|
|
writeRef = refPair[1];
|
|
let childData;
|
|
|
|
await writeRef.set({
|
|
a: {
|
|
aa: { '.priority': 1, '.value': 1 },
|
|
ab: { '.priority': 1, '.value': 1 }
|
|
}
|
|
});
|
|
|
|
readRef.limitToLast(1).on('value', s => {
|
|
expect(s.val()).to.deep.equal({ a: { aa: 1, ab: 1 } });
|
|
});
|
|
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
readRef
|
|
.child('a')
|
|
.startAt(1)
|
|
.endAt(1)
|
|
.on('value', s => {
|
|
childData = s.val();
|
|
if (childData) {
|
|
ea.addEvent();
|
|
}
|
|
});
|
|
|
|
await ea.promise;
|
|
expect(childData).to.deep.equal({ aa: 1, ab: 1 });
|
|
|
|
// This should remove an item from the child query, but *not* the parent query.
|
|
ea.reset();
|
|
writeRef.child('a/ab').setWithPriority(1, 2);
|
|
|
|
await ea.promise;
|
|
|
|
expect(childData).to.deep.equal({ aa: 1 });
|
|
});
|
|
|
|
it('Server: Setting grandchild of item in limit works.', async () => {
|
|
const refPair = getRandomNode(2),
|
|
ref = refPair[0],
|
|
ref2 = refPair[1];
|
|
|
|
ref.set({
|
|
a: {
|
|
name: 'Mike'
|
|
}
|
|
});
|
|
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
const snaps = [];
|
|
ref2.limitToLast(1).on('value', s => {
|
|
const val = s.val();
|
|
if (val !== null) {
|
|
snaps.push(val);
|
|
ea.addEvent();
|
|
}
|
|
});
|
|
|
|
await ea.promise;
|
|
expect(snaps).to.deep.equal([{ a: { name: 'Mike' } }]);
|
|
|
|
ea.reset();
|
|
ref.child('a/name').set('Fred');
|
|
|
|
await ea.promise;
|
|
expect(snaps).to.deep.equal([
|
|
{ a: { name: 'Mike' } },
|
|
{ a: { name: 'Fred' } }
|
|
]);
|
|
});
|
|
|
|
it('Server: Updating grandchildren of item in limit works.', async () => {
|
|
const refPair = getRandomNode(2),
|
|
ref = refPair[0],
|
|
ref2 = refPair[1];
|
|
|
|
ref.set({
|
|
a: {
|
|
name: 'Mike'
|
|
}
|
|
});
|
|
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
const snaps = [];
|
|
ref2.limitToLast(1).on('value', s => {
|
|
const val = s.val();
|
|
if (val !== null) {
|
|
snaps.push(val);
|
|
ea.addEvent();
|
|
}
|
|
});
|
|
|
|
/**
|
|
* If I put this artificial pause here, this test works however
|
|
* something about the timing is broken
|
|
*/
|
|
await ea.promise;
|
|
expect(snaps).to.deep.equal([{ a: { name: 'Mike' } }]);
|
|
|
|
ea.reset();
|
|
ref.child('a').update({ name: null, Name: 'Fred' });
|
|
await ea.promise;
|
|
|
|
expect(snaps).to.deep.equal([
|
|
{ a: { name: 'Mike' } },
|
|
{ a: { Name: 'Fred' } }
|
|
]);
|
|
});
|
|
|
|
it('Server: New child at end of limit shows up.', async () => {
|
|
const refPair = getRandomNode(2),
|
|
ref = refPair[0],
|
|
ref2 = refPair[1];
|
|
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
let snap;
|
|
ref2.limitToLast(1).on('value', s => {
|
|
snap = s.val();
|
|
ea.addEvent();
|
|
});
|
|
|
|
await ea.promise;
|
|
expect(snap).to.be.null;
|
|
ea.reset();
|
|
|
|
ref.child('a').set('new child');
|
|
|
|
/**
|
|
* If I put this artificial pause here, this test works however
|
|
* something about the timing is broken
|
|
*/
|
|
await ea.promise;
|
|
expect(snap).to.deep.equal({ a: 'new child' });
|
|
});
|
|
|
|
it('Server: Priority-only updates are processed correctly by server (1).', async () => {
|
|
const refPair = getRandomNode(2),
|
|
readRef = refPair[0],
|
|
writeRef = refPair[1];
|
|
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
let readVal;
|
|
readRef.limitToLast(2).on('value', s => {
|
|
readVal = s.val();
|
|
if (readVal) {
|
|
ea.addEvent();
|
|
}
|
|
});
|
|
writeRef.set({
|
|
a: { '.priority': 10, '.value': 1 },
|
|
b: { '.priority': 20, '.value': 2 },
|
|
c: { '.priority': 30, '.value': 3 }
|
|
});
|
|
|
|
await ea.promise;
|
|
expect(readVal).to.deep.equal({ b: 2, c: 3 });
|
|
|
|
ea.reset();
|
|
writeRef.child('a').setPriority(25);
|
|
|
|
await ea.promise;
|
|
expect(readVal).to.deep.equal({ a: 1, c: 3 });
|
|
});
|
|
|
|
// Same as above but with an endAt() so we hit CompoundQueryView instead of SimpleLimitView.
|
|
it('Server: Priority-only updates are processed correctly by server (2).', async () => {
|
|
const refPair = getRandomNode(2),
|
|
readRef = refPair[0],
|
|
writeRef = refPair[1];
|
|
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
let readVal;
|
|
readRef
|
|
.endAt(50)
|
|
.limitToLast(2)
|
|
.on('value', s => {
|
|
readVal = s.val();
|
|
if (readVal) {
|
|
ea.addEvent();
|
|
}
|
|
});
|
|
|
|
writeRef.set({
|
|
a: { '.priority': 10, '.value': 1 },
|
|
b: { '.priority': 20, '.value': 2 },
|
|
c: { '.priority': 30, '.value': 3 }
|
|
});
|
|
|
|
await ea.promise;
|
|
expect(readVal).to.deep.equal({ b: 2, c: 3 });
|
|
|
|
ea.reset();
|
|
writeRef.child('a').setPriority(25);
|
|
|
|
await ea.promise;
|
|
expect(readVal).to.deep.equal({ a: 1, c: 3 });
|
|
});
|
|
|
|
it('Latency compensation works with limit and pushed object.', () => {
|
|
const ref = getRandomNode() as Reference;
|
|
const events = [];
|
|
ref.limitToLast(3).on('child_added', s => {
|
|
events.push(s.val());
|
|
});
|
|
|
|
// If you change this to ref.push('foo') it works.
|
|
ref.push({ a: 'foo' });
|
|
|
|
// Should have synchronously gotten an event.
|
|
expect(events.length).to.equal(1);
|
|
});
|
|
|
|
it("Cache doesn't remove items that have fallen out of view.", async () => {
|
|
const refPair = getRandomNode(2),
|
|
readRef = refPair[0],
|
|
writeRef = refPair[1];
|
|
|
|
let ea = EventAccumulatorFactory.waitsForCount(1);
|
|
let readVal;
|
|
readRef.limitToLast(2).on('value', s => {
|
|
readVal = s.val();
|
|
ea.addEvent();
|
|
});
|
|
|
|
await ea.promise;
|
|
expect(readVal).to.be.null;
|
|
|
|
ea = EventAccumulatorFactory.waitsForCount(4);
|
|
for (let i = 0; i < 4; i++) {
|
|
writeRef.child('k' + i).set(i);
|
|
}
|
|
|
|
await ea.promise;
|
|
|
|
await pause(500);
|
|
expect(readVal).to.deep.equal({ k2: 2, k3: 3 });
|
|
|
|
ea = EventAccumulatorFactory.waitsForCount(1);
|
|
writeRef.remove();
|
|
|
|
await ea.promise;
|
|
expect(readVal).to.be.null;
|
|
});
|
|
|
|
it('handles an update that moves another child that has a deeper listener out of view', async () => {
|
|
const refs = getRandomNode(2);
|
|
const reader = refs[0];
|
|
const writer = refs[1];
|
|
|
|
await writer.set({
|
|
a: { '.priority': 10, '.value': 1 },
|
|
b: { '.priority': 20, d: 4 },
|
|
c: { '.priority': 30, '.value': 3 }
|
|
});
|
|
|
|
reader.child('b/d').on('value', snap => {
|
|
expect(snap.val()).to.equal(4);
|
|
});
|
|
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
let val;
|
|
reader.limitToLast(2).on('value', snap => {
|
|
val = snap.val();
|
|
if (val) {
|
|
ea.addEvent();
|
|
}
|
|
});
|
|
|
|
await ea.promise;
|
|
expect(val).to.deep.equal({ b: { d: 4 }, c: 3 });
|
|
|
|
ea.reset();
|
|
writer.child('a').setWithPriority(1, 40);
|
|
|
|
await ea.promise;
|
|
expect(val).to.deep.equal({ c: 3, a: 1 });
|
|
});
|
|
|
|
it('Integer keys behave numerically 1.', done => {
|
|
const ref = getRandomNode() as Reference;
|
|
ref.set(
|
|
{
|
|
1: true,
|
|
50: true,
|
|
550: true,
|
|
6: true,
|
|
600: true,
|
|
70: true,
|
|
8: true,
|
|
80: true
|
|
},
|
|
() => {
|
|
ref.startAt(null, '80').once('value', s => {
|
|
expect(s.val()).to.deep.equal({ 80: true, 550: true, 600: true });
|
|
done();
|
|
});
|
|
}
|
|
);
|
|
});
|
|
|
|
it('Integer keys behave numerically 2.', done => {
|
|
const ref = getRandomNode() as Reference;
|
|
ref.set(
|
|
{
|
|
1: true,
|
|
50: true,
|
|
550: true,
|
|
6: true,
|
|
600: true,
|
|
70: true,
|
|
8: true,
|
|
80: true
|
|
},
|
|
() => {
|
|
ref.endAt(null, '50').once('value', s => {
|
|
expect(s.val()).to.deep.equal({
|
|
1: true,
|
|
6: true,
|
|
8: true,
|
|
50: true
|
|
});
|
|
done();
|
|
});
|
|
}
|
|
);
|
|
});
|
|
|
|
it('Integer keys behave numerically 3.', done => {
|
|
const ref = getRandomNode() as Reference;
|
|
ref.set(
|
|
{
|
|
1: true,
|
|
50: true,
|
|
550: true,
|
|
6: true,
|
|
600: true,
|
|
70: true,
|
|
8: true,
|
|
80: true
|
|
},
|
|
() => {
|
|
ref
|
|
.startAt(null, '50')
|
|
.endAt(null, '80')
|
|
.once('value', s => {
|
|
expect(s.val()).to.deep.equal({ 50: true, 70: true, 80: true });
|
|
done();
|
|
});
|
|
}
|
|
);
|
|
});
|
|
|
|
it('Integer keys behave numerically with startAfter.', done => {
|
|
const ref = getRandomNode() as Reference;
|
|
ref.set(
|
|
{
|
|
1: true,
|
|
50: true,
|
|
550: true,
|
|
6: true,
|
|
600: true,
|
|
70: true,
|
|
8: true,
|
|
80: true
|
|
},
|
|
() => {
|
|
ref
|
|
.startAfter(null, '50')
|
|
.endAt(null, '80')
|
|
.once('value', s => {
|
|
expect(s.val()).to.deep.equal({ 70: true, 80: true });
|
|
done();
|
|
});
|
|
}
|
|
);
|
|
});
|
|
|
|
it('Integer keys behave numerically with startAfter with overflow.', done => {
|
|
const ref = getRandomNode() as Reference;
|
|
ref.set(
|
|
{
|
|
1: true,
|
|
50: true,
|
|
550: true,
|
|
6: true,
|
|
600: true,
|
|
70: true,
|
|
8: true,
|
|
80: true,
|
|
'a': true
|
|
},
|
|
() => {
|
|
ref.startAfter(null, '' + INTEGER_32_MAX).once('value', s => {
|
|
expect(s.val()).to.deep.equal({ 'a': true });
|
|
done();
|
|
});
|
|
}
|
|
);
|
|
});
|
|
|
|
it('Integer keys behave numerically with endBefore.', done => {
|
|
const ref = getRandomNode() as Reference;
|
|
ref.set(
|
|
{
|
|
1: true,
|
|
50: true,
|
|
550: true,
|
|
6: true,
|
|
600: true,
|
|
70: true,
|
|
8: true,
|
|
80: true
|
|
},
|
|
() => {
|
|
ref.endBefore(null, '50').once('value', s => {
|
|
expect(s.val()).to.deep.equal({
|
|
1: true,
|
|
6: true,
|
|
8: true
|
|
});
|
|
done();
|
|
});
|
|
}
|
|
);
|
|
});
|
|
|
|
it('Integer keys behave numerically with endBefore with underflow.', done => {
|
|
const ref = getRandomNode() as Reference;
|
|
ref.set(
|
|
{
|
|
1: true
|
|
},
|
|
() => {
|
|
ref.endBefore(null, '' + INTEGER_32_MIN).once('value', s => {
|
|
expect(s.val()).to.deep.equal(null);
|
|
done();
|
|
});
|
|
}
|
|
);
|
|
});
|
|
|
|
it('Integer keys behave numerically with endBefore at boundary.', done => {
|
|
const ref = getRandomNode() as Reference;
|
|
const integerData = {
|
|
1: true,
|
|
50: true,
|
|
550: true,
|
|
6: true,
|
|
600: true,
|
|
70: true,
|
|
8: true,
|
|
80: true
|
|
};
|
|
const data = Object.assign({}, integerData);
|
|
data['a'] = true;
|
|
ref.set(data, () => {
|
|
ref.endBefore(null, '' + INTEGER_32_MAX).once('value', s => {
|
|
expect(s.val()).to.deep.equal(integerData),
|
|
ref.startAfter(null, '' + INTEGER_32_MAX).once('value', s => {
|
|
expect(s.val()).to.deep.equal({ 'a': true });
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
it('.limitToLast() on node with priority.', done => {
|
|
const ref = getRandomNode() as Reference;
|
|
ref.set({ a: 'blah', '.priority': 'priority' }, () => {
|
|
ref.limitToLast(2).once('value', s => {
|
|
expect(s.exportVal()).to.deep.equal({ a: 'blah' });
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
it('.equalTo works', async () => {
|
|
const ref = getRandomNode() as Reference;
|
|
const done = false;
|
|
|
|
await ref.set({
|
|
a: 1,
|
|
b: { '.priority': 2, '.value': 2 },
|
|
c: { '.priority': '3', '.value': 3 }
|
|
});
|
|
|
|
const snap1 = await ref.equalTo(2).once('value');
|
|
const val1 = snap1.exportVal();
|
|
expect(val1).to.deep.equal({ b: { '.priority': 2, '.value': 2 } });
|
|
|
|
const snap2 = await ref.equalTo('3', 'c').once('value');
|
|
|
|
const val2 = snap2.exportVal();
|
|
expect(val2).to.deep.equal({ c: { '.priority': '3', '.value': 3 } });
|
|
|
|
const snap3 = await ref.equalTo(null, 'c').once('value');
|
|
const val3 = snap3.exportVal();
|
|
expect(val3).to.be.null;
|
|
});
|
|
|
|
it('Handles fallback for orderBy', async () => {
|
|
const ref = getRandomNode() as Reference;
|
|
|
|
const children = [];
|
|
|
|
ref.orderByChild('foo').on('child_added', snap => {
|
|
children.push(snap.key);
|
|
});
|
|
|
|
// Set initial data
|
|
await ref.set({
|
|
a: { foo: 3 },
|
|
b: { foo: 1 },
|
|
c: { foo: 2 }
|
|
});
|
|
|
|
expect(children).to.deep.equal(['b', 'c', 'a']);
|
|
});
|
|
|
|
it('Get notified of deletes that happen while offline.', async () => {
|
|
const refPair = getRandomNode(2);
|
|
const queryRef = refPair[0];
|
|
const writerRef = refPair[1];
|
|
let readSnapshot = null;
|
|
|
|
// Write 3 children and then start our limit query.
|
|
await writerRef.set({ a: 1, b: 2, c: 3 });
|
|
|
|
const ea = EventAccumulatorFactory.waitsForCount(1);
|
|
queryRef.limitToLast(3).on('value', s => {
|
|
readSnapshot = s;
|
|
if (readSnapshot) {
|
|
ea.addEvent();
|
|
}
|
|
});
|
|
|
|
// Wait for us to read the 3 children.
|
|
await ea.promise;
|
|
|
|
expect(readSnapshot.val()).to.deep.equal({ a: 1, b: 2, c: 3 });
|
|
|
|
queryRef.database.goOffline();
|
|
|
|
// Delete an item in the query and then bring our connection back up.
|
|
ea.reset();
|
|
await writerRef.child('b').remove();
|
|
queryRef.database.goOnline();
|
|
|
|
await ea.promise;
|
|
expect(readSnapshot.child('b').val()).to.be.null;
|
|
});
|
|
|
|
it('Snapshot children respect default ordering', done => {
|
|
const refPair = getRandomNode(2);
|
|
const queryRef = refPair[0],
|
|
writerRef = refPair[1];
|
|
|
|
const list = {
|
|
a: {
|
|
thisvaluefirst: { '.value': true, '.priority': 1 },
|
|
name: { '.value': 'Michael', '.priority': 2 },
|
|
thisvaluelast: { '.value': true, '.priority': 3 }
|
|
},
|
|
b: {
|
|
thisvaluefirst: { '.value': true, '.priority': null },
|
|
name: { '.value': 'Rob', '.priority': 2 },
|
|
thisvaluelast: { '.value': true, '.priority': 3 }
|
|
},
|
|
c: {
|
|
thisvaluefirst: { '.value': true, '.priority': 1 },
|
|
name: { '.value': 'Jonny', '.priority': 2 },
|
|
thisvaluelast: { '.value': true, '.priority': 'somestring' }
|
|
}
|
|
};
|
|
|
|
writerRef.set(list, () => {
|
|
queryRef.orderByChild('name').once('value', snap => {
|
|
const expectedKeys = ['thisvaluefirst', 'name', 'thisvaluelast'];
|
|
const expectedNames = ['Jonny', 'Michael', 'Rob'];
|
|
|
|
// Validate that snap.child() resets order to default for child snaps
|
|
const orderedKeys = [];
|
|
snap.child('b').forEach(childSnap => {
|
|
orderedKeys.push(childSnap.key);
|
|
});
|
|
expect(orderedKeys).to.deep.equal(expectedKeys);
|
|
|
|
// Validate that snap.forEach() resets ordering to default for child snaps
|
|
const orderedNames = [];
|
|
snap.forEach(childSnap => {
|
|
orderedNames.push(childSnap.child('name').val());
|
|
const orderedKeys = [];
|
|
childSnap.forEach(grandchildSnap => {
|
|
orderedKeys.push(grandchildSnap.key);
|
|
});
|
|
expect(orderedKeys).to.deep.equal([
|
|
'thisvaluefirst',
|
|
'name',
|
|
'thisvaluelast'
|
|
]);
|
|
});
|
|
expect(orderedNames).to.deep.equal(expectedNames);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
it('Adding listens for the same paths does not check fail', done => {
|
|
// This bug manifests itself if there's a hierarchy of query listener, default listener and one-time listener
|
|
// underneath. During one-time listener registration, sync-tree traversal stopped as soon as it found a complete
|
|
// server cache (this is the case for not indexed query view). The problem is that the same traversal was
|
|
// looking for a ancestor default view, and the early exit prevented from finding the default listener above the
|
|
// one-time listener. Event removal code path wasn't removing the listener because it stopped as soon as it
|
|
// found the default view. This left the zombie one-time listener and check failed on the second attempt to
|
|
// create a listener for the same path (asana#61028598952586).
|
|
const ref = getRandomNode(1)[0];
|
|
|
|
ref.child('child').set({ name: 'John' }, () => {
|
|
ref
|
|
.orderByChild('name')
|
|
.equalTo('John')
|
|
.on('value', snap => {
|
|
ref.child('child').on('value', snap => {
|
|
ref
|
|
.child('child')
|
|
.child('favoriteToy')
|
|
.once('value', snap => {
|
|
ref
|
|
.child('child')
|
|
.child('favoriteToy')
|
|
.once('value', snap => {
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
it('Can JSON serialize refs', () => {
|
|
const ref = getRandomNode() as Reference;
|
|
expect(JSON.stringify(ref)).to.equal('"' + ref.toString() + '"');
|
|
});
|
|
});
|