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