Spaces:
Build error
Build error
/* globals suite test */ | |
const assert = require('assert') | |
const path = require('path') | |
const { exec } = require('child_process') | |
const pkg = require('../package.json') | |
const flat = require('../index') | |
const flatten = flat.flatten | |
const unflatten = flat.unflatten | |
const primitives = { | |
String: 'good morning', | |
Number: 1234.99, | |
Boolean: true, | |
Date: new Date(), | |
null: null, | |
undefined: undefined | |
} | |
suite('Flatten Primitives', function () { | |
Object.keys(primitives).forEach(function (key) { | |
const value = primitives[key] | |
test(key, function () { | |
assert.deepStrictEqual(flatten({ | |
hello: { | |
world: value | |
} | |
}), { | |
'hello.world': value | |
}) | |
}) | |
}) | |
}) | |
suite('Unflatten Primitives', function () { | |
Object.keys(primitives).forEach(function (key) { | |
const value = primitives[key] | |
test(key, function () { | |
assert.deepStrictEqual(unflatten({ | |
'hello.world': value | |
}), { | |
hello: { | |
world: value | |
} | |
}) | |
}) | |
}) | |
}) | |
suite('Flatten', function () { | |
test('Nested once', function () { | |
assert.deepStrictEqual(flatten({ | |
hello: { | |
world: 'good morning' | |
} | |
}), { | |
'hello.world': 'good morning' | |
}) | |
}) | |
test('Nested twice', function () { | |
assert.deepStrictEqual(flatten({ | |
hello: { | |
world: { | |
again: 'good morning' | |
} | |
} | |
}), { | |
'hello.world.again': 'good morning' | |
}) | |
}) | |
test('Multiple Keys', function () { | |
assert.deepStrictEqual(flatten({ | |
hello: { | |
lorem: { | |
ipsum: 'again', | |
dolor: 'sit' | |
} | |
}, | |
world: { | |
lorem: { | |
ipsum: 'again', | |
dolor: 'sit' | |
} | |
} | |
}), { | |
'hello.lorem.ipsum': 'again', | |
'hello.lorem.dolor': 'sit', | |
'world.lorem.ipsum': 'again', | |
'world.lorem.dolor': 'sit' | |
}) | |
}) | |
test('Custom Delimiter', function () { | |
assert.deepStrictEqual(flatten({ | |
hello: { | |
world: { | |
again: 'good morning' | |
} | |
} | |
}, { | |
delimiter: ':' | |
}), { | |
'hello:world:again': 'good morning' | |
}) | |
}) | |
test('Empty Objects', function () { | |
assert.deepStrictEqual(flatten({ | |
hello: { | |
empty: { | |
nested: {} | |
} | |
} | |
}), { | |
'hello.empty.nested': {} | |
}) | |
}) | |
if (typeof Buffer !== 'undefined') { | |
test('Buffer', function () { | |
assert.deepStrictEqual(flatten({ | |
hello: { | |
empty: { | |
nested: Buffer.from('test') | |
} | |
} | |
}), { | |
'hello.empty.nested': Buffer.from('test') | |
}) | |
}) | |
} | |
if (typeof Uint8Array !== 'undefined') { | |
test('typed arrays', function () { | |
assert.deepStrictEqual(flatten({ | |
hello: { | |
empty: { | |
nested: new Uint8Array([1, 2, 3, 4]) | |
} | |
} | |
}), { | |
'hello.empty.nested': new Uint8Array([1, 2, 3, 4]) | |
}) | |
}) | |
} | |
test('Custom Depth', function () { | |
assert.deepStrictEqual(flatten({ | |
hello: { | |
world: { | |
again: 'good morning' | |
} | |
}, | |
lorem: { | |
ipsum: { | |
dolor: 'good evening' | |
} | |
} | |
}, { | |
maxDepth: 2 | |
}), { | |
'hello.world': { | |
again: 'good morning' | |
}, | |
'lorem.ipsum': { | |
dolor: 'good evening' | |
} | |
}) | |
}) | |
test('Transformed Keys', function () { | |
assert.deepStrictEqual(flatten({ | |
hello: { | |
world: { | |
again: 'good morning' | |
} | |
}, | |
lorem: { | |
ipsum: { | |
dolor: 'good evening' | |
} | |
} | |
}, { | |
transformKey: function (key) { | |
return '__' + key + '__' | |
} | |
}), { | |
'__hello__.__world__.__again__': 'good morning', | |
'__lorem__.__ipsum__.__dolor__': 'good evening' | |
}) | |
}) | |
test('Should keep number in the left when object', function () { | |
assert.deepStrictEqual(flatten({ | |
hello: { | |
'0200': 'world', | |
'0500': 'darkness my old friend' | |
} | |
}), { | |
'hello.0200': 'world', | |
'hello.0500': 'darkness my old friend' | |
}) | |
}) | |
}) | |
suite('Unflatten', function () { | |
test('Nested once', function () { | |
assert.deepStrictEqual({ | |
hello: { | |
world: 'good morning' | |
} | |
}, unflatten({ | |
'hello.world': 'good morning' | |
})) | |
}) | |
test('Nested twice', function () { | |
assert.deepStrictEqual({ | |
hello: { | |
world: { | |
again: 'good morning' | |
} | |
} | |
}, unflatten({ | |
'hello.world.again': 'good morning' | |
})) | |
}) | |
test('Multiple Keys', function () { | |
assert.deepStrictEqual({ | |
hello: { | |
lorem: { | |
ipsum: 'again', | |
dolor: 'sit' | |
} | |
}, | |
world: { | |
greet: 'hello', | |
lorem: { | |
ipsum: 'again', | |
dolor: 'sit' | |
} | |
} | |
}, unflatten({ | |
'hello.lorem.ipsum': 'again', | |
'hello.lorem.dolor': 'sit', | |
'world.lorem.ipsum': 'again', | |
'world.lorem.dolor': 'sit', | |
world: { greet: 'hello' } | |
})) | |
}) | |
test('nested objects do not clobber each other when a.b inserted before a', function () { | |
const x = {} | |
x['foo.bar'] = { t: 123 } | |
x.foo = { p: 333 } | |
assert.deepStrictEqual(unflatten(x), { | |
foo: { | |
bar: { | |
t: 123 | |
}, | |
p: 333 | |
} | |
}) | |
}) | |
test('Custom Delimiter', function () { | |
assert.deepStrictEqual({ | |
hello: { | |
world: { | |
again: 'good morning' | |
} | |
} | |
}, unflatten({ | |
'hello world again': 'good morning' | |
}, { | |
delimiter: ' ' | |
})) | |
}) | |
test('Overwrite', function () { | |
assert.deepStrictEqual({ | |
travis: { | |
build: { | |
dir: '/home/travis/build/kvz/environmental' | |
} | |
} | |
}, unflatten({ | |
travis: 'true', | |
travis_build_dir: '/home/travis/build/kvz/environmental' | |
}, { | |
delimiter: '_', | |
overwrite: true | |
})) | |
}) | |
test('Transformed Keys', function () { | |
assert.deepStrictEqual(unflatten({ | |
'__hello__.__world__.__again__': 'good morning', | |
'__lorem__.__ipsum__.__dolor__': 'good evening' | |
}, { | |
transformKey: function (key) { | |
return key.substring(2, key.length - 2) | |
} | |
}), { | |
hello: { | |
world: { | |
again: 'good morning' | |
} | |
}, | |
lorem: { | |
ipsum: { | |
dolor: 'good evening' | |
} | |
} | |
}) | |
}) | |
test('Messy', function () { | |
assert.deepStrictEqual({ | |
hello: { world: 'again' }, | |
lorem: { ipsum: 'another' }, | |
good: { | |
morning: { | |
hash: { | |
key: { | |
nested: { | |
deep: { | |
and: { | |
even: { | |
deeper: { still: 'hello' } | |
} | |
} | |
} | |
} | |
} | |
}, | |
again: { testing: { this: 'out' } } | |
} | |
} | |
}, unflatten({ | |
'hello.world': 'again', | |
'lorem.ipsum': 'another', | |
'good.morning': { | |
'hash.key': { | |
'nested.deep': { | |
'and.even.deeper.still': 'hello' | |
} | |
} | |
}, | |
'good.morning.again': { | |
'testing.this': 'out' | |
} | |
})) | |
}) | |
suite('Overwrite + non-object values in key positions', function () { | |
test('non-object keys + overwrite should be overwritten', function () { | |
assert.deepStrictEqual(flat.unflatten({ a: null, 'a.b': 'c' }, { overwrite: true }), { a: { b: 'c' } }) | |
assert.deepStrictEqual(flat.unflatten({ a: 0, 'a.b': 'c' }, { overwrite: true }), { a: { b: 'c' } }) | |
assert.deepStrictEqual(flat.unflatten({ a: 1, 'a.b': 'c' }, { overwrite: true }), { a: { b: 'c' } }) | |
assert.deepStrictEqual(flat.unflatten({ a: '', 'a.b': 'c' }, { overwrite: true }), { a: { b: 'c' } }) | |
}) | |
test('overwrite value should not affect undefined keys', function () { | |
assert.deepStrictEqual(flat.unflatten({ a: undefined, 'a.b': 'c' }, { overwrite: true }), { a: { b: 'c' } }) | |
assert.deepStrictEqual(flat.unflatten({ a: undefined, 'a.b': 'c' }, { overwrite: false }), { a: { b: 'c' } }) | |
}) | |
test('if no overwrite, should ignore nested values under non-object key', function () { | |
assert.deepStrictEqual(flat.unflatten({ a: null, 'a.b': 'c' }), { a: null }) | |
assert.deepStrictEqual(flat.unflatten({ a: 0, 'a.b': 'c' }), { a: 0 }) | |
assert.deepStrictEqual(flat.unflatten({ a: 1, 'a.b': 'c' }), { a: 1 }) | |
assert.deepStrictEqual(flat.unflatten({ a: '', 'a.b': 'c' }), { a: '' }) | |
}) | |
}) | |
suite('.safe', function () { | |
test('Should protect arrays when true', function () { | |
assert.deepStrictEqual(flatten({ | |
hello: [ | |
{ world: { again: 'foo' } }, | |
{ lorem: 'ipsum' } | |
], | |
another: { | |
nested: [{ array: { too: 'deep' } }] | |
}, | |
lorem: { | |
ipsum: 'whoop' | |
} | |
}, { | |
safe: true | |
}), { | |
hello: [ | |
{ world: { again: 'foo' } }, | |
{ lorem: 'ipsum' } | |
], | |
'lorem.ipsum': 'whoop', | |
'another.nested': [{ array: { too: 'deep' } }] | |
}) | |
}) | |
test('Should not protect arrays when false', function () { | |
assert.deepStrictEqual(flatten({ | |
hello: [ | |
{ world: { again: 'foo' } }, | |
{ lorem: 'ipsum' } | |
] | |
}, { | |
safe: false | |
}), { | |
'hello.0.world.again': 'foo', | |
'hello.1.lorem': 'ipsum' | |
}) | |
}) | |
test('Empty objects should not be removed', function () { | |
assert.deepStrictEqual(unflatten({ | |
foo: [], | |
bar: {} | |
}), { foo: [], bar: {} }) | |
}) | |
}) | |
suite('.object', function () { | |
test('Should create object instead of array when true', function () { | |
const unflattened = unflatten({ | |
'hello.you.0': 'ipsum', | |
'hello.you.1': 'lorem', | |
'hello.other.world': 'foo' | |
}, { | |
object: true | |
}) | |
assert.deepStrictEqual({ | |
hello: { | |
you: { | |
0: 'ipsum', | |
1: 'lorem' | |
}, | |
other: { world: 'foo' } | |
} | |
}, unflattened) | |
assert(!Array.isArray(unflattened.hello.you)) | |
}) | |
test('Should create object instead of array when nested', function () { | |
const unflattened = unflatten({ | |
hello: { | |
'you.0': 'ipsum', | |
'you.1': 'lorem', | |
'other.world': 'foo' | |
} | |
}, { | |
object: true | |
}) | |
assert.deepStrictEqual({ | |
hello: { | |
you: { | |
0: 'ipsum', | |
1: 'lorem' | |
}, | |
other: { world: 'foo' } | |
} | |
}, unflattened) | |
assert(!Array.isArray(unflattened.hello.you)) | |
}) | |
test('Should keep the zero in the left when object is true', function () { | |
const unflattened = unflatten({ | |
'hello.0200': 'world', | |
'hello.0500': 'darkness my old friend' | |
}, { | |
object: true | |
}) | |
assert.deepStrictEqual({ | |
hello: { | |
'0200': 'world', | |
'0500': 'darkness my old friend' | |
} | |
}, unflattened) | |
}) | |
test('Should not create object when false', function () { | |
const unflattened = unflatten({ | |
'hello.you.0': 'ipsum', | |
'hello.you.1': 'lorem', | |
'hello.other.world': 'foo' | |
}, { | |
object: false | |
}) | |
assert.deepStrictEqual({ | |
hello: { | |
you: ['ipsum', 'lorem'], | |
other: { world: 'foo' } | |
} | |
}, unflattened) | |
assert(Array.isArray(unflattened.hello.you)) | |
}) | |
}) | |
if (typeof Buffer !== 'undefined') { | |
test('Buffer', function () { | |
assert.deepStrictEqual(unflatten({ | |
'hello.empty.nested': Buffer.from('test') | |
}), { | |
hello: { | |
empty: { | |
nested: Buffer.from('test') | |
} | |
} | |
}) | |
}) | |
} | |
if (typeof Uint8Array !== 'undefined') { | |
test('typed arrays', function () { | |
assert.deepStrictEqual(unflatten({ | |
'hello.empty.nested': new Uint8Array([1, 2, 3, 4]) | |
}), { | |
hello: { | |
empty: { | |
nested: new Uint8Array([1, 2, 3, 4]) | |
} | |
} | |
}) | |
}) | |
} | |
test('should not pollute prototype', function () { | |
unflatten({ | |
'__proto__.polluted': true | |
}) | |
unflatten({ | |
'prefix.__proto__.polluted': true | |
}) | |
unflatten({ | |
'prefix.0.__proto__.polluted': true | |
}) | |
assert.notStrictEqual({}.polluted, true) | |
}) | |
}) | |
suite('Arrays', function () { | |
test('Should be able to flatten arrays properly', function () { | |
assert.deepStrictEqual({ | |
'a.0': 'foo', | |
'a.1': 'bar' | |
}, flatten({ | |
a: ['foo', 'bar'] | |
})) | |
}) | |
test('Should be able to revert and reverse array serialization via unflatten', function () { | |
assert.deepStrictEqual({ | |
a: ['foo', 'bar'] | |
}, unflatten({ | |
'a.0': 'foo', | |
'a.1': 'bar' | |
})) | |
}) | |
test('Array typed objects should be restored by unflatten', function () { | |
assert.strictEqual( | |
Object.prototype.toString.call(['foo', 'bar']) | |
, Object.prototype.toString.call(unflatten({ | |
'a.0': 'foo', | |
'a.1': 'bar' | |
}).a) | |
) | |
}) | |
test('Do not include keys with numbers inside them', function () { | |
assert.deepStrictEqual(unflatten({ | |
'1key.2_key': 'ok' | |
}), { | |
'1key': { | |
'2_key': 'ok' | |
} | |
}) | |
}) | |
}) | |
suite('Order of Keys', function () { | |
test('Order of keys should not be changed after round trip flatten/unflatten', function () { | |
const obj = { | |
b: 1, | |
abc: { | |
c: [{ | |
d: 1, | |
bca: 1, | |
a: 1 | |
}] | |
}, | |
a: 1 | |
} | |
const result = unflatten( | |
flatten(obj) | |
) | |
assert.deepStrictEqual(Object.keys(obj), Object.keys(result)) | |
assert.deepStrictEqual(Object.keys(obj.abc), Object.keys(result.abc)) | |
assert.deepStrictEqual(Object.keys(obj.abc.c[0]), Object.keys(result.abc.c[0])) | |
}) | |
}) | |
suite('CLI', function () { | |
test('can take filename', function (done) { | |
const cli = path.resolve(__dirname, '..', pkg.bin) | |
const pkgJSON = path.resolve(__dirname, '..', 'package.json') | |
exec(`${cli} ${pkgJSON}`, (err, stdout, stderr) => { | |
assert.ifError(err) | |
assert.strictEqual(stdout.trim(), JSON.stringify(flatten(pkg), null, 2)) | |
done() | |
}) | |
}) | |
test('exits with usage if no file', function (done) { | |
const cli = path.resolve(__dirname, '..', pkg.bin) | |
const pkgJSON = path.resolve(__dirname, '..', 'package.json') | |
exec(`${cli} ${pkgJSON}`, (err, stdout, stderr) => { | |
assert.ifError(err) | |
assert.strictEqual(stdout.trim(), JSON.stringify(flatten(pkg), null, 2)) | |
done() | |
}) | |
}) | |
test('can take piped file', function (done) { | |
const cli = path.resolve(__dirname, '..', pkg.bin) | |
const pkgJSON = path.resolve(__dirname, '..', 'package.json') | |
exec(`cat ${pkgJSON} | ${cli}`, (err, stdout, stderr) => { | |
assert.ifError(err) | |
assert.strictEqual(stdout.trim(), JSON.stringify(flatten(pkg), null, 2)) | |
done() | |
}) | |
}) | |
}) | |