644 lines
15 KiB
JavaScript
644 lines
15 KiB
JavaScript
/* 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()
|
||
})
|
||
})
|
||
})
|