Refactored model to use Symbol
This commit is contained in:
parent
a059eb844e
commit
53b20e2f59
|
@ -1717,8 +1717,7 @@
|
|||
"clone": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/clone/-/clone-1.0.2.tgz",
|
||||
"integrity": "sha1-Jgt6meux7f4kdTgXX3gyQ8sZ0Uk=",
|
||||
"dev": true
|
||||
"integrity": "sha1-Jgt6meux7f4kdTgXX3gyQ8sZ0Uk="
|
||||
},
|
||||
"clone-stats": {
|
||||
"version": "0.0.1",
|
||||
|
@ -1936,6 +1935,14 @@
|
|||
"date-now": "0.1.4"
|
||||
}
|
||||
},
|
||||
"console.table": {
|
||||
"version": "0.9.1",
|
||||
"resolved": "https://registry.npmjs.org/console.table/-/console.table-0.9.1.tgz",
|
||||
"integrity": "sha1-SwH9CmtW//t5CSeD5WqbuQZ4cow=",
|
||||
"requires": {
|
||||
"easy-table": "1.1.0"
|
||||
}
|
||||
},
|
||||
"constants-browserify": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz",
|
||||
|
@ -2224,7 +2231,6 @@
|
|||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz",
|
||||
"integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"clone": "1.0.2"
|
||||
}
|
||||
|
@ -2330,6 +2336,14 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"easy-table": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/easy-table/-/easy-table-1.1.0.tgz",
|
||||
"integrity": "sha1-hvmrTBAvA3G3KXuSplHVgkvIy3M=",
|
||||
"requires": {
|
||||
"wcwidth": "1.0.1"
|
||||
}
|
||||
},
|
||||
"eazy-logger": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/eazy-logger/-/eazy-logger-3.0.2.tgz",
|
||||
|
@ -7891,6 +7905,15 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"wcwidth": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
|
||||
"integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"defaults": "1.0.3"
|
||||
}
|
||||
},
|
||||
"webpack": {
|
||||
"version": "1.14.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-1.14.0.tgz",
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
"dependencies": {
|
||||
"ajv": "4.10.4",
|
||||
"brace": "0.9.1",
|
||||
"console.table": "0.9.1",
|
||||
"javascript-natural-sort": "0.7.1",
|
||||
"lodash": "4.17.4",
|
||||
"prop-types": "15.5.10",
|
||||
|
|
125
src/eson.js
125
src/eson.js
|
@ -5,7 +5,7 @@
|
|||
* All functions are pure and don't mutate the ESON.
|
||||
*/
|
||||
|
||||
import { setIn, getIn, updateIn, deleteIn } from './utils/immutabilityHelpers'
|
||||
import { setIn, getIn, updateIn, deleteIn, cloneWithSymbols } from './utils/immutabilityHelpers'
|
||||
import { isObject } from './utils/typeUtils'
|
||||
import isEqual from 'lodash/isEqual'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
|
@ -27,6 +27,8 @@ export const SELECTED_END = 2
|
|||
export const SELECTED_BEFORE = 3
|
||||
export const SELECTED_AFTER = 4
|
||||
|
||||
export const META = Symbol('meta')
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {JSONType} json
|
||||
|
@ -40,19 +42,19 @@ export function jsonToEson (json, path = []) {
|
|||
let eson = {}
|
||||
const keys = Object.keys(json)
|
||||
keys.forEach((key) => eson[key] = jsonToEson(json[key], path.concat(key)))
|
||||
eson._meta = { id, path, type: 'Object', keys }
|
||||
// TODO: rename keys to props
|
||||
eson[META] = { id, path, type: 'Object', keys }
|
||||
return eson
|
||||
}
|
||||
else if (Array.isArray(json)) {
|
||||
let eson = {}
|
||||
json.forEach((value, index) => eson[index] = jsonToEson(value, path.concat(index)))
|
||||
eson._meta = { id, path, type: 'Array', length: json.length }
|
||||
let eson = json.map((value, index) => jsonToEson(value, path.concat(index)))
|
||||
eson[META] = { id, path, type: 'Array' }
|
||||
return eson
|
||||
}
|
||||
else { // json is a number, string, boolean, or null
|
||||
return {
|
||||
_meta: { id, path, type: 'value', value: json }
|
||||
}
|
||||
let eson = {}
|
||||
eson[META] = { id, path, type: 'value', value: json }
|
||||
return eson
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -63,7 +65,7 @@ export function jsonToEson (json, path = []) {
|
|||
* @return {Array}
|
||||
*/
|
||||
export function mapEsonArray (esonArray, callback) {
|
||||
const length = esonArray._meta.length
|
||||
const length = esonArray[META].length
|
||||
let result = []
|
||||
for (let i = 0; i < length; i++) {
|
||||
result[i] = callback(esonArray[i], i, esonArray)
|
||||
|
@ -71,33 +73,6 @@ export function mapEsonArray (esonArray, callback) {
|
|||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Splice an eson Array: delete items and insert items
|
||||
* @param {ESON} esonArray
|
||||
* @param {number} start
|
||||
* @param {number} deleteCount
|
||||
* @param {Array} [insertItems]
|
||||
*/
|
||||
export function spliceEsonArray(esonArray, start, deleteCount, insertItems = []) {
|
||||
let splicedArray = {}
|
||||
const originalLength = esonArray._meta.length
|
||||
|
||||
for (let i = 0; i < start; i++) {
|
||||
splicedArray[i] = esonArray[i]
|
||||
}
|
||||
for (let i = 0; i < insertItems.length; i++) {
|
||||
splicedArray[i + start] = insertItems[i]
|
||||
}
|
||||
for (let i = start + deleteCount; i < originalLength; i++) {
|
||||
splicedArray[i - deleteCount] = esonArray[i]
|
||||
}
|
||||
|
||||
const length = originalLength - deleteCount + insertItems.length
|
||||
splicedArray._meta = setIn(esonArray._meta, ['length'], length)
|
||||
|
||||
return splicedArray
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand function which will expand all nodes
|
||||
* @param {Path} path
|
||||
|
@ -288,20 +263,29 @@ export function deleteInEson (eson: ESON, jsonPath: JSONPath) : JSONType {
|
|||
export function transform (eson, callback, path = []) {
|
||||
const updated = callback(eson, path)
|
||||
|
||||
if (updated._meta.type === 'Object' || updated._meta.type === 'Array') {
|
||||
if (updated[META].type === 'Object') {
|
||||
let changed = false
|
||||
let updatedProps = {}
|
||||
let updatedObj = {}
|
||||
for (let key in updated) {
|
||||
if (updated.hasOwnProperty(key) && key !== '_meta') { // don't traverse the _meta objects
|
||||
const childPath = path.concat(updated._meta.type === 'Array' ? parseInt(key) : key)
|
||||
updatedProps[key] = transform(updated[key], callback, childPath)
|
||||
changed = changed || (updatedProps[key] !== updated[key])
|
||||
if (updated.hasOwnProperty(key)) {
|
||||
updatedObj[key] = transform(updated[key], callback, path.concat(key))
|
||||
changed = changed || (updatedObj[key] !== updated[key])
|
||||
}
|
||||
}
|
||||
updatedProps._meta = updated._meta
|
||||
return changed ? updatedProps : updated
|
||||
updatedObj[META] = updated[META]
|
||||
return changed ? updatedObj : updated
|
||||
}
|
||||
else { // eson._meta.type === 'value'
|
||||
else if (updated[META].type === 'Array') {
|
||||
let changed = false
|
||||
let updatedArr = []
|
||||
for (let i = 0; i < updated.length; i++) {
|
||||
updatedArr[i] = transform(updated[i], callback, path.concat(i))
|
||||
changed = changed || (updatedArr[i] !== updated[i])
|
||||
}
|
||||
updatedArr[META] = updated[META]
|
||||
return changed ? updatedArr : updated
|
||||
}
|
||||
else { // eson[META].type === 'value'
|
||||
return updated
|
||||
}
|
||||
}
|
||||
|
@ -318,7 +302,7 @@ export function transform (eson, callback, path = []) {
|
|||
*/
|
||||
export function expand (eson, filterCallback, expanded = true) {
|
||||
return transform(eson, function (value, path) {
|
||||
return ((value._meta.type === 'Array' || value._meta.type === 'Object') && filterCallback(path))
|
||||
return ((value[META].type === 'Array' || value[META].type === 'Object') && filterCallback(path))
|
||||
? expandOne(value, [], expanded)
|
||||
: value
|
||||
})
|
||||
|
@ -332,7 +316,7 @@ export function expand (eson, filterCallback, expanded = true) {
|
|||
* @return {ESON}
|
||||
*/
|
||||
export function expandOne (eson, path, expanded = true) {
|
||||
return setIn(eson, path.concat(['_meta', 'expanded']), expanded)
|
||||
return setIn(eson, path.concat([META, 'expanded']), expanded)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -371,7 +355,7 @@ export function applyErrors (eson, errors = []) {
|
|||
const esonWithErrors = errors.reduce((eson, error) => {
|
||||
const path = parseJSONPointer(error.dataPath)
|
||||
// TODO: do we want to be able to store multiple errors per item?
|
||||
return setIn(eson, path.concat(['_meta', 'error']), error)
|
||||
return setIn(eson, path.concat([META, 'error']), error)
|
||||
}, eson)
|
||||
|
||||
// cleanup any old error messages
|
||||
|
@ -393,8 +377,8 @@ export function cleanupMetaData(eson, field, ignorePaths = []) {
|
|||
})
|
||||
|
||||
return transform(eson, function (value, path) {
|
||||
return (value._meta[field] && !pathsMap[compileJSONPointer(path)])
|
||||
? deleteIn(value, ['_meta', field])
|
||||
return (value[META][field] && !pathsMap[compileJSONPointer(path)])
|
||||
? deleteIn(value, [META, field])
|
||||
: value
|
||||
})
|
||||
}
|
||||
|
@ -420,20 +404,20 @@ export function search (eson, text) {
|
|||
if (typeof prop === 'string' && text !== '' && containsCaseInsensitive(prop, text)) {
|
||||
const searchState = isEmpty(matches) ? 'active' : 'normal'
|
||||
matches.push({path, area: 'property'})
|
||||
updatedValue = setIn(updatedValue, ['_meta', 'searchProperty'], searchState)
|
||||
updatedValue = setIn(updatedValue, [META, 'searchProperty'], searchState)
|
||||
}
|
||||
else {
|
||||
updatedValue = deleteIn(updatedValue, ['_meta', 'searchProperty'])
|
||||
updatedValue = deleteIn(updatedValue, [META, 'searchProperty'])
|
||||
}
|
||||
|
||||
// check value
|
||||
if (value._meta.type === 'value' && text !== '' && containsCaseInsensitive(value._meta.value, text)) {
|
||||
if (value[META].type === 'value' && text !== '' && containsCaseInsensitive(value[META].value, text)) {
|
||||
const searchState = isEmpty(matches) ? 'active' : 'normal'
|
||||
matches.push({path, area: 'value'})
|
||||
updatedValue = setIn(updatedValue, ['_meta', 'searchValue'], searchState)
|
||||
updatedValue = setIn(updatedValue, [META, 'searchValue'], searchState)
|
||||
}
|
||||
else {
|
||||
updatedValue = deleteIn(updatedValue, ['_meta', 'searchValue'])
|
||||
updatedValue = deleteIn(updatedValue, [META, 'searchValue'])
|
||||
}
|
||||
|
||||
return updatedValue
|
||||
|
@ -509,7 +493,7 @@ export function nextSearchResult (eson, matches, active) {
|
|||
function setSearchStatus (eson, esonPointer, searchStatus) {
|
||||
const metaProp = esonPointer.area === 'property' ? 'searchProperty': 'searchValue'
|
||||
|
||||
return setIn(eson, esonPointer.path.concat(['_meta', metaProp]), searchStatus)
|
||||
return setIn(eson, esonPointer.path.concat([META, metaProp]), searchStatus)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -523,11 +507,11 @@ export function applySelection (eson, selection) {
|
|||
return cleanupMetaData(eson, 'selected')
|
||||
}
|
||||
else if (selection.before) {
|
||||
const updatedEson = setIn(eson, selection.before.concat(['_meta', 'selected']), SELECTED_BEFORE)
|
||||
const updatedEson = setIn(eson, selection.before.concat([META, 'selected']), SELECTED_BEFORE)
|
||||
return cleanupMetaData(updatedEson, 'selected', [selection.before])
|
||||
}
|
||||
else if (selection.after) {
|
||||
const updatedEson = setIn(eson, selection.after.concat(['_meta', 'selected']), SELECTED_AFTER)
|
||||
const updatedEson = setIn(eson, selection.after.concat([META, 'selected']), SELECTED_AFTER)
|
||||
return cleanupMetaData(updatedEson, 'selected', [selection.after])
|
||||
}
|
||||
else { // selection.start and selection.end
|
||||
|
@ -541,23 +525,24 @@ export function applySelection (eson, selection) {
|
|||
|
||||
// TODO: simplify the update function. Use pathsFromSelection ?
|
||||
|
||||
if (root._meta.type === 'Object') {
|
||||
const startIndex = root._meta.keys.indexOf(start)
|
||||
const endIndex = root._meta.keys.indexOf(end)
|
||||
if (root[META].type === 'Object') {
|
||||
const startIndex = root[META].keys.indexOf(start)
|
||||
const endIndex = root[META].keys.indexOf(end)
|
||||
|
||||
const minIndex = Math.min(startIndex, endIndex)
|
||||
const maxIndex = Math.max(startIndex, endIndex) + 1 // include max index itself
|
||||
|
||||
const selectedProps = root._meta.keys.slice(minIndex, maxIndex)
|
||||
const selectedProps = root[META].keys.slice(minIndex, maxIndex)
|
||||
selectedPaths = selectedProps.map(prop => rootPath.concat(prop))
|
||||
let updatedRoot = Object.assign({}, root)
|
||||
let updatedObj = cloneWithSymbols(root)
|
||||
selectedProps.forEach(prop => {
|
||||
updatedRoot[prop] = setIn(updatedRoot[prop], ['_meta', 'selected'], prop === end ? SELECTED_END : SELECTED)
|
||||
updatedObj[prop] = setIn(updatedObj[prop], [META, 'selected'],
|
||||
prop === end ? SELECTED_END : SELECTED)
|
||||
})
|
||||
|
||||
return updatedRoot
|
||||
return updatedObj
|
||||
}
|
||||
else { // root._meta.type === 'Array'
|
||||
else { // root[META].type === 'Array'
|
||||
const startIndex = parseInt(start)
|
||||
const endIndex = parseInt(end)
|
||||
|
||||
|
@ -567,12 +552,14 @@ export function applySelection (eson, selection) {
|
|||
const selectedIndices = range(minIndex, maxIndex)
|
||||
selectedPaths = selectedIndices.map(index => rootPath.concat(index))
|
||||
|
||||
let updatedRoot = Object.assign({}, root)
|
||||
let updatedArr = root.slice()
|
||||
updatedArr = cloneWithSymbols(root)
|
||||
selectedIndices.forEach(index => {
|
||||
updatedRoot[index] = setIn(updatedRoot[index], ['_meta', 'selected'], index === endIndex ? SELECTED_END : SELECTED)
|
||||
updatedArr[index] = setIn(updatedArr[index], [META, 'selected'],
|
||||
index === endIndex ? SELECTED_END : SELECTED)
|
||||
})
|
||||
|
||||
return updatedRoot
|
||||
return updatedArr
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
'use strict';
|
||||
|
||||
import clone from 'lodash/clone'
|
||||
import { isObjectOrArray, isObject } from './typeUtils'
|
||||
|
||||
/**
|
||||
|
@ -14,6 +13,40 @@ import { isObjectOrArray, isObject } from './typeUtils'
|
|||
* https://github.com/mariocasciaro/object-path-immutable
|
||||
*/
|
||||
|
||||
/**
|
||||
* Shallow clone of an Object, Array, or value
|
||||
* Also copies any symbols on the Objects and Arrays
|
||||
* @param {*} value
|
||||
* @return {*}
|
||||
*/
|
||||
export function cloneWithSymbols (value) {
|
||||
if (Array.isArray(value)) {
|
||||
// copy array items
|
||||
let arr = value.slice()
|
||||
|
||||
// copy all symbols
|
||||
Object.getOwnPropertySymbols(value).forEach(symbol => arr[symbol] = value[symbol])
|
||||
|
||||
return arr
|
||||
}
|
||||
else if (typeof value === 'object') {
|
||||
// copy properties
|
||||
let obj = {}
|
||||
for (let prop in value) {
|
||||
if (value.hasOwnProperty(prop)) {
|
||||
obj[prop] = value[prop]
|
||||
}
|
||||
}
|
||||
|
||||
// copy all symbols
|
||||
Object.getOwnPropertySymbols(value).forEach(symbol => obj[symbol] = value[symbol])
|
||||
|
||||
return obj
|
||||
}
|
||||
else {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* helper function to get a nested property in an object or array
|
||||
|
@ -66,7 +99,7 @@ export function setIn (object, path, value) {
|
|||
return object
|
||||
}
|
||||
else {
|
||||
const updatedObject = clone(object)
|
||||
const updatedObject = cloneWithSymbols(object)
|
||||
updatedObject[key] = updatedValue
|
||||
return updatedObject
|
||||
}
|
||||
|
@ -97,7 +130,7 @@ export function updateIn (object, path, callback) {
|
|||
return object
|
||||
}
|
||||
else {
|
||||
const updatedObject = clone(object)
|
||||
const updatedObject = cloneWithSymbols(object)
|
||||
updatedObject[key] = updatedValue
|
||||
return updatedObject
|
||||
}
|
||||
|
@ -127,7 +160,7 @@ export function deleteIn (object, path) {
|
|||
return object
|
||||
}
|
||||
else {
|
||||
const updatedObject = clone(object)
|
||||
const updatedObject = cloneWithSymbols(object)
|
||||
|
||||
if (Array.isArray(updatedObject)) {
|
||||
updatedObject.splice(key, 1)
|
||||
|
@ -147,7 +180,7 @@ export function deleteIn (object, path) {
|
|||
return object
|
||||
}
|
||||
else {
|
||||
const updatedObject = clone(object)
|
||||
const updatedObject = cloneWithSymbols(object)
|
||||
updatedObject[key] = updatedValue
|
||||
return updatedObject
|
||||
}
|
||||
|
|
|
@ -2,15 +2,18 @@ import { readFileSync } from 'fs'
|
|||
import test from 'ava'
|
||||
import { setIn, getIn, deleteIn } from '../src/utils/immutabilityHelpers'
|
||||
import {
|
||||
META,
|
||||
esonToJson, toEsonPath, toJsonPath, pathExists, transform, traverse,
|
||||
parseJSONPointer, compileJSONPointer,
|
||||
jsonToEson,
|
||||
expand, expandOne, expandPath, applyErrors, search, nextSearchResult,
|
||||
previousSearchResult,
|
||||
applySelection, pathsFromSelection,
|
||||
SELECTED, SELECTED_END, spliceEsonArray
|
||||
SELECTED, SELECTED_END
|
||||
} from '../src/eson'
|
||||
import deepMap from "deep-map/lib/index"
|
||||
import 'console.table'
|
||||
import lodashTransform from 'lodash/transform'
|
||||
import repeat from 'lodash/repeat'
|
||||
|
||||
const JSON1 = loadJSON('./resources/json1.json')
|
||||
const ESON1 = loadJSON('./resources/eson1.json')
|
||||
|
@ -39,22 +42,23 @@ test('toJsonPath', t => {
|
|||
})
|
||||
|
||||
test('jsonToEson', t => {
|
||||
t.deepEqual(replaceIds2(jsonToEson(1)), {_meta: {id: '[ID]', path: [], type: 'value', value: 1}})
|
||||
t.deepEqual(replaceIds2(jsonToEson("foo")), {_meta: {id: '[ID]', path: [], type: 'value', value: "foo"}})
|
||||
t.deepEqual(replaceIds2(jsonToEson(null)), {_meta: {id: '[ID]', path: [], type: 'value', value: null}})
|
||||
t.deepEqual(replaceIds2(jsonToEson(false)), {_meta: {id: '[ID]', path: [], type: 'value', value: false}})
|
||||
t.deepEqual(replaceIds2(jsonToEson({a:1, b: 2})), {
|
||||
_meta: {id: '[ID]', path: [], type: 'Object', keys: ['a', 'b']},
|
||||
a: {_meta: {id: '[ID]', path: ['a'], type: 'value', value: 1}},
|
||||
b: {_meta: {id: '[ID]', path: ['b'], type: 'value', value: 2}}
|
||||
assertDeepEqualEson(t, jsonToEson(1), {[META]: {id: '[ID]', path: [], type: 'value', value: 1}})
|
||||
assertDeepEqualEson(t, jsonToEson("foo"), {[META]: {id: '[ID]', path: [], type: 'value', value: "foo"}})
|
||||
assertDeepEqualEson(t, jsonToEson(null), {[META]: {id: '[ID]', path: [], type: 'value', value: null}})
|
||||
assertDeepEqualEson(t, jsonToEson(false), {[META]: {id: '[ID]', path: [], type: 'value', value: false}})
|
||||
assertDeepEqualEson(t, jsonToEson({a:1, b: 2}), {
|
||||
[META]: {id: '[ID]', path: [], type: 'Object', keys: ['a', 'b']},
|
||||
a: {[META]: {id: '[ID]', path: ['a'], type: 'value', value: 1}},
|
||||
b: {[META]: {id: '[ID]', path: ['b'], type: 'value', value: 2}}
|
||||
})
|
||||
|
||||
// printJSON(replaceIds2(jsonToEson([1,2])))
|
||||
t.deepEqual(replaceIds2(jsonToEson([1,2])), {
|
||||
_meta: {id: '[ID]', path: [], type: 'Array', length: 2},
|
||||
0: {_meta: {id: '[ID]', path: [0], type: 'value', value: 1}},
|
||||
1: {_meta: {id: '[ID]', path: [1], type: 'value', value: 2}}
|
||||
})
|
||||
const actual = jsonToEson([1,2])
|
||||
const expected = [
|
||||
{[META]: {id: '[ID]', path: [0], type: 'value', value: 1}},
|
||||
{[META]: {id: '[ID]', path: [1], type: 'value', value: 2}}
|
||||
]
|
||||
expected[META] = {id: '[ID]', path: [], type: 'Array'}
|
||||
assertDeepEqualEson(t, actual, expected)
|
||||
})
|
||||
|
||||
test('esonToJson', t => {
|
||||
|
@ -73,12 +77,12 @@ test('expand a single path', t => {
|
|||
|
||||
const path = ['obj', 'arr', 2]
|
||||
const collapsed = expandOne(eson, path, false)
|
||||
t.is(collapsed.obj.arr[2]._meta.expanded, false)
|
||||
t.deepEqual(deleteIn(collapsed, path.concat(['_meta', 'expanded'])), eson)
|
||||
t.is(collapsed.obj.arr[2][META].expanded, false)
|
||||
assertDeepEqualEson(t, deleteIn(collapsed, path.concat([META, 'expanded'])), eson)
|
||||
|
||||
const expanded = expandOne(eson, path, true)
|
||||
t.is(expanded.obj.arr[2]._meta.expanded, true)
|
||||
t.deepEqual(deleteIn(expanded, path.concat(['_meta', 'expanded'])), eson)
|
||||
t.is(expanded.obj.arr[2][META].expanded, true)
|
||||
assertDeepEqualEson(t, deleteIn(expanded, path.concat([META, 'expanded'])), eson)
|
||||
})
|
||||
|
||||
test('expand all objects/arrays on a path', t => {
|
||||
|
@ -94,24 +98,24 @@ test('expand all objects/arrays on a path', t => {
|
|||
const path = ['obj', 'arr', 2]
|
||||
|
||||
const collapsed = expandPath(eson, path, false)
|
||||
t.is(collapsed._meta.expanded, false)
|
||||
t.is(collapsed.obj._meta.expanded, false)
|
||||
t.is(collapsed.obj.arr._meta.expanded, false)
|
||||
t.is(collapsed.obj.arr[2]._meta.expanded, false)
|
||||
t.is(collapsed[META].expanded, false)
|
||||
t.is(collapsed.obj[META].expanded, false)
|
||||
t.is(collapsed.obj.arr[META].expanded, false)
|
||||
t.is(collapsed.obj.arr[2][META].expanded, false)
|
||||
|
||||
const expanded = expandPath(eson, path, true)
|
||||
t.is(expanded._meta.expanded, true)
|
||||
t.is(expanded.obj._meta.expanded, true)
|
||||
t.is(expanded.obj.arr._meta.expanded, true)
|
||||
t.is(expanded.obj.arr[2]._meta.expanded, true)
|
||||
t.is(expanded[META].expanded, true)
|
||||
t.is(expanded.obj[META].expanded, true)
|
||||
t.is(expanded.obj.arr[META].expanded, true)
|
||||
t.is(expanded.obj.arr[2][META].expanded, true)
|
||||
|
||||
let orig = expanded
|
||||
orig = deleteIn(orig, [].concat(['_meta', 'expanded']))
|
||||
orig = deleteIn(orig, ['obj'].concat(['_meta', 'expanded']))
|
||||
orig = deleteIn(orig, ['obj', 'arr'].concat(['_meta', 'expanded']))
|
||||
orig = deleteIn(orig, ['obj', 'arr', 2].concat(['_meta', 'expanded']))
|
||||
orig = deleteIn(orig, [].concat([META, 'expanded']))
|
||||
orig = deleteIn(orig, ['obj'].concat([META, 'expanded']))
|
||||
orig = deleteIn(orig, ['obj', 'arr'].concat([META, 'expanded']))
|
||||
orig = deleteIn(orig, ['obj', 'arr', 2].concat([META, 'expanded']))
|
||||
|
||||
t.deepEqual(orig, eson)
|
||||
assertDeepEqualEson(t, orig, eson)
|
||||
})
|
||||
|
||||
test('expand a callback', t => {
|
||||
|
@ -129,15 +133,16 @@ test('expand a callback', t => {
|
|||
}
|
||||
const expandedValue = false
|
||||
const collapsed = expand(eson, filterCallback, expandedValue)
|
||||
t.is(collapsed.obj.arr._meta.expanded, expandedValue)
|
||||
t.is(collapsed.obj.arr._meta.expanded, expandedValue)
|
||||
t.is(collapsed.obj.arr[2]._meta.expanded, expandedValue)
|
||||
t.is(collapsed[META].expanded, undefined)
|
||||
t.is(collapsed.obj[META].expanded, expandedValue)
|
||||
t.is(collapsed.obj.arr[META].expanded, expandedValue)
|
||||
t.is(collapsed.obj.arr[2][META].expanded, expandedValue)
|
||||
|
||||
let orig = collapsed
|
||||
orig = deleteIn(orig, ['obj'].concat(['_meta', 'expanded']))
|
||||
orig = deleteIn(orig, ['obj', 'arr'].concat(['_meta', 'expanded']))
|
||||
orig = deleteIn(orig, ['obj', 'arr', 2].concat(['_meta', 'expanded']))
|
||||
t.deepEqual(orig, eson)
|
||||
orig = deleteIn(orig, ['obj'].concat([META, 'expanded']))
|
||||
orig = deleteIn(orig, ['obj', 'arr'].concat([META, 'expanded']))
|
||||
orig = deleteIn(orig, ['obj', 'arr', 2].concat([META, 'expanded']))
|
||||
assertDeepEqualEson(t, orig, eson)
|
||||
})
|
||||
|
||||
test('expand a callback should not change the object when nothing happens', t => {
|
||||
|
@ -154,7 +159,7 @@ test('expand a callback should not change the object when nothing happens', t =>
|
|||
test('transform (no change)', t => {
|
||||
const eson = jsonToEson({a: [1,2,3], b: {c: 4}})
|
||||
const updated = transform(eson, (value, path) => value)
|
||||
t.deepEqual(updated, eson)
|
||||
assertDeepEqualEson(t, updated, eson)
|
||||
t.is(updated, eson)
|
||||
})
|
||||
|
||||
|
@ -162,13 +167,10 @@ test('transform (change based on value)', t => {
|
|||
const eson = jsonToEson({a: [1,2,3], b: {c: 4}})
|
||||
|
||||
const updated = transform(eson,
|
||||
(value, path) => value._meta.value === 2 ? jsonToEson(20, path) : value)
|
||||
(value, path) => value[META].value === 2 ? jsonToEson(20, path) : value)
|
||||
const expected = jsonToEson({a: [1,20,3], b: {c: 4}})
|
||||
|
||||
replaceIds(updated)
|
||||
replaceIds(expected)
|
||||
|
||||
t.deepEqual(updated, expected)
|
||||
assertDeepEqualEson(t, updated, expected)
|
||||
t.is(updated.b, eson.b) // should not have replaced b
|
||||
})
|
||||
|
||||
|
@ -179,9 +181,7 @@ test('transform (change based on path)', t => {
|
|||
(value, path) => path.join('.') === 'a.1' ? jsonToEson(20, path) : value)
|
||||
const expected = jsonToEson({a: [1,20,3], b: {c: 4}})
|
||||
|
||||
replaceIds(updated)
|
||||
replaceIds(expected)
|
||||
t.deepEqual(updated, expected)
|
||||
assertDeepEqualEson(t, updated, expected)
|
||||
t.is(updated.b, eson.b) // should not have replaced b
|
||||
})
|
||||
|
||||
|
@ -226,9 +226,9 @@ test('add and remove errors', t => {
|
|||
const actual1 = applyErrors(eson, jsonSchemaErrors)
|
||||
|
||||
let expected = eson
|
||||
expected = setIn(expected, ['obj', 'arr', '2', 'last', '_meta', 'error'], jsonSchemaErrors[0])
|
||||
expected = setIn(expected, ['nill', '_meta', 'error'], jsonSchemaErrors[1])
|
||||
t.deepEqual(actual1, expected)
|
||||
expected = setIn(expected, ['obj', 'arr', '2', 'last', META, 'error'], jsonSchemaErrors[0])
|
||||
expected = setIn(expected, ['nill', META, 'error'], jsonSchemaErrors[1])
|
||||
assertDeepEqualEson(t, actual1, expected)
|
||||
|
||||
// re-applying the same errors should not change eson
|
||||
const actual2 = applyErrors(actual1, jsonSchemaErrors)
|
||||
|
@ -236,7 +236,7 @@ test('add and remove errors', t => {
|
|||
|
||||
// clear errors
|
||||
const actual3 = applyErrors(actual2, [])
|
||||
t.deepEqual(actual3, eson)
|
||||
assertDeepEqualEson(t, actual3, eson)
|
||||
t.is(actual3.str, eson.str) // shouldn't have touched values not affected by the errors
|
||||
})
|
||||
|
||||
|
@ -292,14 +292,14 @@ test('search', t => {
|
|||
t.deepEqual(active, {path: ['obj', 'arr', 2, 'last'], area: 'property'})
|
||||
|
||||
let expected = esonWithSearch
|
||||
expected = setIn(expected, ['obj', 'arr', '2', 'last', '_meta', 'searchProperty'], 'active')
|
||||
expected = setIn(expected, ['str', '_meta', 'searchValue'], 'normal')
|
||||
expected = setIn(expected, ['nill', '_meta', 'searchProperty'], 'normal')
|
||||
expected = setIn(expected, ['nill', '_meta', 'searchValue'], 'normal')
|
||||
expected = setIn(expected, ['bool', '_meta', 'searchProperty'], 'normal')
|
||||
expected = setIn(expected, ['bool', '_meta', 'searchValue'], 'normal')
|
||||
expected = setIn(expected, ['obj', 'arr', '2', 'last', META, 'searchProperty'], 'active')
|
||||
expected = setIn(expected, ['str', META, 'searchValue'], 'normal')
|
||||
expected = setIn(expected, ['nill', META, 'searchProperty'], 'normal')
|
||||
expected = setIn(expected, ['nill', META, 'searchValue'], 'normal')
|
||||
expected = setIn(expected, ['bool', META, 'searchProperty'], 'normal')
|
||||
expected = setIn(expected, ['bool', META, 'searchValue'], 'normal')
|
||||
|
||||
t.deepEqual(esonWithSearch, expected)
|
||||
assertDeepEqualEson(t, esonWithSearch, expected)
|
||||
})
|
||||
|
||||
test('nextSearchResult', t => {
|
||||
|
@ -320,27 +320,27 @@ test('nextSearchResult', t => {
|
|||
])
|
||||
|
||||
t.deepEqual(searchResult.active, {path: ['obj', 'arr'], area: 'property'})
|
||||
t.is(getIn(searchResult.eson, ['obj', 'arr', '_meta', 'searchProperty']), 'active')
|
||||
t.is(getIn(searchResult.eson, ['obj', 'arr', 2, 'last', '_meta', 'searchProperty']), 'normal')
|
||||
t.is(getIn(searchResult.eson, ['bool', '_meta', 'searchValue']), 'normal')
|
||||
t.is(getIn(searchResult.eson, ['obj', 'arr', META, 'searchProperty']), 'active')
|
||||
t.is(getIn(searchResult.eson, ['obj', 'arr', 2, 'last', META, 'searchProperty']), 'normal')
|
||||
t.is(getIn(searchResult.eson, ['bool', META, 'searchValue']), 'normal')
|
||||
|
||||
const second = nextSearchResult(searchResult.eson, searchResult.matches, searchResult.active)
|
||||
t.deepEqual(second.active, {path: ['obj', 'arr', 2, 'last'], area: 'property'})
|
||||
t.is(getIn(second.eson, ['obj', 'arr', '_meta', 'searchProperty']), 'normal')
|
||||
t.is(getIn(second.eson, ['obj', 'arr', 2, 'last', '_meta', 'searchProperty']), 'active')
|
||||
t.is(getIn(second.eson, ['bool', '_meta', 'searchValue']), 'normal')
|
||||
t.is(getIn(second.eson, ['obj', 'arr', META, 'searchProperty']), 'normal')
|
||||
t.is(getIn(second.eson, ['obj', 'arr', 2, 'last', META, 'searchProperty']), 'active')
|
||||
t.is(getIn(second.eson, ['bool', META, 'searchValue']), 'normal')
|
||||
|
||||
const third = nextSearchResult(second.eson, second.matches, second.active)
|
||||
t.deepEqual(third.active, {path: ['bool'], area: 'value'})
|
||||
t.is(getIn(third.eson, ['obj', 'arr', '_meta', 'searchProperty']), 'normal')
|
||||
t.is(getIn(third.eson, ['obj', 'arr', 2, 'last', '_meta', 'searchProperty']), 'normal')
|
||||
t.is(getIn(third.eson, ['bool', '_meta', 'searchValue']), 'active')
|
||||
t.is(getIn(third.eson, ['obj', 'arr', META, 'searchProperty']), 'normal')
|
||||
t.is(getIn(third.eson, ['obj', 'arr', 2, 'last', META, 'searchProperty']), 'normal')
|
||||
t.is(getIn(third.eson, ['bool', META, 'searchValue']), 'active')
|
||||
|
||||
const wrappedAround = nextSearchResult(third.eson, third.matches, third.active)
|
||||
t.deepEqual(wrappedAround.active, {path: ['obj', 'arr'], area: 'property'})
|
||||
t.is(getIn(wrappedAround.eson, ['obj', 'arr', '_meta', 'searchProperty']), 'active')
|
||||
t.is(getIn(wrappedAround.eson, ['obj', 'arr', 2, 'last', '_meta', 'searchProperty']), 'normal')
|
||||
t.is(getIn(wrappedAround.eson, ['bool', '_meta', 'searchValue']), 'normal')
|
||||
t.is(getIn(wrappedAround.eson, ['obj', 'arr', META, 'searchProperty']), 'active')
|
||||
t.is(getIn(wrappedAround.eson, ['obj', 'arr', 2, 'last', META, 'searchProperty']), 'normal')
|
||||
t.is(getIn(wrappedAround.eson, ['bool', META, 'searchValue']), 'normal')
|
||||
})
|
||||
|
||||
test('previousSearchResult', t => {
|
||||
|
@ -361,27 +361,27 @@ test('previousSearchResult', t => {
|
|||
])
|
||||
|
||||
t.deepEqual(searchResult.active, {path: ['obj', 'arr'], area: 'property'})
|
||||
t.is(getIn(searchResult.eson, ['obj', 'arr', '_meta', 'searchProperty']), 'active')
|
||||
t.is(getIn(searchResult.eson, ['obj', 'arr', 2, 'last', '_meta', 'searchProperty']), 'normal')
|
||||
t.is(getIn(searchResult.eson, ['bool', '_meta', 'searchValue']), 'normal')
|
||||
t.is(getIn(searchResult.eson, ['obj', 'arr', META, 'searchProperty']), 'active')
|
||||
t.is(getIn(searchResult.eson, ['obj', 'arr', 2, 'last', META, 'searchProperty']), 'normal')
|
||||
t.is(getIn(searchResult.eson, ['bool', META, 'searchValue']), 'normal')
|
||||
|
||||
const third = previousSearchResult(searchResult.eson, searchResult.matches, searchResult.active)
|
||||
t.deepEqual(third.active, {path: ['bool'], area: 'value'})
|
||||
t.is(getIn(third.eson, ['obj', 'arr', '_meta', 'searchProperty']), 'normal')
|
||||
t.is(getIn(third.eson, ['obj', 'arr', 2, 'last', '_meta', 'searchProperty']), 'normal')
|
||||
t.is(getIn(third.eson, ['bool', '_meta', 'searchValue']), 'active')
|
||||
t.is(getIn(third.eson, ['obj', 'arr', META, 'searchProperty']), 'normal')
|
||||
t.is(getIn(third.eson, ['obj', 'arr', 2, 'last', META, 'searchProperty']), 'normal')
|
||||
t.is(getIn(third.eson, ['bool', META, 'searchValue']), 'active')
|
||||
|
||||
const second = previousSearchResult(third.eson, third.matches, third.active)
|
||||
t.deepEqual(second.active, {path: ['obj', 'arr', 2, 'last'], area: 'property'})
|
||||
t.is(getIn(second.eson, ['obj', 'arr', '_meta', 'searchProperty']), 'normal')
|
||||
t.is(getIn(second.eson, ['obj', 'arr', 2, 'last', '_meta', 'searchProperty']), 'active')
|
||||
t.is(getIn(second.eson, ['bool', '_meta', 'searchValue']), 'normal')
|
||||
t.is(getIn(second.eson, ['obj', 'arr', META, 'searchProperty']), 'normal')
|
||||
t.is(getIn(second.eson, ['obj', 'arr', 2, 'last', META, 'searchProperty']), 'active')
|
||||
t.is(getIn(second.eson, ['bool', META, 'searchValue']), 'normal')
|
||||
|
||||
const first = previousSearchResult(second.eson, second.matches, second.active)
|
||||
t.deepEqual(first.active, {path: ['obj', 'arr'], area: 'property'})
|
||||
t.is(getIn(first.eson, ['obj', 'arr', '_meta', 'searchProperty']), 'active')
|
||||
t.is(getIn(first.eson, ['obj', 'arr', 2, 'last', '_meta', 'searchProperty']), 'normal')
|
||||
t.is(getIn(first.eson, ['bool', '_meta', 'searchValue']), 'normal')
|
||||
t.is(getIn(first.eson, ['obj', 'arr', META, 'searchProperty']), 'active')
|
||||
t.is(getIn(first.eson, ['obj', 'arr', 2, 'last', META, 'searchProperty']), 'normal')
|
||||
t.is(getIn(first.eson, ['bool', META, 'searchValue']), 'normal')
|
||||
})
|
||||
|
||||
test('selection (object)', t => {
|
||||
|
@ -401,10 +401,10 @@ test('selection (object)', t => {
|
|||
const actual = applySelection(eson, selection)
|
||||
|
||||
let expected = eson
|
||||
expected = setIn(expected, ['obj', '_meta', 'selected'], SELECTED)
|
||||
expected = setIn(expected, ['str', '_meta', 'selected'], SELECTED)
|
||||
expected = setIn(expected, ['nill', '_meta', 'selected'], SELECTED_END)
|
||||
t.deepEqual(actual, expected)
|
||||
expected = setIn(expected, ['obj', META, 'selected'], SELECTED)
|
||||
expected = setIn(expected, ['str', META, 'selected'], SELECTED)
|
||||
expected = setIn(expected, ['nill', META, 'selected'], SELECTED_END)
|
||||
assertDeepEqualEson(t, actual, expected)
|
||||
|
||||
// test whether old selection results are cleaned up
|
||||
const selection2 = {
|
||||
|
@ -413,9 +413,9 @@ test('selection (object)', t => {
|
|||
}
|
||||
const actual2 = applySelection(actual, selection2)
|
||||
let expected2 = eson
|
||||
expected2 = setIn(expected2, ['nill', '_meta', 'selected'], SELECTED)
|
||||
expected2 = setIn(expected2, ['bool', '_meta', 'selected'], SELECTED_END)
|
||||
t.deepEqual(actual2, expected2)
|
||||
expected2 = setIn(expected2, ['nill', META, 'selected'], SELECTED)
|
||||
expected2 = setIn(expected2, ['bool', META, 'selected'], SELECTED_END)
|
||||
assertDeepEqualEson(t, actual2, expected2)
|
||||
})
|
||||
|
||||
test('selection (array)', t => {
|
||||
|
@ -435,10 +435,10 @@ test('selection (array)', t => {
|
|||
const actual = applySelection(eson, selection)
|
||||
|
||||
let expected = eson
|
||||
expected = setIn(expected, ['obj', 'arr', '0', '_meta', 'selected'], SELECTED_END)
|
||||
expected = setIn(expected, ['obj', 'arr', '1', '_meta', 'selected'], SELECTED)
|
||||
expected = setIn(expected, ['obj', 'arr', '0', META, 'selected'], SELECTED_END)
|
||||
expected = setIn(expected, ['obj', 'arr', '1', META, 'selected'], SELECTED)
|
||||
|
||||
t.deepEqual(actual, expected)
|
||||
assertDeepEqualEson(t, actual, expected)
|
||||
})
|
||||
|
||||
test('selection (value)', t => {
|
||||
|
@ -456,8 +456,8 @@ test('selection (value)', t => {
|
|||
}
|
||||
|
||||
const actual = applySelection(eson, selection)
|
||||
const expected = setIn(eson, ['obj', 'arr', '2', 'first', '_meta', 'selected'], SELECTED_END)
|
||||
t.deepEqual(actual, expected)
|
||||
const expected = setIn(eson, ['obj', 'arr', '2', 'first', META, 'selected'], SELECTED_END)
|
||||
assertDeepEqualEson(t, actual, expected)
|
||||
})
|
||||
|
||||
test('selection (node)', t => {
|
||||
|
@ -475,8 +475,8 @@ test('selection (node)', t => {
|
|||
}
|
||||
|
||||
const actual = applySelection(eson, selection)
|
||||
const expected = setIn(eson, ['obj', 'arr', '_meta', 'selected'], SELECTED_END)
|
||||
t.deepEqual(actual, expected)
|
||||
const expected = setIn(eson, ['obj', 'arr', META, 'selected'], SELECTED_END)
|
||||
assertDeepEqualEson(t, actual, expected)
|
||||
})
|
||||
|
||||
test('pathsFromSelection (object)', t => {
|
||||
|
@ -531,31 +531,34 @@ test('pathsFromSelection (after)', t => {
|
|||
t.deepEqual(pathsFromSelection(ESON1, selection), [])
|
||||
})
|
||||
|
||||
test('spliceEsonArray', t => {
|
||||
const eson = jsonToEson([1,2,3])
|
||||
function assertDeepEqualEson (t, actual, expected, path = [], ignoreIds = true) {
|
||||
const actualMeta = ignoreIds ? normalizeMetaIds(actual[META]) : actual[META]
|
||||
const expectedMeta = ignoreIds ? normalizeMetaIds(expected[META]) : expected[META]
|
||||
|
||||
t.deepEqual(replaceIds(spliceEsonArray(eson, 1, 1)), replaceIds(jsonToEson([1,3])))
|
||||
t.deepEqual(replaceIds(spliceEsonArray(eson, 1, 5)), replaceIds(jsonToEson([1])))
|
||||
t.deepEqual(replaceIds(spliceEsonArray(eson, 1, 0, [10])), replaceIds(jsonToEson([1,5,3])))
|
||||
t.deepEqual(replaceIds(spliceEsonArray(eson, 1, 0, [10,20])), replaceIds(jsonToEson([1,10,20,3])))
|
||||
})
|
||||
t.deepEqual(actualMeta, expectedMeta, `Meta data not equal, path=[${path.join(', ')}]`)
|
||||
|
||||
// helper function to replace all id properties with a constant value
|
||||
function replaceIds (eson, value = '[ID]') {
|
||||
eson._meta.id = value
|
||||
|
||||
if (eson._meta.type === 'Object' || eson._meta.type === 'Array') {
|
||||
for (let key in eson) {
|
||||
if (eson.hasOwnProperty(key) && key !== '_meta') {
|
||||
replaceIds(eson[key], value)
|
||||
}
|
||||
}
|
||||
if (actualMeta.type === 'Array') {
|
||||
t.deepEqual(actual.length, expected.length, 'Actual lengths of arrays should be equal, path=[${path.join(\', \')}]')
|
||||
actual.forEach((item, index) => assertDeepEqualEson(t, actual[index], expected[index], path.concat(index)), ignoreIds)
|
||||
}
|
||||
else if (actualMeta.type === 'Object') {
|
||||
t.deepEqual(Object.keys(actual).sort(), Object.keys(expected).sort(), 'Actual properties should be equal, path=[${path.join(\', \')}]')
|
||||
actualMeta.keys.forEach(key => assertDeepEqualEson(t, actual[key], expected[key], path.concat(key)), ignoreIds)
|
||||
}
|
||||
else { // actual[META].type === 'value'
|
||||
t.deepEqual(Object.keys(actual), [], 'Value should not contain additional properties, path=[${path.join(\', \')}]')
|
||||
}
|
||||
}
|
||||
|
||||
// helper function to replace all id properties with a constant value
|
||||
function replaceIds2 (data, key = 'id', value = '[ID]') {
|
||||
return deepMap(data, (v, k) => k === key ? value : v)
|
||||
function normalizeMetaIds (meta) {
|
||||
return lodashTransform(meta, (result, value, key) => {
|
||||
if (key === 'id') {
|
||||
result[key] = '[ID]'
|
||||
}
|
||||
else {
|
||||
result[key] = value
|
||||
}
|
||||
}, {})
|
||||
}
|
||||
|
||||
// helper function to print JSON in the console
|
||||
|
@ -566,6 +569,33 @@ function printJSON (json, message = null) {
|
|||
console.log(JSON.stringify(json, null, 2))
|
||||
}
|
||||
|
||||
function printESON (eson, message = null) {
|
||||
if (message) {
|
||||
console.log(message)
|
||||
}
|
||||
|
||||
let data = []
|
||||
|
||||
transform(eson, function (value, path) {
|
||||
// const strPath = padEnd(, 20)
|
||||
// console.log(`${strPath} ${'value' in value[META] ? value[META].value : ''} ${JSON.stringify(value[META])}`)
|
||||
|
||||
data.push({
|
||||
path: '[' + path.join(', ') + ']',
|
||||
value: repeat(' ', path.length) + (value[META].type === 'Object'
|
||||
? '{...}'
|
||||
: value[META].type === 'Array'
|
||||
? '[...]'
|
||||
: JSON.stringify(value[META].value)),
|
||||
meta: JSON.stringify(value[META])
|
||||
})
|
||||
|
||||
return value
|
||||
})
|
||||
|
||||
console.table(data)
|
||||
}
|
||||
|
||||
// helper function to load a JSON file
|
||||
function loadJSON (filename) {
|
||||
return JSON.parse(readFileSync(__dirname + '/' + filename, 'utf-8'))
|
||||
|
|
Loading…
Reference in New Issue