'use strict'; var test = require('tape'); var path = require('path'); var jsonFile = require('../index'); var forEach = require('foreach'); var keys = require('object-keys'); var Promise = require('promiseback').Deferred.Promise; var has = require('has'); var noNewlineFilename = 'test/no-trailing-newline.json'; var testFilename = 'test/test.json'; var testContents = { arr: [1, 2, 3], 'false': false, foo: 'bar', 'null': null, obj: { nested: {} }, 'true': true }; var NODE_011_NOT_FOUND = -2; var NODE_010_NOT_FOUND = 34; var WINDOWS_NODE_7_NOT_FOUND = -4058; var isFileNotFoundError = function (err) { return [ NODE_010_NOT_FOUND, NODE_011_NOT_FOUND, WINDOWS_NODE_7_NOT_FOUND ].indexOf(err.errno) > -1; }; test('requires a callback when arg is provided', function (t) { t.plan(6); t['throws'](function () { jsonFile(testFilename, undefined); }, TypeError, 'requires a function'); t['throws'](function () { jsonFile(testFilename, null); }, TypeError, 'requires a function'); t['throws'](function () { jsonFile(testFilename, true); }, TypeError, 'requires a function'); t['throws'](function () { jsonFile(testFilename, /a/g); }, TypeError, 'requires a function'); t['throws'](function () { jsonFile(testFilename, []); }, TypeError, 'requires a function'); t['throws'](function () { jsonFile(testFilename, {}); }, TypeError, 'requires a function'); t.end(); }); test('returns a file', function (t) { t.plan(3); jsonFile(testFilename, function (err, file) { t.error(err, 'no error'); t.ok(file instanceof jsonFile.JSONFile, 'file is instance of JSONFile'); t.ok(file instanceof jsonFile.JSONData, 'file is instance of JSONData'); t.end(); }); }); test('returns an exception if the file is not found', function (t) { t.plan(4); jsonFile('NOT A REAL FILE', function (err, file) { t.ok(err, 'error is truthy'); t.ok(isFileNotFoundError(err), 'error number is correct'); var expectedError = { code: 'ENOENT', errno: err.errno, path: path.resolve('NOT A REAL FILE') }; if (has(err, 'syscall')) { expectedError.syscall = 'open'; } t.deepEqual(err, expectedError, 'returns an error'); t.equal(file, undefined, 'file is undefined'); t.end(); }); }); test('returns an exception if the file has invalid JSON', function (t) { t.plan(4); jsonFile(__filename, function (err, file) { t.ok(err instanceof SyntaxError, 'error is a SyntaxError'); t.equal(typeof err.message, 'string', 'err.message is a string'); var expected = 'Unexpected token \''; t.equal(err.message.slice(0, expected.length), expected, 'gives the expected error'); t.equal(file, undefined, 'file is undefined'); t.end(); }); }); test('format', function (t) { t.plan(5); jsonFile(testFilename, function (err, file) { t.error(err, 'no error'); t.equal(file.format.indent, '\t', 'reads tabs'); t.equal(file.format.trailing, true, 'reads trailing newline'); t.deepEqual(file.format, { indent: '\t', trailing: true }, 'entire format is properly read'); t.test('no trailing newline', function (s1t) { s1t.plan(3); jsonFile(noNewlineFilename, function (noErr, noNewlineFile) { s1t.error(noErr, 'no error'); s1t.notOk(noNewlineFile.format.trailing, 'reads no trailing newline'); s1t.equal(noNewlineFile.format.indent, ' ', 'reads three spaces'); s1t.end(); }); }); t.end(); }); }); test('#get(): file.data', function (st) { st.plan(3); jsonFile(testFilename, function (err, file) { st.error(err, 'no error'); st.deepEqual(file.data, testContents, 'file.data matches expected'); file.get('obj').then(function (value) { st.notEqual(value, file.data.obj, 'get(key)->object is not the same reference'); st.end(); }); }); }); test('#get(): with key, promise', function (st) { st.plan(keys(testContents).length + 1); jsonFile(testFilename, function (err, file) { st.error(err, 'no error'); forEach(testContents, function (keyContents, key) { file.get(key).then(function (value) { st.deepEqual(value, keyContents, 'data from get("' + key + '") matches'); }); }); }); }); test('#get(): with key, callback', function (st) { st.plan((2 * keys(testContents).length) + 1); jsonFile(testFilename, function (err, file) { st.error(err, 'no error'); forEach(testContents, function (keyContents, key) { file.get(key, function (noError, data) { st.error(noError, 'no error'); st.deepEqual(data, keyContents, 'data from callback get("' + key + '") matches'); }); }); }); }); test('#get(): without key, promise', function (s2t) { s2t.plan(3); jsonFile(testFilename, function (err, file) { s2t.error(err, 'no error'); file.get().then(function (getData) { s2t.deepEqual(getData, file.data, 'data from get() matches'); s2t.notEqual(getData, file.data, 'data from get() is not the same reference'); s2t.end(); }); }); }); test('#get(): without key, callback', function (s2t) { s2t.plan(4); jsonFile(testFilename, function (noLoadError, file) { s2t.error(noLoadError, 'no error'); file.get(function (noError, data) { s2t.error(noError, 'no error'); s2t.deepEqual(data, file.data, 'data from async get() matches'); s2t.notEqual(data, file.data, 'data from async get() is not the same reference'); s2t.end(); }); }); }); test('#remove()', function (st) { st.test('with key, callback', function (s2t) { s2t.plan(4); jsonFile(testFilename, function (noLoadError, file) { s2t.ifError(noLoadError, 'no error'); file.remove('arr', function (noError, result) { s2t.ifError(noError, 'no error'); s2t.equal(result, undefined, 'deletion successful'); s2t.equal('arr' in file.data, false, 'key removed from data'); s2t.end(); }); }); }); st.test('with key, promise', function (s2t) { s2t.plan(2); jsonFile(testFilename).then(function (file) { return Promise.all([file, file.remove('arr')]); }).then(function (results) { var file = results[0]; var result = results[1]; s2t.equal(result, undefined, 'deletion successful'); s2t.equal('arr' in file.data, false, 'key removed from data'); s2t.end(); })['catch'](s2t.fail); }); st.test('with an empty key', function (s2t) { s2t.plan(1); jsonFile(testFilename).then(function (file) { return file.remove(''); }).then(s2t.fail)['catch'](function (err) { s2t.equal(err instanceof TypeError, true, 'err is TypeError'); s2t.end(); }); }); st.end(); }); test('#set()', function (t) { t.plan(4); jsonFile(testFilename, function (err, file) { t.error(err, 'no error'); t.equal(undefined, file.data.foobar, 'foo starts undefined'); var data = { foobar: { bar: 'baz', quux: true } }; file.set(data); t.deepEqual(file.data.foobar, data.foobar, 'expected data is set'); t.notEqual(file.data.foobar, data.foobar, 'data is not the same reference'); t.end(); }); }); test('#set(): setting invalid data', function (st) { st.plan(7); jsonFile(testFilename, function (err, file) { st.error(err, 'no error'); var error = new TypeError('object must be a plain object'); st['throws'](function () { return file.set(null); }, error, 'throws when given non-object'); st['throws'](function () { return file.set(true); }, error, 'throws when given non-object'); st['throws'](function () { return file.set([]); }, error, 'throws when given non-object'); st['throws'](function () { return file.set(function () {}); }, error, 'throws when given non-object'); st['throws'](function () { return file.set('foo'); }, error, 'throws when given non-object'); st['throws'](function () { return file.set(/f/); }, error, 'throws when given non-object'); st.end(); }); }); test('returns an error when no file', function (t) { t.plan(4); var filename = path.join(process.cwd(), 'does not exist.json'); jsonFile(filename, function (err, file) { t.ok(err, 'error is truthy'); t.notOk(file, 'file is falsy'); t.ok(isFileNotFoundError(err), 'error number is correct'); var expectedError = { code: 'ENOENT', errno: err.errno, path: filename }; if (has(err, 'syscall')) { expectedError.syscall = 'open'; } t.deepEqual(err, expectedError, 'returned an error'); t.end(); }); }); test('remembers filename', function (t) { t.plan(1); jsonFile(testFilename, function (err, file) { t.equal(file.filename, testFilename, 'filename equals ' + testFilename); t.end(); }); }); test('saves properly', function (t) { t.plan(6); jsonFile(testFilename, function (noLoadError, file) { t.error(noLoadError, 'no error'); t.equal(file.filename, testFilename, 'filename equals ' + testFilename); file.set({ foo: !testContents.foo }); file.save(function (noError) { t.error(noError, 'no error'); jsonFile(testFilename, function (err, file2) { file2.get('foo').then(function (value) { t.equal(value, !testContents.foo, 'value was properly saved'); file2.set({ foo: testContents.foo }); // restore original value file2.save(function (noSaveError) { t.error(noSaveError, 'save callback: no error'); }).then(function () { t.ok(true, 'save promise: success'); })['catch'](function () { t.fail('save promise: error. should not be here.'); }); }); }); }); }); }); test('#saveSync', function (t) { var file = jsonFile.sync(testFilename); file.set({ foo: !testContents.foo }); try { file.saveSync(); t.ok(true, 'saveSync: success'); } finally { file.set({ foo: testContents.foo }); file.saveSync(); t.ok(true, 'saveSync, restore original: success'); t.end(); } }); test('sync', function (t) { t['throws'](function () { jsonFile.sync('not a filename'); }, 'nonexistent filename throws'); var file = jsonFile.sync(testFilename); t.deepEqual(file.data, testContents, 'sync file data is expected data'); t.equal(true, file instanceof jsonFile.JSONFile, 'file is JSONFile'); t.equal(true, file instanceof jsonFile.JSONData, 'file is JSONData'); t.end(); });