| /* 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() |
| }) |
| }) |
| }) |