UNPKG

array-keyed-map

Version:

a map from arrays of values to values

378 lines (306 loc) 7.57 kB
const test = require('tape') const AKM = require('./main.js') const assert = require('assert') test('empty', (t) => { const p = new AKM() t.ok(typeof p.set === 'function', 'set is a function') t.ok(typeof p.get === 'function', 'get is a function') t.same( p.get(['a', 'b']), undefined, 'getting path gives undefined') t.end() }) test('set/get path len 0', (t) => { const p = new AKM() p.set([], true) t.same(p.get([]), true) t.end() }) test('set/get path len 1', (t) => { const p = new AKM() p.set(['a'], true) t.same(p.get(['a']), true) t.end() }) test('set/get path len 2', (t) => { const p = new AKM() p.set(['a', 'b'], true) t.same(p.get(['a', 'b']), true) t.end() }) test('empty strings are ok', (t) => { const p = new AKM() p.set(['', ''], true) t.same(p.get(['']), undefined) t.same(p.get(['', '']), true) t.end() }) test('set returns self, allowing chaining', (t) => { const p = new AKM() p .set(['a'], true) .set(['a', 'b'], true) t.same(p.get(['a']), true) t.same(p.get(['a', 'b']), true) t.end() }) test('any objects work as keys or values', (t) => { const objects = [ new Map(), new WeakMap(), new Set(), {}, ['a'], Symbol('test'), Math.PI, 'hello world', false, 0, () => {} ] const p = new AKM() const str = (x) => Object.prototype.toString.call(x).slice(8, -1) // Try all combinations of two things from the pool of objects as array elements, and values objects.forEach((k1) => { objects.forEach((k2) => { objects.forEach((value) => { p.set([k1, k2], value) t.same( p.get([k1, k2]), value, `[${str(k1)}, ${str(k2)}] => ${str(value)}`) }) }) }) // Try all of them as length-1 paths objects.forEach((k) => { objects.forEach((value) => { p.set([k], value) t.same( p.get([k]), value, `[${str(k)}] => ${str(value)}`) }) }) // The last values set to the length-2 paths should all still be set const shouldBeValue = objects[objects.length - 1] objects.forEach((k1) => { objects.forEach((k2) => { t.same( p.get([k1, k2]), shouldBeValue, `[${str(k1)}, ${str(k2)}] => still ${str(shouldBeValue)}`) }) }) t.end() }) test('set and delete empty path', (t) => { const p = new AKM() p.set([], true) p.delete([]) t.same(p.get([]), undefined) p.set([], true) // Shouldn't throw t.end() }) test('attempting to delete non-existent path', (t) => { const p = new AKM() t.same(p.delete([]), false) t.same(p.size, 0) t.end() }) test('delete longer paths', (t) => { const p = new AKM() const paths = [ [], ['a'], ['a', 'b'], ['a', 'b', 'c'] ] paths.forEach((path) => { p.set(path, true) p.delete(path) t.same(p.get(path), undefined, `${JSON.stringify(path)} can be deleted`) }) t.end() }) test('deleting longer paths doesn\'t affect prefixes', (t) => { const p = new AKM() p.set(['a', 'b'], 'ab') p.set(['a'], 'a') p.delete(['a', 'b']) t.same(p.get(['a']), 'a') t.end() }) test('deleting shorter paths doesn\'t affect longer continuations', (t) => { const p = new AKM() p.set(['a', 'b'], 'ab') p.set(['a'], 'a') p.delete(['a']) t.same(p.get(['a', 'b']), 'ab') t.end() }) test('delete return value', t => { const p = new AKM() p.set(['x'], 'x') t.same(p.delete(['x']), true, 'delete returns true when entry existed') t.same(p.delete(['x']), false, 'delete returns false when no entry existed') t.end() }) test('has', (t) => { const p = new AKM() p.set(['a', 'b'], 'ab') t.same(p.has(['a', 'b']), true) t.same(p.has(['a']), false) p.set(['a'], 'a') t.same(p.has(['a']), true) t.end() }) test('size', (t) => { const p = new AKM() p.set(['a', 'b', 'c'], 'abc') t.same(p.size, 1) p.set(['a'], 'a') t.same(p.size, 2) p.set(['a', 'd'], 'ad') t.same(p.size, 3) p.delete(['a']) t.same(p.size, 2) p.size = 0 t.same(p.size, 2) t.end() }) test('clear', (t) => { const p = new AKM() p.set(['a', 'b', 'c'], 'abc') p.set(['a'], 'a') p.set(['a', 'd'], 'ad') t.same(p.size, 3) p.clear() t.same(p.size, 0) t.same(p.has(['a', 'b', 'c']), false) t.same(p.has(['a']), false) t.same(p.has(['a', 'd']), false) t.end() }) test('iterators', (t) => { const p = new AKM() const key1 = [] const value1 = 'empty path' p.set(key1, value1) const key2 = ['b'] const value2 = 'b' p.set(key2, value2) const key3 = ['a'] const value3 = 'a' p.set(key3, value3) const key4 = ['b', 'a'] const value4 = 'ba' p.set(key4, value4) // We don't guarantee any particular order in which entries are emitted by // iterators. So to make this test work even if the implementation changes, // we specifically disregard order when checking them. function containsAllOf (t, array, expectedItems) { t.same( array.length, expectedItems.length, 'Iterator has right number of elements') expectedItems.forEach(expectedItem => { const wasFound = array.some(actualItem => { try { assert.deepStrictEqual(actualItem, expectedItem) } catch (e) { if (e instanceof assert.AssertionError) return false else throw e } return true }) t.ok(wasFound, `Iterator contains ${JSON.stringify(expectedItem)}`) }) } test('entries', (t) => { containsAllOf(t, [...p.entries()], [ [key1, value1], [key2, value2], [key3, value3], [key4, value4] ]) t.end() }) test('@@iterator', (t) => { containsAllOf(t, Array.from(p), [ [key1, value1], [key2, value2], [key3, value3], [key4, value4] ]) t.end() }) test('keys', (t) => { containsAllOf(t, [...p.keys()], [ key1, key2, key3, key4 ]) t.end() }) test('values', (t) => { containsAllOf(t, [...p.values()], [ value1, value2, value3, value4 ]) t.end() }) test('forEach', (t) => { const kvs = [] const thisValue = {} const forEachReturnValue = p.forEach( function () { kvs.push({ thisValue: this, args: Array.from(arguments) }) }, thisValue) t.same(forEachReturnValue, undefined) containsAllOf(t, kvs, [ { thisValue, args: [value1, key1, p] }, { thisValue, args: [value2, key2, p] }, { thisValue, args: [value4, key4, p] }, { thisValue, args: [value3, key3, p] } ]) t.end() }) t.end() }) test('hasPrefix', (t) => { // Even the empty map has the empty prefix const pEmpty = new AKM() t.ok(pEmpty.hasPrefix([])) // - - - const p = new AKM() p.set(['a', 'b', 'c'], 'abc') p.set(['c'], 'c') t.ok(p.hasPrefix([])) t.ok(p.hasPrefix(['a'])) t.ok(p.hasPrefix(['a', 'b'])) t.ok(p.hasPrefix(['a', 'b', 'c'])) t.ok(!p.hasPrefix(['a', 'b', 'c', 'd'])) t.ok(!p.hasPrefix(['b'])) t.ok(p.hasPrefix(['c'])) t.end() }) test('constructor property', (t) => { const p = new AKM() t.same(p.constructor, AKM) t.end() }) test('@@toStringTag property', (t) => { const p = new AKM() t.same(Object.prototype.toString.call(p), '[object ArrayKeyedMap]') t.end() }) test('construct copy by passing entries of previous to constructor', (t) => { const p1 = new AKM() p1.set(['a', 'b'], 'ab') p1.set(['c'], 'c') const p2 = new AKM(p1.entries()) t.same(p2.get(['a', 'b']), 'ab') t.same(p2.get(['c']), 'c') t.end() })