Implemented support for selection in eson
This commit is contained in:
parent
f3313158db
commit
943d721d84
|
@ -52,7 +52,7 @@ export default class JSONNode extends Component {
|
|||
if (data.expanded) {
|
||||
if (data.props.length > 0) {
|
||||
const props = data.props.map(prop => {
|
||||
return h('li', {key: prop.id},
|
||||
return h('li', { key: prop.id, className: (prop.value.selected ? ' jsoneditor-selected' : '') },
|
||||
h(this.constructor, {
|
||||
path: this.props.path.concat(prop.name),
|
||||
prop,
|
||||
|
@ -98,7 +98,7 @@ export default class JSONNode extends Component {
|
|||
if (data.expanded) {
|
||||
if (data.items.length > 0) {
|
||||
const items = data.items.map((item, index) => {
|
||||
return h('li', {key : item.id},
|
||||
return h('li', { key : item.id, className: (item.value.selected ? ' jsoneditor-selected' : '')},
|
||||
h(this.constructor, {
|
||||
path: this.props.path.concat(String(index)),
|
||||
index,
|
||||
|
|
|
@ -11,7 +11,8 @@ import { enrichSchemaError } from '../utils/schemaUtils'
|
|||
import {
|
||||
jsonToEson, esonToJson, toEsonPath, pathExists,
|
||||
expand, expandPath, addErrors,
|
||||
search, addSearchResults, nextSearchResult, previousSearchResult,
|
||||
search, applySearchResults, nextSearchResult, previousSearchResult,
|
||||
applySelection,
|
||||
compileJSONPointer
|
||||
} from '../eson'
|
||||
import { patchEson } from '../patchEson'
|
||||
|
@ -93,6 +94,11 @@ export default class TreeMode extends Component {
|
|||
search: {
|
||||
text: '',
|
||||
active: null // active search result
|
||||
},
|
||||
|
||||
selection: {
|
||||
start: null, // ESONPointer
|
||||
end: null, // ESONPointer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -162,7 +168,10 @@ export default class TreeMode extends Component {
|
|||
// TODO: performance improvements in search would be nice though it's acceptable right now
|
||||
const searchResults = this.state.search.text ? search(data, this.state.search.text) : null
|
||||
if (searchResults) {
|
||||
data = addSearchResults(data, searchResults, this.state.search.active)
|
||||
data = applySearchResults(data, searchResults, this.state.search.active)
|
||||
}
|
||||
if (this.state.selection) {
|
||||
data = applySelection(data, this.state.selection)
|
||||
}
|
||||
|
||||
return h('div', {
|
||||
|
@ -178,7 +187,7 @@ export default class TreeMode extends Component {
|
|||
className: 'jsoneditor-contents jsoneditor-tree-contents',
|
||||
id: this.id
|
||||
},
|
||||
h('ul', {className: 'jsoneditor-list jsoneditor-root'},
|
||||
h('ul', {className: 'jsoneditor-list jsoneditor-root' + (data.selected ? ' jsoneditor-selected' : '')},
|
||||
h(Node, {
|
||||
data,
|
||||
events: state.events,
|
||||
|
|
88
src/eson.js
88
src/eson.js
|
@ -5,13 +5,13 @@
|
|||
* All functions are pure and don't mutate the ESON.
|
||||
*/
|
||||
|
||||
import { setIn, getIn } from './utils/immutabilityHelpers'
|
||||
import { setIn, getIn, updateIn } from './utils/immutabilityHelpers'
|
||||
import { isObject } from './utils/typeUtils'
|
||||
import { last, allButLast } from './utils/arrayUtils'
|
||||
import isEqual from 'lodash/isEqual'
|
||||
|
||||
import type {
|
||||
ESON, ESONObject, ESONArrayItem, ESONPointer, ESONType, ESONPath,
|
||||
ESON, ESONObject, ESONArrayItem, ESONPointer, ESONSelection, ESONType, ESONPath,
|
||||
Path,
|
||||
JSONPath, JSONType
|
||||
} from './types'
|
||||
|
@ -222,7 +222,7 @@ export function search (eson: ESON, text: string): ESONPointer[] {
|
|||
const parentPath = allButLast(path)
|
||||
const parent = getIn(eson, toEsonPath(eson, parentPath))
|
||||
if (parent.type === 'Object') {
|
||||
results.push({path, type: 'property'})
|
||||
results.push({path, field: 'property'})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -230,7 +230,7 @@ export function search (eson: ESON, text: string): ESONPointer[] {
|
|||
// check value
|
||||
if (value.type === 'value') {
|
||||
if (containsCaseInsensitive(value.value, text)) {
|
||||
results.push({path, type: 'value'})
|
||||
results.push({path, field: 'value'})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -290,17 +290,17 @@ export function previousSearchResult (searchResults: ESONPointer[], current: ESO
|
|||
/**
|
||||
* Merge searchResults into the eson object
|
||||
*/
|
||||
export function addSearchResults (eson: ESON, searchResults: ESONPointer[], activeSearchResult: ESONPointer) {
|
||||
export function applySearchResults (eson: ESON, searchResults: ESONPointer[], activeSearchResult: ESONPointer) {
|
||||
let updatedEson = eson
|
||||
|
||||
searchResults.forEach(function (searchResult) {
|
||||
if (searchResult.type === 'value') {
|
||||
if (searchResult.field === 'value') {
|
||||
const esonPath = toEsonPath(updatedEson, searchResult.path).concat('searchResult')
|
||||
const value = isEqual(searchResult, activeSearchResult) ? 'active' : 'normal'
|
||||
updatedEson = setIn(updatedEson, esonPath, value)
|
||||
}
|
||||
|
||||
if (searchResult.type === 'property') {
|
||||
if (searchResult.field === 'property') {
|
||||
const esonPath = toEsonPath(updatedEson, searchResult.path)
|
||||
const propertyPath = allButLast(esonPath).concat('searchResult')
|
||||
const value = isEqual(searchResult, activeSearchResult) ? 'active' : 'normal'
|
||||
|
@ -312,13 +312,65 @@ export function addSearchResults (eson: ESON, searchResults: ESONPointer[], acti
|
|||
}
|
||||
|
||||
/**
|
||||
* Do a case insensitive search for a search text in a text
|
||||
* @param {String} text
|
||||
* @param {String} search
|
||||
* @return {boolean} Returns true if `search` is found in `text`
|
||||
* Merge searchResults into the eson object
|
||||
*/
|
||||
export function containsCaseInsensitive (text: string, search: string): boolean {
|
||||
return String(text).toLowerCase().indexOf(search.toLowerCase()) !== -1
|
||||
export function applySelection (eson: ESON, selection: ESONSelection) {
|
||||
if (!selection || !selection.start || !selection.end) {
|
||||
return eson
|
||||
}
|
||||
|
||||
// find the parent node shared by both start and end of the selection
|
||||
const rootPath = findSharedPath(selection.start.path, selection.end.path)
|
||||
const rootEsonPath = toEsonPath(eson, rootPath)
|
||||
|
||||
if (rootPath.length === selection.start.path.length || rootPath.length === selection.end.path.length) {
|
||||
// select the root itself
|
||||
return setIn(eson, rootEsonPath.concat(['selected']), true)
|
||||
}
|
||||
else {
|
||||
// select multiple childs of an object or array
|
||||
return updateIn(eson, rootEsonPath, (root) => {
|
||||
if (root.type === 'Object') {
|
||||
const startIndex = findPropertyIndex(root, selection.start.path[rootPath.length])
|
||||
const endIndex = findPropertyIndex(root, selection.end.path[rootPath.length])
|
||||
const minIndex = Math.min(startIndex, endIndex)
|
||||
const maxIndex = Math.max(startIndex, endIndex) + 1 // include max index itself
|
||||
|
||||
const propsBefore = root.props.slice(0, minIndex)
|
||||
const propsUpdated = root.props.slice(minIndex, maxIndex)
|
||||
.map((prop, index) => setIn(prop, ['value', 'selected'], true))
|
||||
const propsAfter = root.props.slice(maxIndex)
|
||||
|
||||
return setIn(root, ['props'], propsBefore.concat(propsUpdated, propsAfter))
|
||||
}
|
||||
else if (root.type === 'Array') {
|
||||
const startIndex = parseInt(selection.start.path[rootPath.length])
|
||||
const endIndex = parseInt(selection.end.path[rootPath.length])
|
||||
const minIndex = Math.min(startIndex, endIndex)
|
||||
const maxIndex = Math.max(startIndex, endIndex) + 1 // include max index itself
|
||||
|
||||
const itemsBefore = root.items.slice(0, minIndex)
|
||||
const itemsUpdated = root.items.slice(minIndex, maxIndex)
|
||||
.map((item, index) => setIn(item, ['value', 'selected'], true))
|
||||
const itemsAfter = root.items.slice(maxIndex)
|
||||
|
||||
return setIn(root, ['items'], itemsBefore.concat(itemsUpdated, itemsAfter))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the common path of two paths.
|
||||
* For example findCommonRoot(['arr', '1', 'name'], ['arr', '1', 'address', 'contact']) returns ['arr', '1']
|
||||
*/
|
||||
function findSharedPath (path1: JSONPath, path2: JSONPath): JSONPath {
|
||||
let i = 0;
|
||||
while (i < path1.length && path1[i] === path2[i]) {
|
||||
i++;
|
||||
}
|
||||
|
||||
return path1.slice(0, i)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -520,6 +572,16 @@ export function compileJSONPointer (path: Path) {
|
|||
|
||||
// TODO: move getId and createUniqueId to a separate file
|
||||
|
||||
/**
|
||||
* Do a case insensitive search for a search text in a text
|
||||
* @param {String} text
|
||||
* @param {String} search
|
||||
* @return {boolean} Returns true if `search` is found in `text`
|
||||
*/
|
||||
export function containsCaseInsensitive (text: string, search: string): boolean {
|
||||
return String(text).toLowerCase().indexOf(search.toLowerCase()) !== -1
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new "unique" id. Id's are created from an incremental counter.
|
||||
* @return {number}
|
||||
|
|
|
@ -256,6 +256,10 @@ div.jsoneditor-value.jsoneditor-empty::after {
|
|||
content: 'value';
|
||||
}
|
||||
|
||||
.jsoneditor-selected {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.jsoneditor-highlight {
|
||||
background-color: yellow;
|
||||
}
|
||||
|
|
14
src/types.js
14
src/types.js
|
@ -33,7 +33,7 @@ export type JSONArrayType = JSONType[]
|
|||
/********************** TYPES FOR THE ESON OBJECT MODEL *************************/
|
||||
|
||||
export type SearchResultStatus = 'normal' | 'active'
|
||||
export type ESONPointerType = 'value' | 'property'
|
||||
export type ESONPointerField = 'value' | 'property'
|
||||
|
||||
export type ESONObjectProperty = {
|
||||
id: number,
|
||||
|
@ -50,18 +50,21 @@ export type ESONArrayItem = {
|
|||
export type ESONObject = {
|
||||
type: 'Object',
|
||||
expanded?: boolean,
|
||||
selected?: boolean,
|
||||
props: ESONObjectProperty[]
|
||||
}
|
||||
|
||||
export type ESONArray = {
|
||||
type: 'Array',
|
||||
expanded?: boolean,
|
||||
selected?: boolean,
|
||||
items: ESONArrayItem[]
|
||||
}
|
||||
|
||||
export type ESONValue = {
|
||||
type: 'value' | 'string',
|
||||
value?: any,
|
||||
selected?: boolean,
|
||||
searchResult?: SearchResultStatus
|
||||
}
|
||||
|
||||
|
@ -74,8 +77,13 @@ export type JSONPath = string[]
|
|||
export type ESONPath = string[]
|
||||
|
||||
export type ESONPointer = {
|
||||
path: ESONPath,
|
||||
type: ESONPointerType
|
||||
path: JSONPath, // TODO: change path to an ESONPath?
|
||||
field?: ESONPointerField
|
||||
}
|
||||
|
||||
export type ESONSelection = {
|
||||
start: ESONPointer,
|
||||
end: ESONPointer
|
||||
}
|
||||
|
||||
// TODO: ESONPointer.path is an array, JSONSchemaError.path is a string -> make this consistent
|
||||
|
|
|
@ -2,10 +2,13 @@ import test from 'ava';
|
|||
import {
|
||||
jsonToEson, esonToJson, pathExists, transform, traverse,
|
||||
parseJSONPointer, compileJSONPointer,
|
||||
expand, addErrors, search, addSearchResults, nextSearchResult, previousSearchResult
|
||||
expand, addErrors, search, applySearchResults, nextSearchResult, previousSearchResult,
|
||||
applySelection
|
||||
} from '../src/eson'
|
||||
|
||||
|
||||
// TODO: move all JSON documents in separate json files to keep the test readable?
|
||||
|
||||
const JSON_EXAMPLE = {
|
||||
obj: {
|
||||
arr: [1,2, {first:3,last:4}]
|
||||
|
@ -15,7 +18,7 @@ const JSON_EXAMPLE = {
|
|||
bool: false
|
||||
}
|
||||
|
||||
const JSON_DATA_EXAMPLE = {
|
||||
const ESON = {
|
||||
type: 'Object',
|
||||
expanded: true,
|
||||
props: [
|
||||
|
@ -105,34 +108,9 @@ const JSON_DATA_EXAMPLE = {
|
|||
]
|
||||
}
|
||||
|
||||
const JSON_DUPLICATE_PROPERTY_EXAMPLE = {
|
||||
type: 'Object',
|
||||
expanded: true,
|
||||
props: [
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'name',
|
||||
value: {
|
||||
type: 'value',
|
||||
expanded: true,
|
||||
value: 'Joe'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'name',
|
||||
value: {
|
||||
type: 'value',
|
||||
expanded: true,
|
||||
value: 'Joe'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
// TODO: instead of all slightly different copies of ESON, built them up via setIn, updateIn based on ESON
|
||||
|
||||
// TODO: instead of all slightly different copies of JSON_DATA_EXAMPLE, built them up via setIn, updateIn based on JSON_DATA_EXAMPLE
|
||||
|
||||
const JSON_DATA_EXAMPLE_COLLAPSED_1 = {
|
||||
const ESON_COLLAPSED_1 = {
|
||||
type: 'Object',
|
||||
expanded: true,
|
||||
props: [
|
||||
|
@ -223,7 +201,7 @@ const JSON_DATA_EXAMPLE_COLLAPSED_1 = {
|
|||
]
|
||||
}
|
||||
|
||||
const JSON_DATA_EXAMPLE_COLLAPSED_2 = {
|
||||
const ESON_COLLAPSED_2 = {
|
||||
type: 'Object',
|
||||
expanded: true,
|
||||
props: [
|
||||
|
@ -314,7 +292,7 @@ const JSON_DATA_EXAMPLE_COLLAPSED_2 = {
|
|||
}
|
||||
|
||||
// after search for 'L' (case insensitive)
|
||||
const JSON_DATA_EXAMPLE_SEARCH_L = {
|
||||
const ESON_SEARCH_L = {
|
||||
type: 'Object',
|
||||
expanded: true,
|
||||
props: [
|
||||
|
@ -410,7 +388,374 @@ const JSON_DATA_EXAMPLE_SEARCH_L = {
|
|||
]
|
||||
}
|
||||
|
||||
const JSON_DATA_SMALL = {
|
||||
const ESON_SELECTED_OBJECT = {
|
||||
type: 'Object',
|
||||
expanded: true,
|
||||
props: [
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'obj',
|
||||
value: {
|
||||
type: 'Object',
|
||||
expanded: true,
|
||||
selected: true,
|
||||
props: [
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'arr',
|
||||
value: {
|
||||
type: 'Array',
|
||||
expanded: true,
|
||||
items: [
|
||||
{
|
||||
id: '[ID]',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '[ID]',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '[ID]',
|
||||
value: {
|
||||
type: 'Object',
|
||||
expanded: true,
|
||||
props: [
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'first',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: 3
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'last',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: 4
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'str',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: 'hello world',
|
||||
selected: true
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'nill',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: null,
|
||||
selected: true
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'bool',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const ESON_SELECTED_ARRAY = {
|
||||
type: 'Object',
|
||||
expanded: true,
|
||||
props: [
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'obj',
|
||||
value: {
|
||||
type: 'Object',
|
||||
expanded: true,
|
||||
props: [
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'arr',
|
||||
value: {
|
||||
type: 'Array',
|
||||
expanded: true,
|
||||
items: [
|
||||
{
|
||||
id: '[ID]',
|
||||
value: {
|
||||
type: 'value',
|
||||
selected: true,
|
||||
value: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '[ID]',
|
||||
value: {
|
||||
type: 'value',
|
||||
selected: true,
|
||||
value: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '[ID]',
|
||||
value: {
|
||||
type: 'Object',
|
||||
expanded: true,
|
||||
props: [
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'first',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: 3
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'last',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: 4
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'str',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: 'hello world'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'nill',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: null
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'bool',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const ESON_SELECTED_VALUE = {
|
||||
type: 'Object',
|
||||
expanded: true,
|
||||
props: [
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'obj',
|
||||
value: {
|
||||
type: 'Object',
|
||||
expanded: true,
|
||||
props: [
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'arr',
|
||||
value: {
|
||||
type: 'Array',
|
||||
expanded: true,
|
||||
items: [
|
||||
{
|
||||
id: '[ID]',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '[ID]',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '[ID]',
|
||||
value: {
|
||||
type: 'Object',
|
||||
expanded: true,
|
||||
props: [
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'first',
|
||||
value: {
|
||||
type: 'value',
|
||||
selected: true,
|
||||
value: 3
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'last',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: 4
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'str',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: 'hello world'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'nill',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: null
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'bool',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const ESON_SELECTED_PARENT = {
|
||||
type: 'Object',
|
||||
expanded: true,
|
||||
props: [
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'obj',
|
||||
value: {
|
||||
type: 'Object',
|
||||
expanded: true,
|
||||
props: [
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'arr',
|
||||
value: {
|
||||
type: 'Array',
|
||||
expanded: true,
|
||||
items: [
|
||||
{
|
||||
id: '[ID]',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '[ID]',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '[ID]',
|
||||
value: {
|
||||
type: 'Object',
|
||||
expanded: true,
|
||||
selected: true,
|
||||
props: [
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'first',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: 3
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'last',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: 4
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'str',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: 'hello world'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'nill',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: null
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'bool',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const ESON_SMALL = {
|
||||
type: 'Object',
|
||||
props: [
|
||||
{
|
||||
|
@ -455,7 +800,7 @@ const JSON_SCHEMA_ERRORS = [
|
|||
{dataPath: '/nill', message: 'Null expected'}
|
||||
]
|
||||
|
||||
const JSON_DATA_EXAMPLE_ERRORS = {
|
||||
const ESON_ERRORS = {
|
||||
type: 'Object',
|
||||
expanded: true,
|
||||
props: [
|
||||
|
@ -555,17 +900,17 @@ test('jsonToEson', t => {
|
|||
const ESON = jsonToEson(JSON_EXAMPLE, expand, [])
|
||||
replaceIds(ESON)
|
||||
|
||||
t.deepEqual(ESON, JSON_DATA_EXAMPLE)
|
||||
t.deepEqual(ESON, ESON)
|
||||
})
|
||||
|
||||
test('esonToJson', t => {
|
||||
t.deepEqual(esonToJson(JSON_DATA_EXAMPLE), JSON_EXAMPLE)
|
||||
t.deepEqual(esonToJson(ESON), JSON_EXAMPLE)
|
||||
})
|
||||
|
||||
test('expand a single path', t => {
|
||||
const collapsed = expand(JSON_DATA_EXAMPLE, ['obj', 'arr', 2], false)
|
||||
const collapsed = expand(ESON, ['obj', 'arr', 2], false)
|
||||
|
||||
t.deepEqual(collapsed, JSON_DATA_EXAMPLE_COLLAPSED_1)
|
||||
t.deepEqual(collapsed, ESON_COLLAPSED_1)
|
||||
})
|
||||
|
||||
test('expand a callback', t => {
|
||||
|
@ -573,9 +918,9 @@ test('expand a callback', t => {
|
|||
return path.length >= 1
|
||||
}
|
||||
const expanded = false
|
||||
const collapsed = expand(JSON_DATA_EXAMPLE, callback, expanded)
|
||||
const collapsed = expand(ESON, callback, expanded)
|
||||
|
||||
t.deepEqual(collapsed, JSON_DATA_EXAMPLE_COLLAPSED_2)
|
||||
t.deepEqual(collapsed, ESON_COLLAPSED_2)
|
||||
})
|
||||
|
||||
test('expand a callback should not change the object when nothing happens', t => {
|
||||
|
@ -583,16 +928,16 @@ test('expand a callback should not change the object when nothing happens', t =>
|
|||
return false
|
||||
}
|
||||
const expanded = false
|
||||
const collapsed = expand(JSON_DATA_EXAMPLE, callback, expanded)
|
||||
const collapsed = expand(ESON, callback, expanded)
|
||||
|
||||
t.is(collapsed, JSON_DATA_EXAMPLE)
|
||||
t.is(collapsed, ESON)
|
||||
})
|
||||
|
||||
test('pathExists', t => {
|
||||
t.is(pathExists(JSON_DATA_EXAMPLE, ['obj', 'arr', 2, 'first']), true)
|
||||
t.is(pathExists(JSON_DATA_EXAMPLE, ['obj', 'foo']), false)
|
||||
t.is(pathExists(JSON_DATA_EXAMPLE, ['obj', 'foo', 'bar']), false)
|
||||
t.is(pathExists(JSON_DATA_EXAMPLE, []), true)
|
||||
t.is(pathExists(ESON, ['obj', 'arr', 2, 'first']), true)
|
||||
t.is(pathExists(ESON, ['obj', 'foo']), false)
|
||||
t.is(pathExists(ESON, ['obj', 'foo', 'bar']), false)
|
||||
t.is(pathExists(ESON, []), true)
|
||||
})
|
||||
|
||||
test('parseJSONPointer', t => {
|
||||
|
@ -612,16 +957,16 @@ test('compileJSONPointer', t => {
|
|||
})
|
||||
|
||||
test('add and remove errors', t => {
|
||||
const dataWithErrors = addErrors(JSON_DATA_EXAMPLE, JSON_SCHEMA_ERRORS)
|
||||
t.deepEqual(dataWithErrors, JSON_DATA_EXAMPLE_ERRORS)
|
||||
const dataWithErrors = addErrors(ESON, JSON_SCHEMA_ERRORS)
|
||||
t.deepEqual(dataWithErrors, ESON_ERRORS)
|
||||
})
|
||||
|
||||
test('transform', t => {
|
||||
// {obj: {a: 2}, arr: [3]}
|
||||
|
||||
let log = []
|
||||
const transformed = transform(JSON_DATA_SMALL, function (value, path, root) {
|
||||
t.is(root, JSON_DATA_SMALL)
|
||||
const transformed = transform(ESON_SMALL, function (value, path, root) {
|
||||
t.is(root, ESON_SMALL)
|
||||
|
||||
log.push([value, path, root])
|
||||
|
||||
|
@ -637,22 +982,22 @@ test('transform', t => {
|
|||
// console.log('transformed', JSON.stringify(transformed, null, 2))
|
||||
|
||||
const EXPECTED_LOG = [
|
||||
[JSON_DATA_SMALL, [], JSON_DATA_SMALL],
|
||||
[JSON_DATA_SMALL.props[0].value, ['obj'], JSON_DATA_SMALL],
|
||||
[JSON_DATA_SMALL.props[0].value.props[0].value, ['obj', 'a'], JSON_DATA_SMALL],
|
||||
[JSON_DATA_SMALL.props[1].value, ['arr'], JSON_DATA_SMALL],
|
||||
[JSON_DATA_SMALL.props[1].value.items[0].value, ['arr', '0'], JSON_DATA_SMALL],
|
||||
[ESON_SMALL, [], ESON_SMALL],
|
||||
[ESON_SMALL.props[0].value, ['obj'], ESON_SMALL],
|
||||
[ESON_SMALL.props[0].value.props[0].value, ['obj', 'a'], ESON_SMALL],
|
||||
[ESON_SMALL.props[1].value, ['arr'], ESON_SMALL],
|
||||
[ESON_SMALL.props[1].value.items[0].value, ['arr', '0'], ESON_SMALL],
|
||||
]
|
||||
|
||||
log.forEach((row, index) => {
|
||||
t.deepEqual(log[index], EXPECTED_LOG[index], 'should have equal log at index ' + index )
|
||||
})
|
||||
t.deepEqual(log, EXPECTED_LOG)
|
||||
t.not(transformed, JSON_DATA_SMALL)
|
||||
t.not(transformed.props[0].value, JSON_DATA_SMALL.props[0].value)
|
||||
t.not(transformed.props[0].value.props[0].value, JSON_DATA_SMALL.props[0].value.props[0].value)
|
||||
t.is(transformed.props[1].value, JSON_DATA_SMALL.props[1].value)
|
||||
t.is(transformed.props[1].value.items[0].value, JSON_DATA_SMALL.props[1].value.items[0].value)
|
||||
t.not(transformed, ESON_SMALL)
|
||||
t.not(transformed.props[0].value, ESON_SMALL.props[0].value)
|
||||
t.not(transformed.props[0].value.props[0].value, ESON_SMALL.props[0].value.props[0].value)
|
||||
t.is(transformed.props[1].value, ESON_SMALL.props[1].value)
|
||||
t.is(transformed.props[1].value.items[0].value, ESON_SMALL.props[1].value.items[0].value)
|
||||
|
||||
})
|
||||
|
||||
|
@ -660,8 +1005,8 @@ test('traverse', t => {
|
|||
// {obj: {a: 2}, arr: [3]}
|
||||
|
||||
let log = []
|
||||
const returnValue = traverse(JSON_DATA_SMALL, function (value, path, root) {
|
||||
t.is(root, JSON_DATA_SMALL)
|
||||
const returnValue = traverse(ESON_SMALL, function (value, path, root) {
|
||||
t.is(root, ESON_SMALL)
|
||||
|
||||
log.push([value, path, root])
|
||||
})
|
||||
|
@ -669,11 +1014,11 @@ test('traverse', t => {
|
|||
t.is(returnValue, undefined)
|
||||
|
||||
const EXPECTED_LOG = [
|
||||
[JSON_DATA_SMALL, [], JSON_DATA_SMALL],
|
||||
[JSON_DATA_SMALL.props[0].value, ['obj'], JSON_DATA_SMALL],
|
||||
[JSON_DATA_SMALL.props[0].value.props[0].value, ['obj', 'a'], JSON_DATA_SMALL],
|
||||
[JSON_DATA_SMALL.props[1].value, ['arr'], JSON_DATA_SMALL],
|
||||
[JSON_DATA_SMALL.props[1].value.items[0].value, ['arr', '0'], JSON_DATA_SMALL],
|
||||
[ESON_SMALL, [], ESON_SMALL],
|
||||
[ESON_SMALL.props[0].value, ['obj'], ESON_SMALL],
|
||||
[ESON_SMALL.props[0].value.props[0].value, ['obj', 'a'], ESON_SMALL],
|
||||
[ESON_SMALL.props[1].value, ['arr'], ESON_SMALL],
|
||||
[ESON_SMALL.props[1].value.items[0].value, ['arr', '0'], ESON_SMALL],
|
||||
]
|
||||
|
||||
log.forEach((row, index) => {
|
||||
|
@ -684,51 +1029,51 @@ test('traverse', t => {
|
|||
|
||||
|
||||
test('search', t => {
|
||||
const searchResults = search(JSON_DATA_EXAMPLE, 'L')
|
||||
const searchResults = search(ESON, 'L')
|
||||
// printJSON(searchResults)
|
||||
|
||||
t.deepEqual(searchResults, [
|
||||
{path: ['obj', 'arr', '2', 'last'], type: 'property'},
|
||||
{path: ['str'], type: 'value'},
|
||||
{path: ['nill'], type: 'property'},
|
||||
{path: ['nill'], type: 'value'},
|
||||
{path: ['bool'], type: 'property'},
|
||||
{path: ['bool'], type: 'value'}
|
||||
{path: ['obj', 'arr', '2', 'last'], field: 'property'},
|
||||
{path: ['str'], field: 'value'},
|
||||
{path: ['nill'], field: 'property'},
|
||||
{path: ['nill'], field: 'value'},
|
||||
{path: ['bool'], field: 'property'},
|
||||
{path: ['bool'], field: 'value'}
|
||||
])
|
||||
|
||||
const activeSearchResult = searchResults[0]
|
||||
const updatedData = addSearchResults(JSON_DATA_EXAMPLE, searchResults, activeSearchResult)
|
||||
const updatedData = applySearchResults(ESON, searchResults, activeSearchResult)
|
||||
// printJSON(updatedData)
|
||||
|
||||
t.deepEqual(updatedData, JSON_DATA_EXAMPLE_SEARCH_L)
|
||||
t.deepEqual(updatedData, ESON_SEARCH_L)
|
||||
})
|
||||
|
||||
test('nextSearchResult', t => {
|
||||
const searchResults = [
|
||||
{path: ['obj', 'arr', '2', 'last'], type: 'property'},
|
||||
{path: ['str'], type: 'value'},
|
||||
{path: ['nill'], type: 'property'},
|
||||
{path: ['nill'], type: 'value'},
|
||||
{path: ['bool'], type: 'property'},
|
||||
{path: ['bool'], type: 'value'}
|
||||
{path: ['obj', 'arr', '2', 'last'], field: 'property'},
|
||||
{path: ['str'], field: 'value'},
|
||||
{path: ['nill'], field: 'property'},
|
||||
{path: ['nill'], field: 'value'},
|
||||
{path: ['bool'], field: 'property'},
|
||||
{path: ['bool'], field: 'value'}
|
||||
]
|
||||
|
||||
t.deepEqual(nextSearchResult(searchResults,
|
||||
{path: ['nill'], type: 'property'}),
|
||||
{path: ['nill'], type: 'value'})
|
||||
{path: ['nill'], field: 'property'}),
|
||||
{path: ['nill'], field: 'value'})
|
||||
|
||||
// wrap around
|
||||
t.deepEqual(nextSearchResult(searchResults,
|
||||
{path: ['bool'], type: 'value'}),
|
||||
{path: ['obj', 'arr', '2', 'last'], type: 'property'})
|
||||
{path: ['bool'], field: 'value'}),
|
||||
{path: ['obj', 'arr', '2', 'last'], field: 'property'})
|
||||
|
||||
// return first when current is not found
|
||||
t.deepEqual(nextSearchResult(searchResults,
|
||||
{path: ['non', 'existing'], type: 'value'}),
|
||||
{path: ['obj', 'arr', '2', 'last'], type: 'property'})
|
||||
{path: ['non', 'existing'], field: 'value'}),
|
||||
{path: ['obj', 'arr', '2', 'last'], field: 'property'})
|
||||
|
||||
// return null when searchResults are empty
|
||||
t.deepEqual(nextSearchResult([], {path: ['non', 'existing'], type: 'value'}), null)
|
||||
t.deepEqual(nextSearchResult([], {path: ['non', 'existing'], field: 'value'}), null)
|
||||
})
|
||||
|
||||
test('previousSearchResult', t => {
|
||||
|
@ -759,6 +1104,49 @@ test('previousSearchResult', t => {
|
|||
t.deepEqual(previousSearchResult([], {path: ['non', 'existing'], type: 'value'}), null)
|
||||
})
|
||||
|
||||
test('selection (object)', t => {
|
||||
const selection = {
|
||||
start: {path: ['obj', 'arr', '2', 'last']},
|
||||
end: {path: ['nill']}
|
||||
}
|
||||
const result = applySelection(ESON, selection)
|
||||
|
||||
t.deepEqual(result, ESON_SELECTED_OBJECT)
|
||||
})
|
||||
|
||||
test('selection (array)', t => {
|
||||
const selection = {
|
||||
start: {path: ['obj', 'arr', '1']},
|
||||
end: {path: ['obj', 'arr', '0']} // note the "wrong" order of start and end
|
||||
}
|
||||
|
||||
const result = applySelection(ESON, selection)
|
||||
|
||||
t.deepEqual(result, ESON_SELECTED_ARRAY)
|
||||
})
|
||||
|
||||
test('selection (value)', t => {
|
||||
const selection = {
|
||||
start: {path: ['obj', 'arr', '2', 'first']},
|
||||
end: {path: ['obj', 'arr', '2', 'first']}
|
||||
}
|
||||
|
||||
const result = applySelection(ESON, selection)
|
||||
|
||||
t.deepEqual(result, ESON_SELECTED_VALUE)
|
||||
})
|
||||
|
||||
test('selection (single parent)', t => {
|
||||
const selection = {
|
||||
start: {path: ['obj', 'arr', '2']},
|
||||
end: {path: ['obj', 'arr', '2']}
|
||||
}
|
||||
|
||||
const result = applySelection(ESON, selection)
|
||||
|
||||
t.deepEqual(result, ESON_SELECTED_PARENT)
|
||||
})
|
||||
|
||||
// helper function to replace all id properties with a constant value
|
||||
function replaceIds (data, value = '[ID]') {
|
||||
if (data.type === 'Object') {
|
||||
|
|
Loading…
Reference in New Issue