blob: 922821fb0cadc593cf5d823f3568f040dacc727b [file] [log] [blame]
'use strict';
var PouchDB = require('../../packages/node_modules/pouchdb-for-coverage');
var should = require('chai').should();
var pouchCollate = PouchDB.collate;
var collate = pouchCollate.collate;
var normalizeKey = pouchCollate.normalizeKey;
var toIndexableString = pouchCollate.toIndexableString;
var parseIndexableString = pouchCollate.parseIndexableString;
function stringLexCompare(a, b) {
var aLen = a.length;
var bLen = b.length;
var i;
for (i = 0; i < aLen; i++) {
if (i === bLen) {
// b is shorter substring of a
return 1;
}
var aChar = a.charAt(i);
var bChar = b.charAt(i);
if (aChar !== bChar) {
return aChar < bChar ? -1 : 1;
}
}
if (aLen < bLen) {
// a is shorter substring of b
return -1;
}
return 0;
}
/*
* returns the decimal form for the given integer, i.e. writes
* out all the digits (in base-10) instead of using scientific notation
*/
function intToDecimalForm(int) {
var isNeg = int < 0;
var result = '';
do {
var remainder = isNeg ? -Math.ceil(int % 10) : Math.floor(int % 10);
result = remainder + result;
int = isNeg ? Math.ceil(int / 10) : Math.floor(int / 10);
} while (int);
if (isNeg && result !== '0') {
result = '-' + result;
}
return result;
}
var verifyLexicalKeysSort = function (keys) {
var lexical = keys.map(function (key) {
return [key, pouchCollate.toIndexableString(key)];
});
lexical.sort(function (a, b) {
return stringLexCompare(a[1], b[1]);
});
keys.sort(pouchCollate.collate);
keys.forEach(function (expected, i) {
var actual = lexical[i][0];
should.equal(actual, expected, 'expect ' + JSON.stringify(actual) +
' is ' + JSON.stringify(expected));
});
};
describe('test.collate.js', function () {
var a = {
array: [1, 2, 3],
bool: true,
string: '123',
object: {
a: 3,
b: 2
},
number: 1
};
var b = {
array: ['a', 'b'],
bool: false,
string: 'ab',
object: {
c: 1,
b: 3
},
number: 2
};
var c = {
object: {
a: 1,
b: 2,
c: 3
},
array: [1, 2]
};
it('compare array to itself', function () {
collate(a.array, a.array).should.equal(0);
collate(b.array, b.array).should.equal(0);
collate(c.array, c.array).should.equal(0);
});
it('compare boolean to itself', function () {
collate(a.bool, a.bool).should.equal(0);
collate(b.bool, b.bool).should.equal(0);
});
it('compare string to itself', function () {
collate(a.string, a.string).should.equal(0);
collate(b.string, b.string).should.equal(0);
});
it('compare number to itself', function () {
collate(a.number, a.number).should.equal(0);
collate(b.number, b.number).should.equal(0);
});
it('compare null to itself', function () {
collate(null, null).should.equal(0);
});
it('compare object to itself', function () {
collate(a.object, a.object).should.equal(0);
collate(b.object, b.object).should.equal(0);
collate(c.object, c.object).should.equal(0);
});
it('compare array to array', function () {
collate(a.array, b.array).should.equal(-1);
collate(b.array, a.array).should.equal(1);
collate(c.array, b.array).should.equal(-1);
collate(b.array, c.array).should.equal(1);
collate(a.array, c.array).should.equal(1);
collate(c.array, a.array).should.equal(-1);
});
it('compare array to array', function () {
collate([a.array], [b.array]).should.equal(-1);
collate([b.array], [a.array]).should.equal(1);
});
it('compare boolean to boolean', function () {
collate(a.bool, b.bool).should.equal(1);
collate(b.bool, a.bool).should.equal(-1);
});
it('compare string to string', function () {
collate(a.string, b.string).should.equal(-1);
collate(b.string, a.string).should.equal(1);
});
it('compare number to number', function () {
collate(a.number, b.number).should.equal(-1);
collate(b.number, a.number).should.equal(1);
});
it('compare object to object', function () {
collate(a.object, b.object).should.equal(-1);
collate(b.object, a.object).should.equal(1);
collate(c.object, b.object).should.equal(-1);
collate(b.object, c.object).should.equal(1);
collate(c.object, a.object).should.be.below(0);
collate(a.object, c.object).should.be.above(0);
});
it('objects differing only in num of keys', function () {
collate({1: 1}, {1: 1, 2: 2}).should.equal(-1);
collate({1: 1, 2: 2}, {1: 1}).should.equal(1);
});
it('compare number to null', function () {
collate(a.number, null).should.be.above(0);
});
it('compare number to function', function () {
collate(a.number, function () {
}).should.not.equal(collate(a.number, function () {}));
collate(b.number, function () {
}).should.not.equal(collate(b.number, function () {}));
collate(function () {
}, a.number).should.not.equal(collate(function () {}, a.number));
collate(function () {
}, b.number).should.not.equal(collate(function () {}, b.number));
});
});
describe('normalizeKey', function () {
it('verify key normalizations', function () {
var normalizations = [
[null, null],
[NaN, null],
[undefined, null],
[Infinity, null],
[-Infinity, null],
['', ''],
['foo', 'foo'],
['0', '0'],
['1', '1'],
[0, 0],
[1, 1],
[Number.MAX_VALUE, Number.MAX_VALUE],
[new Date('1982-11-30T00:00:00.000Z'), '1982-11-30T00:00:00.000Z'] // Thriller release date
];
normalizations.forEach(function (normalization) {
var original = normalization[0];
var expected = normalization[1];
var normalized = normalizeKey(original);
var message = 'check normalization of ' + JSON.stringify(original) +
' to ' + JSON.stringify(expected) +
', got ' + JSON.stringify(normalized);
should.equal(normalized, expected, message);
});
});
});
describe('indexableString', function () {
it('verify intToDecimalForm', function () {
intToDecimalForm(0).should.equal('0');
intToDecimalForm(Number.MIN_VALUE).should.equal('0');
intToDecimalForm(-Number.MIN_VALUE).should.equal('0');
var maxValueStr = '1797693134862316800886484642206468426866682428440286464' +
'42228680066046004606080400844208228060084840044686866242482868202680268' +
'82040288406280040662242886466688240606642242682208668042640440204020242' +
'48802248082808208888442866208026644060866608420408868240026826626668642' +
'46642840408646468824200860804260804068888';
intToDecimalForm(Number.MAX_VALUE).should.equal(maxValueStr);
intToDecimalForm(-Number.MAX_VALUE).should.equal('-' + maxValueStr);
var simpleNums = [-3000, 3000, 322, 2308, -32, -1, 0, 1, 2, -2, -10, 10, -100, 100];
simpleNums.forEach(function (simpleNum) {
intToDecimalForm(simpleNum).should.equal(simpleNum.toString());
});
});
it('verify toIndexableString()', function () {
var keys = [
null,
false,
true,
-Number.MAX_VALUE,
-300,
-200,
-100,
-10,
-2.5,
-2,
-1.5,
-1,
-0.5,
-0.0001,
-Number.MIN_VALUE,
0,
Number.MIN_VALUE,
0.0001,
0.1,
0.5,
1,
1.5,
2,
3,
10,
15,
100,
200,
300,
Number.MAX_VALUE,
'',
'1',
'10',
'100',
'2',
'20',
'[]',
//'é',
'foo',
'mo',
'moe',
//'moé',
//'moët et chandon',
'moz',
'mozilla',
'mozilla with a super long string see how far it can go',
'mozzy',
[],
[ null ],
[ null, null ],
[ null, 'foo' ],
[ false ],
[ false, 100 ],
[ true ],
[ true, 100 ],
[ 0 ],
[ 0, null ],
[ 0, 1 ],
[ 0, '' ],
[ 0, 'foo' ],
[ '', '' ],
[ 'foo' ],
[ 'foo', 1 ],
{},
{ '0': null },
{ '0': false },
{ '0': true },
{ '0': 0 },
{ '0': 1 },
{ '0': 'bar' },
{ '0': 'foo' },
{ '0': 'foo', '1': false },
{ '0': 'foo', '1': true },
{ '0': 'foo', '1': 0 },
{ '0': 'foo', '1': '0' },
{ '0': 'foo', '1': 'bar' },
{ '0': 'quux' },
{ '1': 'foo' }
//{ '1': 'foo', '0' : 'foo' } // key order actually matters, but node sorts them
];
verifyLexicalKeysSort(keys);
});
it('verify toIndexableString()', function () {
var keys = [
['test', 'test'],
['test\u0000']
];
verifyLexicalKeysSort(keys);
});
it('verify deep normalization', function () {
var a = {
list : [undefined, '1982-11-30T00:00:00.000Z'],
obj : {
foo: null,
date: '1982-11-30T00:00:00.000Z'
},
brokenList : [undefined, 1, 2, undefined, 3, 4, 5, undefined]
};
var b = {
list : [null, new Date('1982-11-30T00:00:00.000Z')],
obj : {
foo: NaN,
date: new Date('1982-11-30T00:00:00.000Z')
},
ignoredParam : undefined,
brokenList : [null, 1, 2, null, 3, 4, 5, null]
};
// sanity check
JSON.stringify(a).should.equal(JSON.stringify(b), 'stringify a,b');
toIndexableString(a).should.equal(toIndexableString(b), 'string a,b');
toIndexableString(a).should.equal(toIndexableString(b), 'string a,a');
toIndexableString(b).should.equal(toIndexableString(b), 'string b,b');
normalizeKey(a).should.deep.equal(normalizeKey(b), 'normalize a,b');
normalizeKey(a).should.deep.equal(normalizeKey(a), 'normalize a,a');
normalizeKey(b).should.deep.equal(normalizeKey(b), 'normalize b,b');
collate(a, b).should.equal(0, 'collate a,b');
collate(a, a).should.equal(0, 'collate a,a');
collate(b, b).should.equal(0, 'collate b,b');
});
it('verify parseIndexableString', function () {
var keys = [null, false, true, 0, 1, -1, 9, -9, 10, -10, 0.1, -0.1, -0.01,
100, 200, 20, -20, -200, -30, Number.MAX_VALUE, Number.MIN_VALUE,
'foo', '', '\u0000', '\u0001', '\u0002', [1], {foo: true},
{foo: 'bar', 'baz': 'quux', foobaz: {bar: 'bar', baz: 'baz', quux: {foo: 'bar'}}},
{foo: {bar: true}},
[{foo: 'bar'}, {bar: 'baz'}, {}, ['foo', 'bar', 'baz']],
[[[['foo']], [], [['bar']]]],
-Number.MAX_VALUE,
-300,
-200,
-100,
-10,
-2.5,
-2,
-1.5,
-1,
-0.5,
-0.0001,
-Number.MIN_VALUE,
0,
Number.MIN_VALUE,
0.0001,
0.1,
0.5,
1,
1.5,
2,
3,
10,
15,
100,
200,
300,
Number.MAX_VALUE,
'',
'1',
'10',
'100',
'2',
'20',
'[]',
//'é',
'foo',
'mo',
'moe',
//'moé',
//'moët et chandon',
'moz',
'mozilla',
'mozilla with a super long string see how far it can go',
'mozzy',
[],
[ null ],
[ null, null ],
[ null, 'foo' ],
[ false ],
[ false, 100 ],
[ true ],
[ true, 100 ],
[ 0 ],
[ 0, null ],
[ 0, 1 ],
[ 0, '' ],
[ 0, 'foo' ],
[ '', '' ],
[ 'foo' ],
[ 'foo', 1 ],
{},
{ '0': null },
{ '0': false },
{ '0': true },
{ '0': 0 },
{ '0': 1 },
{ '0': 'bar' },
{ '0': 'foo' },
{ '0': 'foo', '1': false },
{ '0': 'foo', '1': true },
{ '0': 'foo', '1': 0 },
{ '0': 'foo', '1': '0' },
{ '0': 'foo', '1': 'bar' },
{ '0': 'quux' },
{ '1': 'foo' }
];
keys.forEach(function (key) {
var indexableString = toIndexableString(key);
JSON.stringify(parseIndexableString(indexableString)).should.equal(
JSON.stringify(key), 'check parseIndexableString for key: ' + key +
'(indexable string is: ' + indexableString + ')');
});
});
it('throws error in parseIndexableString on invalid input', function () {
try {
parseIndexableString('');
should.fail("didn't expect to parse correctly");
} catch (err) {
should.exist(err);
}
});
});