mirror of
https://github.com/hanzoai/firebase-js-sdk.git
synced 2026-09-24 16:59:10 +00:00
364 lines
12 KiB
TypeScript
364 lines
12 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 { expect } from 'chai';
|
|
|
|
import { ChildrenNode } from '../src/core/snap/ChildrenNode';
|
|
import { NAME_COMPARATOR } from '../src/core/snap/comparators';
|
|
import { PRIORITY_INDEX } from '../src/core/snap/indexes/PriorityIndex';
|
|
import { IndexMap } from '../src/core/snap/IndexMap';
|
|
import { LeafNode } from '../src/core/snap/LeafNode';
|
|
import { Node } from '../src/core/snap/Node';
|
|
import { nodeFromJSON } from '../src/core/snap/nodeFromJSON';
|
|
import { newEmptyPath, Path } from '../src/core/util/Path';
|
|
import { SortedMap } from '../src/core/util/SortedMap';
|
|
|
|
describe('Node Tests', () => {
|
|
const DEFAULT_INDEX = PRIORITY_INDEX;
|
|
|
|
it('Create leaf nodes of various types.', () => {
|
|
let x = new LeafNode(5, new LeafNode(42));
|
|
expect(x.getValue()).to.equal(5);
|
|
expect(x.getPriority().val()).to.equal(42);
|
|
expect(x.isLeafNode()).to.equal(true);
|
|
|
|
x = new LeafNode('test');
|
|
expect(x.getValue()).to.equal('test');
|
|
x = new LeafNode(true);
|
|
expect(x.getValue()).to.equal(true);
|
|
});
|
|
|
|
it('LeafNode.updatePriority returns a new leaf node without changing the old.', () => {
|
|
const x = new LeafNode('test', new LeafNode(42));
|
|
const y = x.updatePriority(new LeafNode(187));
|
|
|
|
// old node is the same.
|
|
expect(x.getValue()).to.equal('test');
|
|
expect(x.getPriority().val()).to.equal(42);
|
|
|
|
// new node has the new priority but the old value.
|
|
expect((y as any).getValue()).to.equal('test');
|
|
expect(y.getPriority().val()).to.equal(187);
|
|
});
|
|
|
|
it('LeafNode.updateImmediateChild returns a new children node.', () => {
|
|
const x = new LeafNode('test', new LeafNode(42));
|
|
const y = x.updateImmediateChild('test', new LeafNode('foo'));
|
|
|
|
expect(y.isLeafNode()).to.equal(false);
|
|
expect(y.getPriority().val()).to.equal(42);
|
|
expect((y.getImmediateChild('test') as LeafNode).getValue()).to.equal(
|
|
'foo'
|
|
);
|
|
});
|
|
|
|
it('LeafNode.getImmediateChild returns an empty node.', () => {
|
|
const x = new LeafNode('test');
|
|
expect(x.getImmediateChild('foo')).to.equal(ChildrenNode.EMPTY_NODE);
|
|
});
|
|
|
|
it('LeafNode.getChild returns an empty node.', () => {
|
|
const x = new LeafNode('test');
|
|
expect(x.getChild(new Path('foo/bar'))).to.equal(ChildrenNode.EMPTY_NODE);
|
|
});
|
|
|
|
it('ChildrenNode.updatePriority returns a new internal node without changing the old.', () => {
|
|
const x = ChildrenNode.EMPTY_NODE.updateImmediateChild(
|
|
'child',
|
|
new LeafNode(5)
|
|
);
|
|
const children = (x as any).children_;
|
|
const y = x.updatePriority(new LeafNode(17));
|
|
expect((y as any).children_).to.equal((x as any).children_);
|
|
expect((x as any).children_).to.equal(children);
|
|
expect(x.getPriority().val()).to.equal(null);
|
|
expect(y.getPriority().val()).to.equal(17);
|
|
});
|
|
|
|
it('ChildrenNode.updateImmediateChild returns a new internal node with the new child, without changing the old.', () => {
|
|
const children = new SortedMap<string, Node>(NAME_COMPARATOR);
|
|
const x = new ChildrenNode(
|
|
children,
|
|
ChildrenNode.EMPTY_NODE,
|
|
IndexMap.Default
|
|
);
|
|
const newValue = new LeafNode('new value');
|
|
const y = x.updateImmediateChild('test', newValue);
|
|
expect((x as any).children_).to.equal(children);
|
|
expect((y as any).children_.get('test')).to.equal(newValue);
|
|
});
|
|
|
|
it('ChildrenNode.updateChild returns a new internal node with the new child, without changing the old.', () => {
|
|
const children = new SortedMap<string, Node>(NAME_COMPARATOR);
|
|
const x = new ChildrenNode(
|
|
children,
|
|
ChildrenNode.EMPTY_NODE,
|
|
IndexMap.Default
|
|
);
|
|
const newValue = new LeafNode('new value');
|
|
const y = x.updateChild(new Path('test/foo'), newValue);
|
|
expect((x as any).children_).to.equal(children);
|
|
expect(y.getChild(new Path('test/foo'))).to.equal(newValue);
|
|
});
|
|
|
|
it('Node.hash() works correctly.', () => {
|
|
const node = nodeFromJSON({
|
|
intNode: 4,
|
|
doubleNode: 4.5623,
|
|
stringNode: 'hey guys',
|
|
boolNode: true
|
|
});
|
|
|
|
// !!!NOTE!!! These hashes must match what the server generates. If you change anything so these hashes change,
|
|
// make sure you change the corresponding server code.
|
|
expect(node.getImmediateChild('intNode').hash()).to.equal(
|
|
'eVih19a6ZDz3NL32uVBtg9KSgQY='
|
|
);
|
|
expect(node.getImmediateChild('doubleNode').hash()).to.equal(
|
|
'vf1CL0tIRwXXunHcG/irRECk3lY='
|
|
);
|
|
expect(node.getImmediateChild('stringNode').hash()).to.equal(
|
|
'CUNLXWpCVoJE6z7z1vE57lGaKAU='
|
|
);
|
|
expect(node.getImmediateChild('boolNode').hash()).to.equal(
|
|
'E5z61QM0lN/U2WsOnusszCTkR8M='
|
|
);
|
|
|
|
expect(node.hash()).to.equal('6Mc4jFmNdrLVIlJJjz2/MakTK9I=');
|
|
});
|
|
|
|
it('Node.hash() works correctly with priorities.', () => {
|
|
const node = nodeFromJSON({
|
|
root: { c: { '.value': 99, '.priority': 'abc' }, '.priority': 'def' }
|
|
});
|
|
|
|
expect(node.hash()).to.equal('Fm6tzN4CVEu5WxFDZUdTtqbTVaA=');
|
|
});
|
|
|
|
it('Node.hash() works correctly with number priorities.', () => {
|
|
const node = nodeFromJSON({
|
|
root: { c: { '.value': 99, '.priority': 42 }, '.priority': 3.14 }
|
|
});
|
|
|
|
expect(node.hash()).to.equal('B15QCqrzCxrI5zz1y00arWqFRFg=');
|
|
});
|
|
|
|
it('Node.hash() stress...', () => {
|
|
const node = nodeFromJSON({
|
|
a: -1.7976931348623157e308,
|
|
b: 1.7976931348623157e308,
|
|
c: 'unicode ✔ 🐵 🌴 x͢',
|
|
d: 3.1415926535897932384626433832795,
|
|
e: {
|
|
'.value': 12345678901234568,
|
|
'.priority': '🐵'
|
|
},
|
|
'✔': 'foo',
|
|
'.priority': '✔'
|
|
});
|
|
expect(node.getImmediateChild('a').hash()).to.equal(
|
|
'7HxgOBDEC92uQwhCuuvKA2rbXDA='
|
|
);
|
|
expect(node.getImmediateChild('b').hash()).to.equal(
|
|
'8R+ekVQmxs6ZWP0fdzFHxVeGnWo='
|
|
);
|
|
expect(node.getImmediateChild('c').hash()).to.equal(
|
|
'JoKoFUnbmg3/DlY70KaDWslfYPk='
|
|
);
|
|
expect(node.getImmediateChild('d').hash()).to.equal(
|
|
'Y41iC5+92GIqXfabOm33EanRI8s='
|
|
);
|
|
expect(node.getImmediateChild('e').hash()).to.equal(
|
|
'+E+Mxlqh5MhT+On05bjsZ6JaaxI='
|
|
);
|
|
expect(node.getImmediateChild('✔').hash()).to.equal(
|
|
'MRRL/+aA/uibaL//jghUpxXS/uY='
|
|
);
|
|
expect(node.hash()).to.equal('CyC0OU8GSkOAKnsPjheWtWC0Yxo=');
|
|
});
|
|
|
|
it('ChildrenNode.getPredecessorChild works correctly.', () => {
|
|
const node = nodeFromJSON({
|
|
d: true,
|
|
a: true,
|
|
g: true,
|
|
c: true,
|
|
e: true
|
|
});
|
|
|
|
// HACK: Pass null instead of the actual childNode, since it's not actually needed.
|
|
expect(node.getPredecessorChildName('a', null, DEFAULT_INDEX)).to.equal(
|
|
null
|
|
);
|
|
expect(node.getPredecessorChildName('c', null, DEFAULT_INDEX)).to.equal(
|
|
'a'
|
|
);
|
|
expect(node.getPredecessorChildName('d', null, DEFAULT_INDEX)).to.equal(
|
|
'c'
|
|
);
|
|
expect(node.getPredecessorChildName('e', null, DEFAULT_INDEX)).to.equal(
|
|
'd'
|
|
);
|
|
expect(node.getPredecessorChildName('g', null, DEFAULT_INDEX)).to.equal(
|
|
'e'
|
|
);
|
|
});
|
|
|
|
it('SortedChildrenNode.getPredecessorChild works correctly.', () => {
|
|
const node = nodeFromJSON({
|
|
d: { '.value': true, '.priority': 22 },
|
|
a: { '.value': true, '.priority': 25 },
|
|
g: { '.value': true, '.priority': 19 },
|
|
c: { '.value': true, '.priority': 23 },
|
|
e: { '.value': true, '.priority': 21 }
|
|
});
|
|
|
|
expect(
|
|
node.getPredecessorChildName(
|
|
'a',
|
|
node.getImmediateChild('a'),
|
|
DEFAULT_INDEX
|
|
)
|
|
).to.equal('c');
|
|
expect(
|
|
node.getPredecessorChildName(
|
|
'c',
|
|
node.getImmediateChild('c'),
|
|
DEFAULT_INDEX
|
|
)
|
|
).to.equal('d');
|
|
expect(
|
|
node.getPredecessorChildName(
|
|
'd',
|
|
node.getImmediateChild('d'),
|
|
DEFAULT_INDEX
|
|
)
|
|
).to.equal('e');
|
|
expect(
|
|
node.getPredecessorChildName(
|
|
'e',
|
|
node.getImmediateChild('e'),
|
|
DEFAULT_INDEX
|
|
)
|
|
).to.equal('g');
|
|
expect(
|
|
node.getPredecessorChildName(
|
|
'g',
|
|
node.getImmediateChild('g'),
|
|
DEFAULT_INDEX
|
|
)
|
|
).to.equal(null);
|
|
});
|
|
|
|
it('SortedChildrenNode.updateImmediateChild works correctly.', () => {
|
|
let node = nodeFromJSON({
|
|
d: { '.value': true, '.priority': 22 },
|
|
a: { '.value': true, '.priority': 25 },
|
|
g: { '.value': true, '.priority': 19 },
|
|
c: { '.value': true, '.priority': 23 },
|
|
e: { '.value': true, '.priority': 21 },
|
|
'.priority': 1000
|
|
});
|
|
|
|
node = node.updateImmediateChild('c', nodeFromJSON(false));
|
|
expect((node.getImmediateChild('c') as LeafNode).getValue()).to.equal(
|
|
false
|
|
);
|
|
expect(node.getImmediateChild('c').getPriority().val()).to.equal(null);
|
|
expect(node.getPriority().val()).to.equal(1000);
|
|
});
|
|
|
|
it('removing nodes correctly removes intermediate nodes with no remaining children', () => {
|
|
const json = { a: { b: { c: 1 } } };
|
|
const node = nodeFromJSON(json);
|
|
const newNode = node.updateChild(
|
|
new Path('a/b/c'),
|
|
ChildrenNode.EMPTY_NODE
|
|
);
|
|
expect(newNode.isEmpty()).to.equal(true);
|
|
});
|
|
|
|
it('removing nodes leaves intermediate nodes with other children', () => {
|
|
const json = { a: { b: { c: 1 }, d: 2 } };
|
|
const node = nodeFromJSON(json);
|
|
const newNode = node.updateChild(
|
|
new Path('a/b/c'),
|
|
ChildrenNode.EMPTY_NODE
|
|
);
|
|
expect(newNode.isEmpty()).to.equal(false);
|
|
expect(newNode.getChild(new Path('a/b/c')).isEmpty()).to.equal(true);
|
|
expect(newNode.getChild(new Path('a/d')).val()).to.equal(2);
|
|
});
|
|
|
|
it('removing nodes leaves other leaf nodes', () => {
|
|
const json = { a: { b: { c: 1, d: 2 } } };
|
|
const node = nodeFromJSON(json);
|
|
const newNode = node.updateChild(
|
|
new Path('a/b/c'),
|
|
ChildrenNode.EMPTY_NODE
|
|
);
|
|
expect(newNode.isEmpty()).to.equal(false);
|
|
expect(newNode.getChild(new Path('a/b/c')).isEmpty()).to.equal(true);
|
|
expect(newNode.getChild(new Path('a/b/d')).val()).to.equal(2);
|
|
});
|
|
|
|
it('removing nodes correctly removes the root', () => {
|
|
let json = null;
|
|
let node = nodeFromJSON(json);
|
|
let newNode = node.updateChild(newEmptyPath(), ChildrenNode.EMPTY_NODE);
|
|
expect(newNode.isEmpty()).to.equal(true);
|
|
|
|
json = { a: 1 };
|
|
node = nodeFromJSON(json);
|
|
newNode = node.updateChild(new Path('a'), ChildrenNode.EMPTY_NODE);
|
|
expect(newNode.isEmpty()).to.equal(true);
|
|
});
|
|
|
|
it('ignores null values', () => {
|
|
const json = { a: 1, b: null };
|
|
const node = nodeFromJSON(json);
|
|
expect((node as any).children_.get('b')).to.equal(null);
|
|
});
|
|
|
|
it('Leading zeroes in path are handled properly', () => {
|
|
const json = { '1': 1, '01': 2, '001': 3 };
|
|
const tree = nodeFromJSON(json);
|
|
expect(tree.getChild(new Path('1')).val()).to.equal(1);
|
|
expect(tree.getChild(new Path('01')).val()).to.equal(2);
|
|
expect(tree.getChild(new Path('001')).val()).to.equal(3);
|
|
});
|
|
|
|
it('Treats leading zeroes as objects, not array', () => {
|
|
const json = { '3': 1, '03': 2 };
|
|
const tree = nodeFromJSON(json);
|
|
const val = tree.val();
|
|
expect(val).to.deep.equal(json);
|
|
});
|
|
|
|
it("Updating empty children doesn't overwrite leaf node", () => {
|
|
const empty = ChildrenNode.EMPTY_NODE;
|
|
const node = nodeFromJSON('value');
|
|
expect(node).to.deep.equal(node.updateChild(new Path('.priority'), empty));
|
|
expect(node).to.deep.equal(node.updateChild(new Path('child'), empty));
|
|
expect(node).to.deep.equal(
|
|
node.updateChild(new Path('child/.priority'), empty)
|
|
);
|
|
expect(node).to.deep.equal(node.updateImmediateChild('child', empty));
|
|
expect(node).to.deep.equal(node.updateImmediateChild('.priority', empty));
|
|
});
|
|
});
|