Files

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));
});
});