Implemented sort action that keeps state

This commit is contained in:
jos 2017-12-20 08:36:55 +01:00
parent 34ca56aee0
commit 0e22497467
5 changed files with 131 additions and 35 deletions

View File

@ -24,6 +24,7 @@
"test": "ava --verbose",
"test-eson": "ava --verbose test/eson.test.js",
"test-patch": "ava --verbose test/patchEson.test.js",
"test-actions": "ava --verbose test/actions.test.js",
"watch:test": "ava --verbose --watch"
},
"dependencies": {

View File

@ -1,5 +1,6 @@
import last from 'lodash/last'
import initial from 'lodash/initial'
import isEmpty from 'lodash/isEmpty'
import {
META,
compileJSONPointer, esonToJson, findNextProp,
@ -304,54 +305,76 @@ export function sort (eson, path, order = null) {
// console.log('sort', path, order)
const compare = order === 'desc' ? compareDesc : compareAsc
const reverseCompare = (a, b) => -compare(a, b)
const object = getIn(eson, path)
if (object[META].type === 'Array') {
const orderedItems = cloneWithSymbols(object)
const items = object.map(item => item[META].value)
const createAction = (value, fromIndex, toIndex, array) => ({
op: 'move',
from: compileJSONPointer(path.concat(String(fromIndex))),
path: compileJSONPointer(path.concat(String(toIndex)))
})
// order the items by value
orderedItems.sort((a, b) => compare(a[META].value, b[META].value))
const actions = sortWithComparator(items, compare, createAction)
// when no order is provided, test whether ordering ascending
// changed anything. If not, sort descending
if (!order && strictShallowEqual(object, orderedItems)) {
orderedItems.reverse()
if (!order && isEmpty(actions)) {
return sortWithComparator(items, reverseCompare, createAction)
}
// TODO: refactor into a set of move actions, so we keep eson state of the items
return [{
op: 'replace',
path: compileJSONPointer(path),
value: esonToJson(orderedItems)
}]
return actions
}
else { // object[META].type === 'Object'
// order the properties by key
const orderedProps = object[META].props.slice().sort(compare)
const props = object[META].props
const createAction = (value, fromIndex, toIndex, objectProps) => ({
op: 'move',
from: compileJSONPointer(path.concat(value)),
path: compileJSONPointer(path.concat(value)),
meta: {
before: props[toIndex]
}
})
const actions = sortWithComparator(props, compare, createAction)
// when no order is provided, test whether ordering ascending
// changed anything. If not, sort descending
if (!order && strictShallowEqual(object[META].props, orderedProps)) {
orderedProps.reverse()
if (!order && isEmpty(actions)) {
return sortWithComparator(props, reverseCompare, createAction)
}
const orderedObject = setIn(object, [META, 'props'], orderedProps)
// TODO: refactor into a set of move actions, so we keep eson state of the items
return [{
op: 'replace',
path: compileJSONPointer(path),
value: esonToJson(orderedObject),
meta: {
order: orderedProps // TODO: order isn't used right now in patchEson.
}
}]
return actions
}
}
// TODO: comment
function sortWithComparator (items, comparator, createAction) {
const orderedItems = items.slice()
let actions = []
for (let i = 0; i < orderedItems.length; i++) {
let firstIndex = i
for (let j = i; j < orderedItems.length; j++) {
if (comparator(orderedItems[firstIndex], orderedItems[j]) > 0) {
firstIndex = j
}
}
if (i !== firstIndex) {
const firstItem = orderedItems[firstIndex]
orderedItems.splice(firstIndex, 1)
orderedItems.unshift(firstItem)
actions.push(createAction(firstItem, firstIndex, i))
}
}
return actions
}
/**
* Create a JSON entry
* @param {ESONType} type

View File

@ -138,11 +138,6 @@ export function replace (data, path, value) {
// keep the original id
let newValue = setIn(value, [META, 'id'], oldValue[META].id)
// FIXME: get the original expanded state of the copied value from JSON-Patch
if (newValue[META].type === 'Object' || newValue[META].type === 'Array') {
newValue = setIn(newValue, [META, 'expanded'], true)
}
return {
data: setIn(data, path, newValue),
revert: [{

79
test/actions.test.js Normal file
View File

@ -0,0 +1,79 @@
'use strict'
import test from 'ava'
import {
sort
} from '../src/actions'
import { assertDeepEqualEson } from './utils/assertDeepEqualEson'
import {esonToJson, expandOne, jsonToEson, META} from '../src/eson'
import {patchEson} from '../src/patchEson'
// TODO: test changeValue
// TODO: test changeProperty
// TODO: test changeType (or cleanup the function)
// TODO: test duplicate
// TODO: test insertBefore
// TODO: test replace
// TODO: test append
// TODO: test remove
// TODO: test removeAll
// FIXME: sort root array
// test('sort root Array', t => {
// const eson = jsonToEson([1,3,2])
//
// t.deepEqual(patchEson(eson, sort(eson, [])), jsonToEson([1,2,3]))
// t.deepEqual(patchEson(eson, sort(eson, [], 'asc')), jsonToEson([1,2,3]))
// t.deepEqual(patchEson(eson, sort(eson, [], 'desc')), jsonToEson([3,2,1]))
// })
test('sort nested Array', t => {
const eson = jsonToEson({arr: [4,1,8,5,3,9,2,7,6]})
const actual = patchEson(eson, sort(eson, ['arr'])).data
const expected = jsonToEson({arr: [1,2,3,4,5,6,7,8,9]})
assertDeepEqualEson(t, actual, expected)
})
test('sort nested Array reverse order', t => {
// no order provided -> order ascending, but if nothing changes, order descending
const eson = jsonToEson({arr: [1,2,3,4,5,6,7,8,9]})
const actual = patchEson(eson, sort(eson, ['arr'])).data
const expected = jsonToEson({arr: [9,8,7,6,5,4,3,2,1]})
assertDeepEqualEson(t, actual, expected)
// id's and META should be the same
t.deepEqual(actual.arr[META].id, eson.arr[META].id)
t.deepEqual(actual.arr[7][META].id, eson.arr[1][META].id)
})
// FIXME: sort root object
// test('sort root Object', t => {
// const eson = jsonToEson({c: 2, b: 3, a:4})
//
// t.deepEqual(esonToJson(patchEson(eson, sort(eson, [])).data[META].props), ['a', 'b', 'c'])
// t.deepEqual(esonToJson(patchEson(eson, sort(eson, [], 'asc')).data[META].props), ['a', 'b', 'c'])
// t.deepEqual(esonToJson(patchEson(eson, sort(eson, [], 'desc')).data[META].props), ['c', 'b', 'a'])
// })
test('sort nested Object', t => {
const eson = jsonToEson({obj: {c: 2, b: 3, a:4}})
eson.obj[META].expanded = true
eson.obj.c[META].expanded = true
const actual = patchEson(eson, sort(eson, ['obj'])).data
// should keep META data
t.deepEqual(actual.obj[META].props, ['a', 'b', 'c'])
t.deepEqual(actual.obj[META].expanded, true)
t.deepEqual(actual.obj.c[META].expanded, true)
t.deepEqual(actual.obj[META].id, eson.obj[META].id)
t.deepEqual(actual.obj.a[META].id, eson.obj.a[META].id)
t.deepEqual(actual.obj.b[META].id, eson.obj.b[META].id)
t.deepEqual(actual.obj.c[META].id, eson.obj.c[META].id)
// asc, desc
t.deepEqual(patchEson(eson, sort(eson, ['obj'])).data.obj[META].props, ['a', 'b', 'c'])
t.deepEqual(patchEson(eson, sort(eson, ['obj'], 'asc')).data.obj[META].props, ['a', 'b', 'c'])
t.deepEqual(patchEson(eson, sort(eson, ['obj'], 'desc')).data.obj[META].props, ['c', 'b', 'a'])
})

View File

@ -17,8 +17,6 @@ import 'console.table'
import repeat from 'lodash/repeat'
import { assertDeepEqualEson } from './utils/assertDeepEqualEson'
const ESON2 = loadJSON('./resources/eson2.json')
test('jsonToEson', t => {
assertDeepEqualEson(t, jsonToEson(1), {[META]: {id: '[ID]', path: [], type: 'value', value: 1}})
assertDeepEqualEson(t, jsonToEson("foo"), {[META]: {id: '[ID]', path: [], type: 'value', value: "foo"}})