diff --git a/index.js b/index.js index 58165ce..1f7dd9d 100644 --- a/index.js +++ b/index.js @@ -30,6 +30,6 @@ module.exports = function supports () { encodings: manifest.encodings || false, // Methods that are not part of abstract-leveldown or levelup - additionalMethods: manifest.additionalMethods || {} + additionalMethods: xtend(manifest.additionalMethods) }) } diff --git a/test/cloneable.js b/test/cloneable.js new file mode 100644 index 0000000..2712416 --- /dev/null +++ b/test/cloneable.js @@ -0,0 +1,26 @@ +'use strict' + +var supports = require('..') + +// Every object in a manifest must have a unique identity, to avoid accidental +// mutation. In supports() we only shallowly clone the manifest object itself +// and additionalMethods. If in the future we add more objects to manifests, +// this test will break and we'll know to start performing a deep clone. +module.exports = function cloneable (t, manifest) { + var copy = supports(manifest) + verifyUnique(t, 'manifest', manifest, copy) +} + +function verifyUnique (t, path, a, b) { + if (isObject(a) && isObject(b)) { + t.ok(a !== b, path + ' has unique identity') + + Object.keys(a).forEach(function (key) { + verifyUnique(t, path + '.' + key, a[key], b[key]) + }) + } +} + +function isObject (o) { + return typeof o === 'object' && o !== null +} diff --git a/test/index.js b/test/index.js index 73cdf96..aba38bd 100644 --- a/test/index.js +++ b/test/index.js @@ -2,6 +2,7 @@ var xtend = require('xtend') var shape = require('./shape') +var cloneable = require('./cloneable') module.exports = function suite (test, testCommon) { test('db has manifest', function (t) { @@ -9,6 +10,7 @@ module.exports = function suite (test, testCommon) { var manifest = db.supports shape(t, manifest) + cloneable(t, manifest) var before = xtend(manifest, { additionalMethods: xtend(manifest.additionalMethods) diff --git a/test/self.js b/test/self.js index fc67ac4..3c7d75b 100644 --- a/test/self.js +++ b/test/self.js @@ -3,9 +3,11 @@ var test = require('tape') var supports = require('..') var shape = require('./shape') +var cloneable = require('./cloneable') test('no options', function (t) { shape(t, supports()) + cloneable(t, supports()) t.end() }) @@ -44,13 +46,30 @@ test('truthy options', function (t) { test('merges input objects without mutating them', function (t) { var input1 = { bufferKeys: null, streams: false } - var input2 = { streams: true } + var input2 = { streams: true, additionalMethods: {} } var manifest = supports(input1, input2) + manifest.foobar = true + manifest.additionalMethods.baz = true + t.same(input1, { bufferKeys: null, streams: false }) - t.same(input2, { streams: true }) + t.same(input2, { streams: true, additionalMethods: {} }) t.is(manifest.bufferKeys, false) t.is(manifest.streams, true) shape(t, manifest) t.end() }) + +test('inherits additionalMethods', function (t) { + var manifest = supports({ additionalMethods: { foo: true } }, {}) + t.same(manifest.additionalMethods, { foo: true }) + t.end() +}) + +test('does not merge additionalMethods', function (t) { + var input1 = { additionalMethods: { foo: true } } + var input2 = { additionalMethods: { bar: true } } + var manifest = supports(input1, input2) + t.same(manifest.additionalMethods, { bar: true }) + t.end() +})