jsoneditor/test/eson.test.js

563 lines
18 KiB
JavaScript
Raw Normal View History

import { readFileSync } from 'fs'
import test from 'ava'
import { setIn, getIn, deleteIn } from '../src/utils/immutabilityHelpers'
2016-09-09 17:01:06 +08:00
import {
esonToJson, toEsonPath, toJsonPath, pathExists, transform, traverse,
2016-11-12 21:10:10 +08:00
parseJSONPointer, compileJSONPointer,
jsonToEson,
2017-12-14 23:40:55 +08:00
expand, expandOne, expandPath, applyErrors, search, nextSearchResult, previousSearchResult,
2017-10-15 03:57:59 +08:00
applySelection, pathsFromSelection,
SELECTED, SELECTED_END
2017-09-08 19:36:15 +08:00
} from '../src/eson'
2017-11-30 04:52:18 +08:00
import deepMap from "deep-map/lib/index"
const JSON1 = loadJSON('./resources/json1.json')
const ESON1 = loadJSON('./resources/eson1.json')
const ESON2 = loadJSON('./resources/eson2.json')
2016-11-12 21:10:10 +08:00
test('toEsonPath', t => {
const jsonPath = ['obj', 'arr', '2', 'last']
const esonPath = [
'props', '0', 'value',
'props', '0', 'value',
'items', '2', 'value',
'props', '1', 'value'
]
t.deepEqual(toEsonPath(ESON1, jsonPath), esonPath)
})
test('toJsonPath', t => {
const jsonPath = ['obj', 'arr', '2', 'last']
const esonPath = [
'props', '0', 'value',
'props', '0', 'value',
'items', '2', 'value',
'props', '1', 'value'
]
t.deepEqual(toJsonPath(ESON1, esonPath), jsonPath)
})
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})), {
2017-11-30 04:52:18 +08:00
_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])), {
2017-11-30 04:52:18 +08:00
_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}}
})
})
2017-09-08 19:36:15 +08:00
test('esonToJson', t => {
t.deepEqual(esonToJson(ESON1), JSON1)
})
test('expand a single path', t => {
const eson = jsonToEson({
"obj": {
"arr": [1,2, {"first":3,"last":4}]
},
"str": "hello world",
"nill": null,
"bool": false
})
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)
const expanded = expandOne(eson, path, true)
t.is(expanded.obj.arr[2]._meta.expanded, true)
t.deepEqual(deleteIn(expanded, path.concat(['_meta', 'expanded'])), eson)
})
test('expand all objects/arrays on a path', t => {
const eson = jsonToEson({
"obj": {
"arr": [1,2, {"first":3,"last":4}]
},
"str": "hello world",
"nill": null,
"bool": false
})
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)
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)
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']))
t.deepEqual(orig, eson)
})
test('expand a callback', t => {
const eson = jsonToEson({
"obj": {
"arr": [1,2, {"first":3,"last":4}]
},
"str": "hello world",
"nill": null,
"bool": false
})
function filterCallback (path) {
return path.length >= 1
}
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)
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)
})
test('expand a callback should not change the object when nothing happens', t => {
const eson = jsonToEson({a: [1,2,3], b: {c: 4}})
function callback (path) {
return false
}
const expanded = false
const collapsed = expand(eson, callback, expanded)
t.is(collapsed, eson)
})
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)
t.is(updated, eson)
})
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)
const expected = jsonToEson({a: [1,20,3], b: {c: 4}})
replaceIds(updated)
replaceIds(expected)
t.deepEqual(updated, expected)
t.is(updated.b, eson.b) // should not have replaced b
})
test('transform (change based on path)', t => {
const eson = jsonToEson({a: [1,2,3], b: {c: 4}})
const updated = transform(eson,
(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)
t.is(updated.b, eson.b) // should not have replaced b
})
2016-09-09 17:01:06 +08:00
test('pathExists', t => {
t.is(pathExists(ESON1, ['obj', 'arr', 2, 'first']), true)
t.is(pathExists(ESON1, ['obj', 'foo']), false)
t.is(pathExists(ESON1, ['obj', 'foo', 'bar']), false)
t.is(pathExists(ESON1, []), true)
2016-09-09 17:01:06 +08:00
})
test('parseJSONPointer', t => {
t.deepEqual(parseJSONPointer('/obj/a'), ['obj', 'a'])
t.deepEqual(parseJSONPointer('/arr/-'), ['arr', '-'])
t.deepEqual(parseJSONPointer('/foo/~1~0 ~0~1'), ['foo', '/~ ~/'])
t.deepEqual(parseJSONPointer('/obj'), ['obj'])
2016-10-28 17:04:06 +08:00
t.deepEqual(parseJSONPointer('/'), [''])
t.deepEqual(parseJSONPointer(''), [])
2016-09-09 17:01:06 +08:00
})
test('compileJSONPointer', t => {
t.deepEqual(compileJSONPointer(['foo', 'bar']), '/foo/bar')
t.deepEqual(compileJSONPointer(['foo', '/~ ~/']), '/foo/~1~0 ~0~1')
2016-10-28 17:04:06 +08:00
t.deepEqual(compileJSONPointer(['']), '/')
t.deepEqual(compileJSONPointer([]), '')
2016-09-09 17:01:06 +08:00
})
2016-11-12 21:10:10 +08:00
test('add and remove errors', t => {
2017-12-14 21:30:02 +08:00
const eson = jsonToEson({
"obj": {
"arr": [1,2, {"first":3,"last":4}]
},
"str": "hello world",
"nill": null,
"bool": false
})
const jsonSchemaErrors = [
{dataPath: '/obj/arr/2/last', message: 'String expected'},
{dataPath: '/nill', message: 'Null expected'}
]
2017-12-14 23:40:55 +08:00
const actual1 = applyErrors(eson, jsonSchemaErrors)
2017-12-14 21:30:02 +08:00
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)
2017-12-14 21:30:02 +08:00
// re-applying the same errors should not change eson
2017-12-14 23:40:55 +08:00
const actual2 = applyErrors(actual1, jsonSchemaErrors)
2017-12-14 21:30:02 +08:00
t.is(actual2, actual1)
2017-12-14 21:30:02 +08:00
// clear errors
2017-12-14 23:40:55 +08:00
const actual3 = applyErrors(actual2, [])
2017-12-14 21:30:02 +08:00
t.deepEqual(actual3, eson)
t.is(actual3.str, eson.str) // shouldn't have touched values not affected by the errors
2016-11-20 03:44:58 +08:00
})
test('traverse', t => {
// {obj: {a: 2}, arr: [3]}
let log = []
const returnValue = traverse(ESON2, function (value, path, root) {
t.is(root, ESON2)
log.push([value, path, root])
})
t.is(returnValue, undefined)
const EXPECTED_LOG = [
[ESON2, [], ESON2],
[ESON2.props[0].value, ['obj'], ESON2],
[ESON2.props[0].value.props[0].value, ['obj', 'a'], ESON2],
[ESON2.props[1].value, ['arr'], ESON2],
[ESON2.props[1].value.items[0].value, ['arr', '0'], ESON2],
]
log.forEach((row, index) => {
t.deepEqual(log[index], EXPECTED_LOG[index], 'should have equal log at index ' + index )
})
t.deepEqual(log, EXPECTED_LOG)
})
2016-11-20 04:21:09 +08:00
test('search', t => {
const eson = jsonToEson({
"obj": {
"arr": [1,2, {"first":3,"last":4}]
},
"str": "hello world",
"nill": null,
"bool": false
})
const searchResult = search(eson, 'L')
const esonWithSearch = searchResult.eson
const matches = searchResult.matches
const active = searchResult.active
t.deepEqual(matches, [
{path: ['obj', 'arr', 2, 'last'], area: 'property'},
2017-11-03 21:23:04 +08:00
{path: ['str'], area: 'value'},
{path: ['nill'], area: 'property'},
{path: ['nill'], area: 'value'},
{path: ['bool'], area: 'property'},
{path: ['bool'], area: 'value'}
])
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')
2016-11-12 21:10:10 +08:00
t.deepEqual(esonWithSearch, expected)
2016-11-12 21:10:10 +08:00
})
test('nextSearchResult', t => {
const eson = jsonToEson({
"obj": {
"arr": [1,2, {"first":3,"last":4}]
},
"str": "hello world",
"nill": null,
"bool": false
})
const searchResult = search(eson, 'A')
t.deepEqual(searchResult.matches, [
{path: ['obj', 'arr'], area: 'property'},
{path: ['obj', 'arr', 2, 'last'], area: 'property'},
{path: ['bool'], area: 'value'}
])
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')
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')
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')
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')
})
test('previousSearchResult', t => {
const eson = jsonToEson({
"obj": {
"arr": [1,2, {"first":3,"last":4}]
},
"str": "hello world",
"nill": null,
"bool": false
})
const searchResult = search(eson, 'A')
t.deepEqual(searchResult.matches, [
{path: ['obj', 'arr'], area: 'property'},
{path: ['obj', 'arr', 2, 'last'], area: 'property'},
{path: ['bool'], area: 'value'}
])
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')
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')
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')
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')
})
test('selection (object)', t => {
2017-12-14 23:40:55 +08:00
const eson = jsonToEson({
"obj": {
"arr": [1,2, {"first":3,"last":4}]
},
"str": "hello world",
"nill": null,
"bool": false
})
const selection = {
2017-11-22 17:12:59 +08:00
start: ['obj', 'arr', '2', 'last'],
end: ['nill']
}
2017-12-14 23:40:55 +08:00
const actual = applySelection(eson, selection)
2017-12-14 23:40:55 +08:00
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)
2017-12-14 23:40:55 +08:00
// test whether old selection results are cleaned up
const selection2 = {
start: ['nill'],
end: ['bool']
}
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)
})
test('selection (array)', t => {
2017-12-14 23:40:55 +08:00
const eson = jsonToEson({
"obj": {
"arr": [1,2, {"first":3,"last":4}]
},
"str": "hello world",
"nill": null,
"bool": false
})
const selection = {
2017-11-22 17:12:59 +08:00
start: ['obj', 'arr', '1'],
end: ['obj', 'arr', '0'] // note the "wrong" order of start and end
}
2017-12-14 23:40:55 +08:00
const actual = applySelection(eson, selection)
2017-12-14 23:40:55 +08:00
let expected = eson
expected = setIn(expected, ['obj', 'arr', '0', '_meta', 'selected'], SELECTED_END)
expected = setIn(expected, ['obj', 'arr', '1', '_meta', 'selected'], SELECTED)
t.deepEqual(actual, expected)
})
test('selection (value)', t => {
2017-12-14 23:40:55 +08:00
const eson = jsonToEson({
"obj": {
"arr": [1,2, {"first":3,"last":4}]
},
"str": "hello world",
"nill": null,
"bool": false
})
const selection = {
2017-11-22 17:12:59 +08:00
start: ['obj', 'arr', '2', 'first'],
end: ['obj', 'arr', '2', 'first']
}
2017-12-14 23:40:55 +08:00
const actual = applySelection(eson, selection)
const expected = setIn(eson, ['obj', 'arr', '2', 'first', '_meta', 'selected'], SELECTED_END)
t.deepEqual(actual, expected)
})
2017-09-29 17:08:44 +08:00
test('selection (node)', t => {
2017-12-14 23:40:55 +08:00
const eson = jsonToEson({
"obj": {
"arr": [1,2, {"first":3,"last":4}]
},
"str": "hello world",
"nill": null,
"bool": false
})
const selection = {
2017-11-22 17:12:59 +08:00
start: ['obj', 'arr'],
end: ['obj', 'arr']
}
2017-12-14 23:40:55 +08:00
const actual = applySelection(eson, selection)
const expected = setIn(eson, ['obj', 'arr', '_meta', 'selected'], SELECTED_END)
t.deepEqual(actual, expected)
})
test('pathsFromSelection (object)', t => {
const selection = {
2017-11-22 17:12:59 +08:00
start: ['obj', 'arr', '2', 'last'],
end: ['nill']
}
t.deepEqual(pathsFromSelection(ESON1, selection), [
['obj'],
['str'],
['nill']
])
})
test('pathsFromSelection (array)', t => {
const selection = {
2017-11-22 17:12:59 +08:00
start: ['obj', 'arr', '1'],
end: ['obj', 'arr', '0'] // note the "wrong" order of start and end
}
t.deepEqual(pathsFromSelection(ESON1, selection), [
['obj', 'arr', '0'],
['obj', 'arr', '1']
])
})
test('pathsFromSelection (value)', t => {
const selection = {
2017-11-22 17:12:59 +08:00
start: ['obj', 'arr', '2', 'first'],
end: ['obj', 'arr', '2', 'first']
}
t.deepEqual(pathsFromSelection(ESON1, selection), [
['obj', 'arr', '2', 'first'],
])
})
2017-11-22 17:12:59 +08:00
test('pathsFromSelection (before)', t => {
const selection = {
before: ['obj', 'arr', '2', 'first']
}
t.deepEqual(pathsFromSelection(ESON1, selection), [])
})
test('pathsFromSelection (after)', t => {
const selection = {
after: ['obj', 'arr', '2', 'first']
}
t.deepEqual(pathsFromSelection(ESON1, selection), [])
})
// 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)
}
}
}
}
2017-11-30 04:52:18 +08:00
// 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)
}
// helper function to print JSON in the console
function printJSON (json, message = null) {
if (message) {
console.log(message)
}
console.log(JSON.stringify(json, null, 2))
}
// helper function to load a JSON file
function loadJSON (filename) {
return JSON.parse(readFileSync(__dirname + '/' + filename, 'utf-8'))
}