Changed data model to items and props having an id
This commit is contained in:
parent
0f7c601635
commit
ab8d4b4be6
|
@ -53,10 +53,10 @@ 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.name},
|
||||
return h('li', {key: prop.id},
|
||||
h(this.constructor, {
|
||||
parent: this,
|
||||
prop: prop,
|
||||
prop,
|
||||
data: prop.value,
|
||||
options,
|
||||
events
|
||||
|
@ -78,6 +78,7 @@ export default class JSONNode extends Component {
|
|||
return h('div', {}, [node, childs])
|
||||
}
|
||||
|
||||
// TODO: extract a function renderChilds shared by both renderJSONObject and renderJSONArray (rename .props and .items to .childs?)
|
||||
renderJSONArray ({prop, index, data, options, events}) {
|
||||
const childCount = data.items.length
|
||||
const node = h('div', {name: compileJSONPointer(this.getPath()), key: 'node', className: 'jsoneditor-node jsoneditor-array'}, [
|
||||
|
@ -91,12 +92,12 @@ export default class JSONNode extends Component {
|
|||
let childs
|
||||
if (data.expanded) {
|
||||
if (data.items.length > 0) {
|
||||
const items = data.items.map((child, index) => {
|
||||
return h('li', {key : index},
|
||||
const items = data.items.map((item, index) => {
|
||||
return h('li', {key : item.id},
|
||||
h(this.constructor, {
|
||||
parent: this,
|
||||
index,
|
||||
data: child,
|
||||
data: item.value,
|
||||
options,
|
||||
events
|
||||
})
|
||||
|
@ -287,8 +288,6 @@ export default class JSONNode extends Component {
|
|||
target = target.parentNode
|
||||
}
|
||||
|
||||
console.log('value', this.props)
|
||||
|
||||
target.className = JSONNode.getValueClass(type, itsAnUrl, isEmpty) +
|
||||
JSONNode.getSearchResultClass(this.props.data.searchResult)
|
||||
target.title = itsAnUrl ? JSONNode.URL_TITLE : ''
|
||||
|
|
|
@ -118,16 +118,11 @@ export default class TreeMode extends Component {
|
|||
}
|
||||
|
||||
// enrich the data with search results
|
||||
// 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)
|
||||
}
|
||||
// TODO: moveTo active search result (not focus!)
|
||||
// if (this.state.search.active) {
|
||||
// data = addFocus(data, this.state.search.active)
|
||||
// }
|
||||
|
||||
// console.log('data', data)
|
||||
|
||||
return h('div', {
|
||||
className: `jsoneditor jsoneditor-mode-${props.mode}`,
|
||||
|
|
231
src/jsonData.js
231
src/jsonData.js
|
@ -9,7 +9,10 @@ import { setIn, updateIn, getIn, deleteIn, insertAt } from './utils/immutability
|
|||
import { isObject } from './utils/typeUtils'
|
||||
import isEqual from 'lodash/isEqual'
|
||||
|
||||
import type {JSONData, DataPointer, Path} from './types'
|
||||
import type {
|
||||
JSONData, ObjectData, ItemData, DataPointer, Path,
|
||||
JSONPatch, JSONPatchAction, PatchOptions, JSONPatchResult
|
||||
} from './types'
|
||||
|
||||
/**
|
||||
* Expand function which will expand all nodes
|
||||
|
@ -33,15 +36,21 @@ export function jsonToData (json, expand = expandAll, path = [], type = 'value')
|
|||
return {
|
||||
type: 'Array',
|
||||
expanded: expand(path),
|
||||
items: json.map((child, index) => jsonToData(child, expand, path.concat(index)))
|
||||
items: json.map((child, index) => {
|
||||
return {
|
||||
id: getId(), // TODO: use id based on index (only has to be unique within this array)
|
||||
value: jsonToData(child, expand, path.concat(index))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
else if (isObject(json)) {
|
||||
return {
|
||||
type: 'Object',
|
||||
expanded: expand(path),
|
||||
props: Object.keys(json).map(name => {
|
||||
props: Object.keys(json).map((name, index) => {
|
||||
return {
|
||||
id: getId(), // TODO: use id based on index (only has to be unique within this array)
|
||||
name,
|
||||
value: jsonToData(json[name], expand, path.concat(name))
|
||||
}
|
||||
|
@ -61,10 +70,10 @@ export function jsonToData (json, expand = expandAll, path = [], type = 'value')
|
|||
* @param {JSONData} data
|
||||
* @return {Object | Array | string | number | boolean | null} json
|
||||
*/
|
||||
export function dataToJson (data) {
|
||||
export function dataToJson (data: JSONData) {
|
||||
switch (data.type) {
|
||||
case 'Array':
|
||||
return data.items.map(dataToJson)
|
||||
return data.items.map(item => dataToJson(item.value))
|
||||
|
||||
case 'Object':
|
||||
const object = {}
|
||||
|
@ -87,7 +96,7 @@ export function dataToJson (data) {
|
|||
* @return {Path} dataPath
|
||||
* @private
|
||||
*/
|
||||
export function toDataPath (data, path) {
|
||||
export function toDataPath (data: JSONData, path: Path) : Path {
|
||||
if (path.length === 0) {
|
||||
return []
|
||||
}
|
||||
|
@ -95,14 +104,14 @@ export function toDataPath (data, path) {
|
|||
if (data.type === 'Array') {
|
||||
// index of an array
|
||||
const index = path[0]
|
||||
const item = data.items[index]
|
||||
const item = data.items[parseInt(index)]
|
||||
if (!item) {
|
||||
throw new Error('Array item "' + index + '" not found')
|
||||
}
|
||||
|
||||
return ['items', index].concat(toDataPath(item, path.slice(1)))
|
||||
return ['items', index, 'value'].concat(toDataPath(item.value, path.slice(1)))
|
||||
}
|
||||
else {
|
||||
else if (data.type === 'Object') {
|
||||
// object property. find the index of this property
|
||||
const index = findPropertyIndex(data, path[0])
|
||||
const prop = data.props[index]
|
||||
|
@ -111,19 +120,11 @@ export function toDataPath (data, path) {
|
|||
}
|
||||
|
||||
return ['props', index, 'value']
|
||||
.concat(toDataPath(prop && prop.value, path.slice(1)))
|
||||
.concat(toDataPath(prop.value, path.slice(1)))
|
||||
}
|
||||
else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a path of a JSON object into a path in the corresponding data model
|
||||
* @param {JSONData} data
|
||||
* @param {Path} path
|
||||
* @return {Path} dataPath
|
||||
* @private
|
||||
*/
|
||||
export function toPath (data, dataPath) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -134,7 +135,7 @@ export function toPath (data, dataPath) {
|
|||
* what nodes must be expanded
|
||||
* @return {{data: JSONData, revert: Object[], error: Error | null}}
|
||||
*/
|
||||
export function patchData (data, patch, expand = expandAll) {
|
||||
export function patchData (data: JSONData, patch: JSONPatchAction[], expand = expandAll) {
|
||||
let updatedData = data
|
||||
let revert = []
|
||||
|
||||
|
@ -142,6 +143,8 @@ export function patchData (data, patch, expand = expandAll) {
|
|||
const action = patch[i]
|
||||
const options = action.jsoneditor
|
||||
|
||||
// TODO: check whether action.op and action.path exist
|
||||
|
||||
switch (action.op) {
|
||||
case 'add': {
|
||||
const path = parseJSONPointer(action.path)
|
||||
|
@ -172,6 +175,14 @@ export function patchData (data, patch, expand = expandAll) {
|
|||
}
|
||||
|
||||
case 'copy': {
|
||||
if (!action.from) {
|
||||
return {
|
||||
data,
|
||||
revert: [],
|
||||
error: new Error('Property "from" expected in copy action ' + JSON.stringify(action))
|
||||
}
|
||||
}
|
||||
|
||||
const result = copy(updatedData, action.path, action.from, options)
|
||||
updatedData = result.data
|
||||
revert = result.revert.concat(revert)
|
||||
|
@ -180,6 +191,14 @@ export function patchData (data, patch, expand = expandAll) {
|
|||
}
|
||||
|
||||
case 'move': {
|
||||
if (!action.from) {
|
||||
return {
|
||||
data,
|
||||
revert: [],
|
||||
error: new Error('Property "from" expected in move action ' + JSON.stringify(action))
|
||||
}
|
||||
}
|
||||
|
||||
const result = move(updatedData, action.path, action.from, options)
|
||||
updatedData = result.data
|
||||
revert = result.revert.concat(revert)
|
||||
|
@ -224,7 +243,7 @@ export function patchData (data, patch, expand = expandAll) {
|
|||
* @param {JSONData} value
|
||||
* @return {{data: JSONData, revert: JSONPatch}}
|
||||
*/
|
||||
export function replace (data, path, value) {
|
||||
export function replace (data: JSONData, path: Path, value: JSONData) {
|
||||
const dataPath = toDataPath(data, path)
|
||||
const oldValue = getIn(data, dataPath)
|
||||
|
||||
|
@ -247,7 +266,7 @@ export function replace (data, path, value) {
|
|||
* @param {string} path
|
||||
* @return {{data: JSONData, revert: JSONPatch}}
|
||||
*/
|
||||
export function remove (data, path) {
|
||||
export function remove (data: JSONData, path: string) {
|
||||
// console.log('remove', path)
|
||||
const pathArray = parseJSONPointer(path)
|
||||
|
||||
|
@ -259,6 +278,9 @@ export function remove (data, path) {
|
|||
if (parent.type === 'Array') {
|
||||
const dataPath = toDataPath(data, pathArray)
|
||||
|
||||
// remove the 'value' property, we want to remove the whole item from the items array
|
||||
dataPath.pop()
|
||||
|
||||
return {
|
||||
data: deleteIn(data, dataPath),
|
||||
revert: [{
|
||||
|
@ -275,7 +297,9 @@ export function remove (data, path) {
|
|||
const dataPath = toDataPath(data, pathArray)
|
||||
const prop = pathArray[pathArray.length - 1]
|
||||
|
||||
dataPath.pop() // remove the 'value' property, we want to remove the whole object property
|
||||
// remove the 'value' property, we want to remove the whole object property from props
|
||||
dataPath.pop()
|
||||
|
||||
return {
|
||||
data: deleteIn(data, dataPath),
|
||||
revert: [{
|
||||
|
@ -298,12 +322,12 @@ export function remove (data, path) {
|
|||
* @param {JSONPatch} patch
|
||||
* @return {Array}
|
||||
*/
|
||||
export function simplifyPatch(patch) {
|
||||
export function simplifyPatch(patch: JSONPatch) {
|
||||
const simplifiedPatch = []
|
||||
const paths = {}
|
||||
|
||||
// loop over the patch from last to first
|
||||
for (var i = patch.length - 1; i >= 0; i--) {
|
||||
for (let i = patch.length - 1; i >= 0; i--) {
|
||||
const action = patch[i]
|
||||
if (action.op === 'test') {
|
||||
// ignore test actions
|
||||
|
@ -331,7 +355,7 @@ export function simplifyPatch(patch) {
|
|||
* @return {{data: JSONData, revert: JSONPatch}}
|
||||
* @private
|
||||
*/
|
||||
export function add (data, path, value, options) {
|
||||
export function add (data: JSONData, path: string, value: JSONData, options) {
|
||||
const pathArray = parseJSONPointer(path)
|
||||
const parentPath = pathArray.slice(0, pathArray.length - 1)
|
||||
const dataPath = toDataPath(data, parentPath)
|
||||
|
@ -341,16 +365,29 @@ export function add (data, path, value, options) {
|
|||
|
||||
let updatedData
|
||||
if (parent.type === 'Array') {
|
||||
updatedData = insertAt(data, dataPath.concat('items', prop), value)
|
||||
const newItem = {
|
||||
id: getId(), // TODO: create a unique id within current id's instead of using a global, ever incrementing id
|
||||
value
|
||||
}
|
||||
updatedData = insertAt(data, dataPath.concat('items', prop), newItem)
|
||||
}
|
||||
else { // parent.type === 'Object'
|
||||
updatedData = updateIn(data, dataPath, (object) => {
|
||||
const newProp = { name: prop, value }
|
||||
const index = (options && typeof options.before === 'string')
|
||||
? findPropertyIndex(object, options.before) // insert before
|
||||
: object.props.length // append
|
||||
const existingIndex = findPropertyIndex(object, prop)
|
||||
if (existingIndex !== -1) {
|
||||
// replace existing item
|
||||
return setIn(object, ['props', existingIndex, 'value'], value)
|
||||
}
|
||||
else {
|
||||
// insert new item
|
||||
const newId = getId()
|
||||
const newProp = { id: newId, name: prop, value }
|
||||
const index = (options && typeof options.before === 'string')
|
||||
? findPropertyIndex(object, options.before) // insert before
|
||||
: object.props.length // append
|
||||
|
||||
return insertAt(object, ['props', index], newProp)
|
||||
return insertAt(object, ['props', index], newProp)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -387,7 +424,7 @@ export function add (data, path, value, options) {
|
|||
* @return {{data: JSONData, revert: JSONPatch}}
|
||||
* @private
|
||||
*/
|
||||
export function copy (data, path, from, options) {
|
||||
export function copy (data: JSONData, path: string, from: string, options) {
|
||||
const value = getIn(data, toDataPath(data, parseJSONPointer(from)))
|
||||
|
||||
return add(data, path, value, options)
|
||||
|
@ -402,7 +439,7 @@ export function copy (data, path, from, options) {
|
|||
* @return {{data: JSONData, revert: JSONPatch}}
|
||||
* @private
|
||||
*/
|
||||
export function move (data, path, from, options) {
|
||||
export function move (data: JSONData, path: string, from: string, options) {
|
||||
const fromArray = parseJSONPointer(from)
|
||||
const dataValue = getIn(data, toDataPath(data, fromArray))
|
||||
|
||||
|
@ -446,7 +483,7 @@ export function move (data, path, from, options) {
|
|||
* @param {*} value
|
||||
* @return {null | Error} Returns an error when the tests, returns null otherwise
|
||||
*/
|
||||
export function test (data, path, value) {
|
||||
export function test (data: JSONData, path: string, value: any) {
|
||||
if (value === undefined) {
|
||||
return new Error('Test failed, no value provided')
|
||||
}
|
||||
|
@ -462,6 +499,8 @@ export function test (data, path, value) {
|
|||
}
|
||||
}
|
||||
|
||||
type ExpandCallback = (Path) => boolean
|
||||
|
||||
/**
|
||||
* Expand or collapse one or multiple items or properties
|
||||
* @param {JSONData} data
|
||||
|
@ -472,7 +511,7 @@ export function test (data, path, value) {
|
|||
* @param {boolean} [expanded=true] New expanded state: true to expand, false to collapse
|
||||
* @return {JSONData}
|
||||
*/
|
||||
export function expand (data, callback, expanded: boolean = true) {
|
||||
export function expand (data: JSONData, callback: Path | (Path) => boolean, expanded: boolean = true) {
|
||||
// console.log('expand', callback, expand)
|
||||
|
||||
if (typeof callback === 'function') {
|
||||
|
@ -499,7 +538,7 @@ export function expand (data, callback, expanded: boolean = true) {
|
|||
/**
|
||||
* Expand all Objects and Arrays on a path
|
||||
*/
|
||||
export function expandPath (data: JSONData, path: Path) {
|
||||
export function expandPath (data: JSONData, path: Path) : JSONData {
|
||||
let updatedData = data
|
||||
|
||||
if (path) {
|
||||
|
@ -521,7 +560,7 @@ export function expandPath (data: JSONData, path: Path) {
|
|||
* @param {JSONData} data
|
||||
* @param {JSONSchemaError[]} errors
|
||||
*/
|
||||
export function addErrors (data, errors) {
|
||||
export function addErrors (data: JSONData, errors) {
|
||||
let updatedData = data
|
||||
|
||||
if (errors) {
|
||||
|
@ -641,24 +680,6 @@ export function addSearchResults (data: JSONData, searchResults: DataPointer[],
|
|||
return updatedData
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge a object describing where the focus is to the data
|
||||
*/
|
||||
export function addFocus (data: JSONData, focusOn: DataPointer) {
|
||||
if (focusOn.type == 'value') {
|
||||
const dataPath = toDataPath(data, focusOn.path).concat('focus')
|
||||
return setIn(data, dataPath, true)
|
||||
}
|
||||
|
||||
if (focusOn.type === 'property') {
|
||||
const valueDataPath = toDataPath(data, focusOn.path)
|
||||
const propertyDataPath = allButLast(valueDataPath).concat('focus')
|
||||
return setIn(data, propertyDataPath, true)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Do a case insensitive search for a search text in a text
|
||||
* @param {String} text
|
||||
|
@ -669,13 +690,15 @@ export function containsCaseInsensitive (text: string, search: string): boolean
|
|||
return String(text).toLowerCase().indexOf(search.toLowerCase()) !== -1
|
||||
}
|
||||
|
||||
type RecurseCallback = (JSONData, Path, JSONData) => JSONData
|
||||
|
||||
/**
|
||||
* Recursively transform JSONData: a recursive "map" function
|
||||
* @param {JSONData} data
|
||||
* @param {function(value: JSONData, path: Path, root: JSONData)} callback
|
||||
* @return {JSONData} Returns the transformed data
|
||||
*/
|
||||
export function transform (data, callback) {
|
||||
export function transform (data: JSONData, callback: RecurseCallback) {
|
||||
return recurseTransform (data, [], data, callback)
|
||||
}
|
||||
|
||||
|
@ -687,40 +710,33 @@ export function transform (data, callback) {
|
|||
* @param {function(value: JSONData, path: Path, root: JSONData)} callback
|
||||
* @return {JSONData} Returns the transformed data
|
||||
*/
|
||||
function recurseTransform (value, path, root, callback) {
|
||||
function recurseTransform (value: JSONData, path: Path, root?: JSONData, callback: RecurseCallback) : JSONData{
|
||||
let updatedValue = callback(value, path, root)
|
||||
|
||||
switch (value.type) {
|
||||
case 'Array': {
|
||||
let updatedItems = updatedValue.items
|
||||
if (value.type === 'Array') {
|
||||
let updatedItems = updatedValue.items
|
||||
|
||||
updatedValue.items.forEach((item, index) => {
|
||||
const updatedItem = recurseTransform(item, path.concat(String(index)), root, callback)
|
||||
updatedItems = setIn(updatedItems, [index], updatedItem)
|
||||
})
|
||||
updatedValue.items.forEach((item, index) => {
|
||||
const updatedItem = recurseTransform(item.value, path.concat(String(index)), root, callback)
|
||||
updatedItems = setIn(updatedItems, [index, 'value'], updatedItem)
|
||||
})
|
||||
|
||||
updatedValue = setIn(updatedValue, ['items'], updatedItems)
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'Object': {
|
||||
let updatedProps = updatedValue.props
|
||||
|
||||
updatedValue.props.forEach((prop, index) => {
|
||||
const updatedItem = recurseTransform(prop.value, path.concat(prop.name), root, callback)
|
||||
updatedProps = setIn(updatedProps, [index, 'value'], updatedItem)
|
||||
})
|
||||
|
||||
updatedValue = setIn(updatedValue, ['props'], updatedProps)
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
default: // type 'string' or 'value'
|
||||
// no childs to traverse
|
||||
updatedValue = setIn(updatedValue, ['items'], updatedItems)
|
||||
}
|
||||
|
||||
if (value.type === 'Object') {
|
||||
let updatedProps = updatedValue.props
|
||||
|
||||
updatedValue.props.forEach((prop, index) => {
|
||||
const updatedItem = recurseTransform(prop.value, path.concat(prop.name), root, callback)
|
||||
updatedProps = setIn(updatedProps, [index, 'value'], updatedItem)
|
||||
})
|
||||
|
||||
updatedValue = setIn(updatedValue, ['props'], updatedProps)
|
||||
}
|
||||
|
||||
// (for type 'string' or 'value' there are no childs to traverse)
|
||||
|
||||
return updatedValue
|
||||
}
|
||||
|
||||
|
@ -729,7 +745,7 @@ function recurseTransform (value, path, root, callback) {
|
|||
* @param {JSONData} data
|
||||
* @param {function(value: JSONData, path: Path, root: JSONData)} callback
|
||||
*/
|
||||
export function traverse (data, callback) {
|
||||
export function traverse (data: JSONData, callback: RecurseCallback) {
|
||||
return recurseTraverse (data, [], data, callback)
|
||||
}
|
||||
|
||||
|
@ -740,13 +756,13 @@ export function traverse (data, callback) {
|
|||
* @param {JSONData | null} root The root object, object at path=[]
|
||||
* @param {function(value: JSONData, path: Path, root: JSONData)} callback
|
||||
*/
|
||||
function recurseTraverse (value, path, root, callback) {
|
||||
function recurseTraverse (value: JSONData, path: Path, root: JSONData, callback: RecurseCallback) {
|
||||
callback(value, path, root)
|
||||
|
||||
switch (value.type) {
|
||||
case 'Array': {
|
||||
value.items.forEach((item, index) => {
|
||||
recurseTraverse(item, path.concat(String(index)), root, callback)
|
||||
value.items.forEach((item: ItemData, index) => {
|
||||
recurseTraverse(item.value, path.concat(String(index)), root, callback)
|
||||
})
|
||||
break
|
||||
}
|
||||
|
@ -783,7 +799,9 @@ export function pathExists (data, path) {
|
|||
if (data.type === 'Array') {
|
||||
// index of an array
|
||||
index = path[0]
|
||||
return pathExists(data.items[index], path.slice(1))
|
||||
const item = data.items[index]
|
||||
|
||||
return pathExists(item && item.value, path.slice(1))
|
||||
}
|
||||
else {
|
||||
// object property. find the index of this property
|
||||
|
@ -825,7 +843,7 @@ export function resolvePathIndex (data, path) {
|
|||
* @return {string | null} Returns the name of the next property,
|
||||
* or null if there is none
|
||||
*/
|
||||
export function findNextProp (parent, prop) {
|
||||
export function findNextProp (parent: ObjectData, prop: string) : string | null {
|
||||
const index = findPropertyIndex(parent, prop)
|
||||
if (index === -1) {
|
||||
return null
|
||||
|
@ -841,7 +859,7 @@ export function findNextProp (parent, prop) {
|
|||
* @param {string} prop
|
||||
* @return {number} Returns the index when found, -1 when not found
|
||||
*/
|
||||
export function findPropertyIndex (object, prop) {
|
||||
export function findPropertyIndex (object: ObjectData, prop: string) {
|
||||
return object.props.findIndex(p => p.name === prop)
|
||||
}
|
||||
|
||||
|
@ -851,7 +869,7 @@ export function findPropertyIndex (object, prop) {
|
|||
* @param {string} pointer
|
||||
* @return {Array}
|
||||
*/
|
||||
export function parseJSONPointer (pointer) {
|
||||
export function parseJSONPointer (pointer: string) {
|
||||
const path = pointer.split('/')
|
||||
path.shift() // remove the first empty entry
|
||||
|
||||
|
@ -864,12 +882,33 @@ export function parseJSONPointer (pointer) {
|
|||
* @param {Path} path
|
||||
* @return {string}
|
||||
*/
|
||||
export function compileJSONPointer (path) {
|
||||
export function compileJSONPointer (path: Path) {
|
||||
return path
|
||||
.map(p => '/' + String(p).replace(/~/g, '~0').replace(/\//g, '~1'))
|
||||
.join('')
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new "unique" id. Id's are created from an incremental counter.
|
||||
* @return {number}
|
||||
*/
|
||||
// TODO: use createUniqueId instead of getId()
|
||||
function getId () : number {
|
||||
_id++
|
||||
return _id
|
||||
}
|
||||
let _id = 0
|
||||
|
||||
/**
|
||||
* Find a unique id from an array with properties each having an id field.
|
||||
* The
|
||||
* @param {{id: string}} array
|
||||
*/
|
||||
// TODO: use createUniqueId instead of getId()
|
||||
function createUniqueId (array) {
|
||||
return Math.max(...array.map(item => item.id)) + 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last item of an array
|
||||
*/
|
||||
|
|
48
src/types.js
48
src/types.js
|
@ -1,19 +1,6 @@
|
|||
// @flow
|
||||
|
||||
/**
|
||||
*
|
||||
* @typedef {'Object' | 'Array' | 'value' | 'string'} JSONDataType
|
||||
*
|
||||
* @typedef {{
|
||||
* patch: JSONPatch,
|
||||
* revert: JSONPatch,
|
||||
* error: null | Error
|
||||
* }} JSONPatchResult
|
||||
*
|
||||
* @typedef {{
|
||||
* dataPath: string,
|
||||
* message: string
|
||||
* }} JSONSchemaError
|
||||
*
|
||||
* @typedef {{
|
||||
* name: string?,
|
||||
|
@ -33,10 +20,6 @@
|
|||
* ace: Object?
|
||||
* }} Options
|
||||
*
|
||||
* @typedef {{
|
||||
* expand: function (path: Path)?
|
||||
* }} PatchOptions
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
|
@ -53,11 +36,17 @@ export type SearchResultStatus = 'normal' | 'active'
|
|||
export type DataPointerType = 'value' | 'property'
|
||||
|
||||
export type PropertyData = {
|
||||
id: number,
|
||||
name: string,
|
||||
value: JSONData,
|
||||
searchResult: ?SearchResultStatus
|
||||
}
|
||||
|
||||
export type ItemData = {
|
||||
id: number,
|
||||
value: JSONData
|
||||
}
|
||||
|
||||
export type ObjectData = {
|
||||
type: 'Object',
|
||||
expanded: ?boolean,
|
||||
|
@ -67,7 +56,7 @@ export type ObjectData = {
|
|||
export type ArrayData = {
|
||||
type: 'Array',
|
||||
expanded: ?boolean,
|
||||
items: ?JSONData[]
|
||||
items: ItemData[]
|
||||
}
|
||||
|
||||
export type ValueData = {
|
||||
|
@ -78,6 +67,8 @@ export type ValueData = {
|
|||
|
||||
export type JSONData = ObjectData | ArrayData | ValueData
|
||||
|
||||
export type JSONDataType = 'Object' | 'Array' | 'value' | 'string'
|
||||
|
||||
|
||||
|
||||
export type Path = string[]
|
||||
|
@ -96,8 +87,25 @@ export type SetOptions = {
|
|||
|
||||
export type JSONPatchAction = {
|
||||
op: string, // TODO: define allowed ops
|
||||
path?: string,
|
||||
path: string,
|
||||
from?: string,
|
||||
value?: any
|
||||
value?: any,
|
||||
jsoneditor?: PatchOptions
|
||||
}
|
||||
export type JSONPatch = JSONPatchAction[]
|
||||
|
||||
export type PatchOptions = {
|
||||
type: JSONDataType,
|
||||
expand: (Path) => boolean
|
||||
}
|
||||
|
||||
export type JSONPatchResult = {
|
||||
patch: JSONPatch,
|
||||
revert: JSONPatch,
|
||||
error: null | Error
|
||||
}
|
||||
|
||||
export type JSONSchemaError = {
|
||||
dataPath: string,
|
||||
message: string
|
||||
}
|
||||
|
|
|
@ -20,45 +20,58 @@ const JSON_DATA_EXAMPLE = {
|
|||
expanded: true,
|
||||
props: [
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'obj',
|
||||
value: {
|
||||
type: 'Object',
|
||||
expanded: true,
|
||||
props: [
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'arr',
|
||||
value: {
|
||||
type: 'Array',
|
||||
expanded: true,
|
||||
items: [
|
||||
{
|
||||
type: 'value',
|
||||
value: 1
|
||||
id: '[ID]',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'value',
|
||||
value: 2
|
||||
id: '[ID]',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'Object',
|
||||
expanded: true,
|
||||
props: [
|
||||
{
|
||||
name: 'first',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: 3
|
||||
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
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'last',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: 4
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -66,6 +79,7 @@ const JSON_DATA_EXAMPLE = {
|
|||
}
|
||||
},
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'str',
|
||||
value: {
|
||||
type: 'value',
|
||||
|
@ -73,6 +87,7 @@ const JSON_DATA_EXAMPLE = {
|
|||
}
|
||||
},
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'nill',
|
||||
value: {
|
||||
type: 'value',
|
||||
|
@ -80,6 +95,7 @@ const JSON_DATA_EXAMPLE = {
|
|||
}
|
||||
},
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'bool',
|
||||
value: {
|
||||
type: 'value',
|
||||
|
@ -89,50 +105,66 @@ const JSON_DATA_EXAMPLE = {
|
|||
]
|
||||
}
|
||||
|
||||
// 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 = {
|
||||
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: [
|
||||
|
||||
{
|
||||
type: 'value',
|
||||
value: 1
|
||||
id: '[ID]',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'value',
|
||||
value: 2
|
||||
id: '[ID]',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'Object',
|
||||
expanded: false,
|
||||
props: [
|
||||
{
|
||||
name: 'first',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: 3
|
||||
id: '[ID]',
|
||||
value: {
|
||||
type: 'Object',
|
||||
expanded: false,
|
||||
props: [
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'first',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: 3
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'last',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: 4
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'last',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: 4
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -140,6 +172,7 @@ const JSON_DATA_EXAMPLE_COLLAPSED_1 = {
|
|||
}
|
||||
},
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'str',
|
||||
value: {
|
||||
type: 'value',
|
||||
|
@ -147,6 +180,7 @@ const JSON_DATA_EXAMPLE_COLLAPSED_1 = {
|
|||
}
|
||||
},
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'nill',
|
||||
value: {
|
||||
type: 'value',
|
||||
|
@ -154,6 +188,7 @@ const JSON_DATA_EXAMPLE_COLLAPSED_1 = {
|
|||
}
|
||||
},
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'bool',
|
||||
value: {
|
||||
type: 'value',
|
||||
|
@ -168,45 +203,58 @@ const JSON_DATA_EXAMPLE_COLLAPSED_2 = {
|
|||
expanded: true,
|
||||
props: [
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'obj',
|
||||
value: {
|
||||
type: 'Object',
|
||||
expanded: false,
|
||||
props: [
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'arr',
|
||||
value: {
|
||||
type: 'Array',
|
||||
expanded: false,
|
||||
items: [
|
||||
{
|
||||
type: 'value',
|
||||
value: 1
|
||||
id: '[ID]',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'value',
|
||||
value: 2
|
||||
id: '[ID]',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'Object',
|
||||
expanded: false,
|
||||
props: [
|
||||
{
|
||||
name: 'first',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: 3
|
||||
id: '[ID]',
|
||||
value: {
|
||||
type: 'Object',
|
||||
expanded: false,
|
||||
props: [
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'first',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: 3
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'last',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: 4
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'last',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: 4
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -214,6 +262,7 @@ const JSON_DATA_EXAMPLE_COLLAPSED_2 = {
|
|||
}
|
||||
},
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'str',
|
||||
value: {
|
||||
type: 'value',
|
||||
|
@ -221,6 +270,7 @@ const JSON_DATA_EXAMPLE_COLLAPSED_2 = {
|
|||
}
|
||||
},
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'nill',
|
||||
value: {
|
||||
type: 'value',
|
||||
|
@ -228,6 +278,7 @@ const JSON_DATA_EXAMPLE_COLLAPSED_2 = {
|
|||
}
|
||||
},
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'bool',
|
||||
value: {
|
||||
type: 'value',
|
||||
|
@ -243,45 +294,58 @@ const JSON_DATA_EXAMPLE_SEARCH_L = {
|
|||
expanded: true,
|
||||
props: [
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'obj',
|
||||
value: {
|
||||
type: 'Object',
|
||||
expanded: true,
|
||||
props: [
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'arr',
|
||||
value: {
|
||||
type: 'Array',
|
||||
expanded: true,
|
||||
items: [
|
||||
{
|
||||
type: 'value',
|
||||
value: 1
|
||||
id: '[ID]',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'value',
|
||||
value: 2
|
||||
id: '[ID]',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'Object',
|
||||
expanded: true,
|
||||
props: [
|
||||
{
|
||||
name: 'first',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: 3
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'last',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: 4
|
||||
id: '[ID]',
|
||||
value: {
|
||||
type: 'Object',
|
||||
expanded: true,
|
||||
props: [
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'first',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: 3
|
||||
}
|
||||
},
|
||||
searchResult: 'active'
|
||||
}
|
||||
]
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'last',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: 4
|
||||
},
|
||||
searchResult: 'active'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -290,6 +354,7 @@ const JSON_DATA_EXAMPLE_SEARCH_L = {
|
|||
}
|
||||
},
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'str',
|
||||
value: {
|
||||
type: 'value',
|
||||
|
@ -298,6 +363,7 @@ const JSON_DATA_EXAMPLE_SEARCH_L = {
|
|||
}
|
||||
},
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'nill',
|
||||
value: {
|
||||
type: 'value',
|
||||
|
@ -307,6 +373,7 @@ const JSON_DATA_EXAMPLE_SEARCH_L = {
|
|||
searchResult: 'normal'
|
||||
},
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'bool',
|
||||
value: {
|
||||
type: 'value',
|
||||
|
@ -322,11 +389,13 @@ const JSON_DATA_SMALL = {
|
|||
type: 'Object',
|
||||
props: [
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'obj',
|
||||
value: {
|
||||
type: 'Object',
|
||||
props: [
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'a',
|
||||
value: {
|
||||
type: 'value',
|
||||
|
@ -337,13 +406,17 @@ const JSON_DATA_SMALL = {
|
|||
}
|
||||
},
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'arr',
|
||||
value: {
|
||||
type: 'Array',
|
||||
items: [
|
||||
{
|
||||
type: 'value',
|
||||
value: 3
|
||||
id: '[ID]',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: 3
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -362,46 +435,59 @@ const JSON_DATA_EXAMPLE_ERRORS = {
|
|||
expanded: true,
|
||||
props: [
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'obj',
|
||||
value: {
|
||||
type: 'Object',
|
||||
expanded: true,
|
||||
props: [
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'arr',
|
||||
value: {
|
||||
type: 'Array',
|
||||
expanded: true,
|
||||
items: [
|
||||
{
|
||||
type: 'value',
|
||||
value: 1
|
||||
id: '[ID]',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'value',
|
||||
value: 2
|
||||
id: '[ID]',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'Object',
|
||||
expanded: true,
|
||||
props: [
|
||||
{
|
||||
name: 'first',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: 3
|
||||
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,
|
||||
error: JSON_SCHEMA_ERRORS[0]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'last',
|
||||
value: {
|
||||
type: 'value',
|
||||
value: 4,
|
||||
error: JSON_SCHEMA_ERRORS[0]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -409,6 +495,7 @@ const JSON_DATA_EXAMPLE_ERRORS = {
|
|||
}
|
||||
},
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'str',
|
||||
value: {
|
||||
type: 'value',
|
||||
|
@ -416,6 +503,7 @@ const JSON_DATA_EXAMPLE_ERRORS = {
|
|||
}
|
||||
},
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'nill',
|
||||
value: {
|
||||
type: 'value',
|
||||
|
@ -424,6 +512,7 @@ const JSON_DATA_EXAMPLE_ERRORS = {
|
|||
}
|
||||
},
|
||||
{
|
||||
id: '[ID]',
|
||||
name: 'bool',
|
||||
value: {
|
||||
type: 'value',
|
||||
|
@ -438,7 +527,10 @@ test('jsonToData', t => {
|
|||
return true
|
||||
}
|
||||
|
||||
t.deepEqual(jsonToData(JSON_EXAMPLE, expand, []), JSON_DATA_EXAMPLE)
|
||||
const jsonData = jsonToData(JSON_EXAMPLE, expand, [])
|
||||
replaceIds(jsonData)
|
||||
|
||||
t.deepEqual(jsonData, JSON_DATA_EXAMPLE)
|
||||
})
|
||||
|
||||
test('dataToJson', t => {
|
||||
|
@ -622,6 +714,21 @@ test('jsonpatch replace', t => {
|
|||
])
|
||||
})
|
||||
|
||||
test('jsonpatch replace (keep ids intact)', t => {
|
||||
const json = { value: 42 }
|
||||
const patch = [
|
||||
{op: 'replace', path: '/value', value: 100}
|
||||
]
|
||||
|
||||
const data = jsonToData(json)
|
||||
const valueId = data.props[0].id
|
||||
|
||||
const patchedData = patchData(data, patch).data
|
||||
const patchedValueId = patchedData.props[0].id
|
||||
|
||||
t.is(patchedValueId, valueId)
|
||||
})
|
||||
|
||||
test('jsonpatch copy', t => {
|
||||
const json = {
|
||||
arr: [1,2,3],
|
||||
|
@ -659,6 +766,34 @@ test('jsonpatch copy', t => {
|
|||
])
|
||||
})
|
||||
|
||||
// test('jsonpatch copy (create new ids)', t => {
|
||||
// const json = { foo: { bar: 42 } }
|
||||
// const patch = [
|
||||
// {op: 'copy', from: '/foo', path: '/copied'}
|
||||
// ]
|
||||
//
|
||||
// const data = jsonToData(json)
|
||||
// const objectId = data.id
|
||||
// const fooId = data.props[0].value.id
|
||||
// const barId = data.props[0].value.props[0].value.id
|
||||
//
|
||||
// const patchedData = patchData(data, patch).data
|
||||
// const patchedObjectId = patchedData.id
|
||||
// const patchedFooId = patchedData.props[0].value.id
|
||||
// const patchedBarId = patchedData.props[0].value.props[0].value.id
|
||||
// const patchedCopiedId = patchedData.props[1].value.id
|
||||
// const patchedCopiedBarId = patchedData.props[1].value.props[0].value.id
|
||||
//
|
||||
// t.is(patchedData.props[0].name, 'foo')
|
||||
// t.is(patchedData.props[1].name, 'copied')
|
||||
//
|
||||
// t.is(patchedObjectId, objectId, 'same object id')
|
||||
// t.is(patchedFooId, fooId, 'same foo id')
|
||||
// t.is(patchedBarId, barId, 'same bar id')
|
||||
// t.not(patchedCopiedId, patchedFooId, 'different copied foo id')
|
||||
// t.not(patchedCopiedBarId, patchedBarId, 'different copied bar id')
|
||||
// })
|
||||
//
|
||||
test('jsonpatch move', t => {
|
||||
const json = {
|
||||
arr: [1,2,3],
|
||||
|
@ -732,6 +867,54 @@ test('jsonpatch move before', t => {
|
|||
})
|
||||
|
||||
test('jsonpatch move and replace', t => {
|
||||
const json = { a: 2, b: 3 }
|
||||
|
||||
const patch = [
|
||||
{op: 'move', from: '/a', path: '/b'},
|
||||
]
|
||||
|
||||
const data = jsonToData(json)
|
||||
const result = patchData(data, patch)
|
||||
const patchedData = result.data
|
||||
const revert = result.revert
|
||||
const patchedJson = dataToJson(patchedData)
|
||||
|
||||
replaceIds(patchedData)
|
||||
t.deepEqual(patchedData, {
|
||||
"type": "Object",
|
||||
"expanded": true,
|
||||
"props": [
|
||||
{
|
||||
"id": "[ID]",
|
||||
"name": "b",
|
||||
"value": {
|
||||
"type": "value",
|
||||
"value": 2
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
t.deepEqual(patchedJson, { b : 2 })
|
||||
t.deepEqual(revert, [
|
||||
{op:'move', from: '/b', path: '/a'},
|
||||
{op:'add', path:'/b', value: 3, jsoneditor: {type: 'value', before: 'b'}}
|
||||
])
|
||||
|
||||
// test revert
|
||||
const data2 = jsonToData(patchedJson)
|
||||
const result2 = patchData(data2, revert)
|
||||
const patchedData2 = result2.data
|
||||
const revert2 = result2.revert
|
||||
const patchedJson2 = dataToJson(patchedData2)
|
||||
|
||||
t.deepEqual(patchedJson2, json)
|
||||
t.deepEqual(revert2, [
|
||||
{op: 'move', from: '/a', path: '/b'}
|
||||
])
|
||||
})
|
||||
|
||||
test('jsonpatch move and replace (nested)', t => {
|
||||
const json = {
|
||||
arr: [1,2,3],
|
||||
obj: {a : 4}
|
||||
|
@ -768,6 +951,44 @@ test('jsonpatch move and replace', t => {
|
|||
])
|
||||
})
|
||||
|
||||
test('jsonpatch move (keep id intact)', t => {
|
||||
const json = { value: 42 }
|
||||
const patch = [
|
||||
{op: 'move', from: '/value', path: '/moved'}
|
||||
]
|
||||
|
||||
const data = jsonToData(json)
|
||||
const objectId = data.id
|
||||
const valueId = data.props[0].value.id
|
||||
|
||||
const patchedData = patchData(data, patch).data
|
||||
|
||||
const patchedObjectId = patchedData.id
|
||||
const patchedValueId = patchedData.props[0].value.id
|
||||
|
||||
t.is(patchedObjectId, objectId)
|
||||
t.is(patchedValueId, valueId)
|
||||
})
|
||||
|
||||
test('jsonpatch move and replace (keep ids intact)', t => {
|
||||
const json = { a: 2, b: 3 }
|
||||
const patch = [
|
||||
{op: 'move', from: '/a', path: '/b'}
|
||||
]
|
||||
|
||||
const data = jsonToData(json)
|
||||
const bId = data.props[1].id
|
||||
|
||||
t.is(data.props[0].name, 'a')
|
||||
t.is(data.props[1].name, 'b')
|
||||
|
||||
const patchedData = patchData(data, patch).data
|
||||
const patchedBId = patchedData.props[0].id
|
||||
|
||||
t.is(patchedData.props[0].name, 'b')
|
||||
t.is(patchedBId, bId)
|
||||
})
|
||||
|
||||
test('jsonpatch test (ok)', t => {
|
||||
const json = {
|
||||
arr: [1,2,3],
|
||||
|
@ -855,7 +1076,7 @@ test('add and remove errors', t => {
|
|||
|
||||
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)
|
||||
|
@ -878,18 +1099,18 @@ test('transform', t => {
|
|||
[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], ['arr', '0'], JSON_DATA_SMALL],
|
||||
[JSON_DATA_SMALL.props[1].value.items[0].value, ['arr', '0'], JSON_DATA_SMALL],
|
||||
]
|
||||
|
||||
// log.forEach((row, index) => {
|
||||
// t.deepEqual(log[index], EXPECTED_LOG[index], 'should have equal log at index ' + index )
|
||||
// })
|
||||
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], JSON_DATA_SMALL.props[1].value.items[0])
|
||||
t.is(transformed.props[1].value.items[0].value, JSON_DATA_SMALL.props[1].value.items[0].value)
|
||||
|
||||
})
|
||||
|
||||
|
@ -910,19 +1131,19 @@ test('traverse', t => {
|
|||
[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], ['arr', '0'], JSON_DATA_SMALL],
|
||||
[JSON_DATA_SMALL.props[1].value.items[0].value, ['arr', '0'], JSON_DATA_SMALL],
|
||||
]
|
||||
|
||||
// log.forEach((row, index) => {
|
||||
// t.deepEqual(log[index], EXPECTED_LOG[index], 'should have equal log at index ' + index )
|
||||
// })
|
||||
log.forEach((row, index) => {
|
||||
t.deepEqual(log[index], EXPECTED_LOG[index], 'should have equal log at index ' + index )
|
||||
})
|
||||
t.deepEqual(log, EXPECTED_LOG)
|
||||
})
|
||||
|
||||
|
||||
test('search', t => {
|
||||
const searchResults = search(JSON_DATA_EXAMPLE, 'L')
|
||||
//console.log(JSON.stringify(searchResults, null, 2))
|
||||
// printJSON(searchResults)
|
||||
|
||||
t.deepEqual(searchResults, [
|
||||
{path: ['obj', 'arr', '2', 'last'], type: 'property'},
|
||||
|
@ -935,7 +1156,7 @@ test('search', t => {
|
|||
|
||||
const activeSearchResult = searchResults[0]
|
||||
const updatedData = addSearchResults(JSON_DATA_EXAMPLE, searchResults, activeSearchResult)
|
||||
// console.log(JSON.stringify(updatedData, null, 2))
|
||||
// printJSON(updatedData)
|
||||
|
||||
t.deepEqual(updatedData, JSON_DATA_EXAMPLE_SEARCH_L)
|
||||
})
|
||||
|
@ -995,3 +1216,28 @@ test('previousSearchResult', t => {
|
|||
// return null when searchResults are empty
|
||||
t.deepEqual(previousSearchResult([], {path: ['non', 'existing'], type: 'value'}), null)
|
||||
})
|
||||
|
||||
// helper function to replace all id properties with a constant value
|
||||
function replaceIds (data, value = '[ID]') {
|
||||
if (data.type === 'Object') {
|
||||
data.props.forEach(prop => {
|
||||
prop.id = value
|
||||
replaceIds(prop.value, value)
|
||||
})
|
||||
}
|
||||
|
||||
if (data.type === 'Array') {
|
||||
data.items.forEach(item => {
|
||||
item.id = value
|
||||
replaceIds(item.value, value)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 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))
|
||||
}
|
Loading…
Reference in New Issue