Implement selecting one or multiple nodes by dragging
This commit is contained in:
parent
20a067508d
commit
9a23799d5f
|
@ -64,8 +64,7 @@
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|
||||||
.bottom {
|
//display: flex;
|
||||||
height: $input-padding;
|
//flex-direction: column;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -11,6 +11,7 @@
|
||||||
import { faSearch, faUndo, faRedo } from '@fortawesome/free-solid-svg-icons'
|
import { faSearch, faUndo, faRedo } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { createHistory } from './history.js'
|
import { createHistory } from './history.js'
|
||||||
import Node from './JSONNode.svelte'
|
import Node from './JSONNode.svelte'
|
||||||
|
import { expandSelection } from './selection.js'
|
||||||
import {
|
import {
|
||||||
existsIn,
|
existsIn,
|
||||||
getIn,
|
getIn,
|
||||||
|
@ -36,6 +37,19 @@
|
||||||
|
|
||||||
export let doc = {}
|
export let doc = {}
|
||||||
let state = undefined
|
let state = undefined
|
||||||
|
let selection = null
|
||||||
|
let selectionMap = {}
|
||||||
|
|
||||||
|
$: {
|
||||||
|
selectionMap = {}
|
||||||
|
if (selection != null) {
|
||||||
|
selection.forEach(path => {
|
||||||
|
selectionMap[compileJSONPointer(path)] = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$: console.log('selectionMap', selectionMap)
|
||||||
|
|
||||||
export let onChangeJson = () => {}
|
export let onChangeJson = () => {}
|
||||||
|
|
||||||
|
@ -257,6 +271,14 @@
|
||||||
state = setIn(state, path.concat(STATE_LIMIT), limit)
|
state = setIn(state, path.concat(STATE_LIMIT), limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleSelect (anchorPath, focusPath) {
|
||||||
|
if (anchorPath && focusPath) {
|
||||||
|
selection = expandSelection(doc, state, anchorPath, focusPath)
|
||||||
|
} else {
|
||||||
|
selection = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expand all nodes on given path
|
* Expand all nodes on given path
|
||||||
* @param {Path} path
|
* @param {Path} path
|
||||||
|
@ -373,8 +395,9 @@
|
||||||
onPatch={handlePatch}
|
onPatch={handlePatch}
|
||||||
onExpand={handleExpand}
|
onExpand={handleExpand}
|
||||||
onLimit={handleLimit}
|
onLimit={handleLimit}
|
||||||
|
onSelect={handleSelect}
|
||||||
|
selectionMap={selectionMap}
|
||||||
/>
|
/>
|
||||||
<div class='bottom'></div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,18 @@
|
||||||
font-size: $font-size;
|
font-size: $font-size;
|
||||||
color: $black;
|
color: $black;
|
||||||
|
|
||||||
|
&.root {
|
||||||
|
min-height: 100%;
|
||||||
|
padding-bottom: $input-padding;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected .header,
|
||||||
|
&.selected .contents,
|
||||||
|
&.selected .footer {
|
||||||
|
background-color: $selection-background;
|
||||||
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
@ -27,6 +39,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
|
display: inline-block;
|
||||||
padding-left: $line-height + $input-padding; // must be the same as the width of the expand button
|
padding-left: $line-height + $input-padding; // must be the same as the width of the expand button
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,10 +99,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.items,
|
|
||||||
.props {
|
|
||||||
padding-left: $indentation-width;
|
|
||||||
}
|
|
||||||
.value {
|
.value {
|
||||||
|
|
||||||
&.string {
|
&.string {
|
||||||
|
@ -125,8 +134,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
div.limit {
|
div.limit {
|
||||||
margin-left: $indentation-width;
|
|
||||||
|
|
||||||
button {
|
button {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
|
|
|
@ -1,18 +1,21 @@
|
||||||
<script>
|
<script>
|
||||||
import { debounce, initial } from 'lodash-es'
|
import { debounce, initial, isEqual } from 'lodash-es'
|
||||||
import {
|
import {
|
||||||
DEBOUNCE_DELAY, DEFAULT_LIMIT,
|
DEBOUNCE_DELAY,
|
||||||
STATE_EXPANDED, STATE_LIMIT, STATE_PROPS,
|
DEFAULT_LIMIT,
|
||||||
|
STATE_EXPANDED,
|
||||||
|
STATE_LIMIT,
|
||||||
|
STATE_PROPS,
|
||||||
STATE_SEARCH_PROPERTY,
|
STATE_SEARCH_PROPERTY,
|
||||||
STATE_SEARCH_VALUE
|
STATE_SEARCH_VALUE,
|
||||||
|
INDENTATION_WIDTH
|
||||||
} from './constants.js'
|
} from './constants.js'
|
||||||
|
import { singleton } from './singleton.js'
|
||||||
import { getPlainText, setPlainText } from './utils/domUtils.js'
|
import { getPlainText, setPlainText } from './utils/domUtils.js'
|
||||||
import Icon from 'svelte-awesome'
|
import Icon from 'svelte-awesome'
|
||||||
import { faCaretDown, faCaretRight } from '@fortawesome/free-solid-svg-icons'
|
import { faCaretDown, faCaretRight } from '@fortawesome/free-solid-svg-icons'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import { findUniqueName } from './utils/stringUtils.js'
|
|
||||||
import { isUrl, stringConvert, valueType } from './utils/typeUtils'
|
import { isUrl, stringConvert, valueType } from './utils/typeUtils'
|
||||||
import { updateProps } from './utils/updateProps.js'
|
|
||||||
import { compileJSONPointer } from './utils/jsonPointer'
|
import { compileJSONPointer } from './utils/jsonPointer'
|
||||||
|
|
||||||
export let key = undefined // only applicable for object properties
|
export let key = undefined // only applicable for object properties
|
||||||
|
@ -23,6 +26,8 @@
|
||||||
export let onPatch
|
export let onPatch
|
||||||
export let onExpand
|
export let onExpand
|
||||||
export let onLimit
|
export let onLimit
|
||||||
|
export let onSelect
|
||||||
|
export let selectionMap
|
||||||
|
|
||||||
$: expanded = state && state[STATE_EXPANDED]
|
$: expanded = state && state[STATE_EXPANDED]
|
||||||
$: limit = state && state[STATE_LIMIT]
|
$: limit = state && state[STATE_LIMIT]
|
||||||
|
@ -67,6 +72,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getIndentationStyle(level) {
|
||||||
|
return `margin-left: ${level * INDENTATION_WIDTH}px`
|
||||||
|
}
|
||||||
|
|
||||||
function getValueClass (value, searchResult) {
|
function getValueClass (value, searchResult) {
|
||||||
const type = valueType (value)
|
const type = valueType (value)
|
||||||
|
|
||||||
|
@ -182,11 +191,73 @@
|
||||||
function handleShowMore () {
|
function handleShowMore () {
|
||||||
onLimit(path, (Math.round(limit / DEFAULT_LIMIT) + 1) * DEFAULT_LIMIT)
|
onLimit(path, (Math.round(limit / DEFAULT_LIMIT) + 1) * DEFAULT_LIMIT)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleMouseDown (event) {
|
||||||
|
// unselect existing selection on mouse down if any
|
||||||
|
if (singleton.selectionAnchor != null && singleton.selectionFocus != null) {
|
||||||
|
singleton.selectionAnchor = null
|
||||||
|
singleton.selectionFocus = null
|
||||||
|
onSelect(null, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the mouse down is not happening in the key or value input fields
|
||||||
|
if (event.target.contentEditable !== 'true') {
|
||||||
|
// initialize dragging a selection
|
||||||
|
singleton.mousedown = true
|
||||||
|
singleton.selectionAnchor = path
|
||||||
|
singleton.selectionFocus = null
|
||||||
|
event.stopPropagation()
|
||||||
|
}
|
||||||
|
|
||||||
|
// we attache the mouse up event listener to the global document,
|
||||||
|
// so we will not miss if the mouse up is happening outside of the editor
|
||||||
|
document.addEventListener('mouseup', handleMouseUp)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMouseMove (event) {
|
||||||
|
if (singleton.mousedown) {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
|
||||||
|
if (singleton.selectionFocus == null) {
|
||||||
|
// First move event, no selection yet.
|
||||||
|
// Clear the default selection of the browser
|
||||||
|
if (window.getSelection) {
|
||||||
|
window.getSelection().empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isEqual(path, singleton.selectionFocus)) {
|
||||||
|
singleton.selectionFocus = path
|
||||||
|
onSelect(singleton.selectionAnchor, singleton.selectionFocus)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMouseUp (event) {
|
||||||
|
if (singleton.mousedown) {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
|
||||||
|
singleton.mousedown = false
|
||||||
|
}
|
||||||
|
|
||||||
|
document.removeEventListener('mouseup', handleMouseUp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: this is not efficient. Create a nested object with the selection and pass that
|
||||||
|
$: selected = selectionMap[compileJSONPointer(path)] === true
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class='json-node'>
|
<div
|
||||||
|
class='json-node'
|
||||||
|
class:root={path.length === 0}
|
||||||
|
class:selected={selected}
|
||||||
|
on:mousedown={handleMouseDown}
|
||||||
|
on:mousemove={handleMouseMove}
|
||||||
|
>
|
||||||
{#if type === 'array'}
|
{#if type === 'array'}
|
||||||
<div class='header'>
|
<div class='header' style={getIndentationStyle(path.length)}>
|
||||||
<button
|
<button
|
||||||
class='expand'
|
class='expand'
|
||||||
on:click={toggleExpand}
|
on:click={toggleExpand}
|
||||||
|
@ -230,20 +301,22 @@
|
||||||
onPatch={onPatch}
|
onPatch={onPatch}
|
||||||
onExpand={onExpand}
|
onExpand={onExpand}
|
||||||
onLimit={onLimit}
|
onLimit={onLimit}
|
||||||
|
onSelect={onSelect}
|
||||||
|
selectionMap={selectionMap}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
{#if limited}
|
{#if limited}
|
||||||
<div class='limit'>
|
<div class="limit" style={getIndentationStyle(path.length + 2)}>
|
||||||
(showing {limit} of {value.length} items <button on:click={handleShowMore}>show more</button> <button on:click={handleShowAll}>show all</button>)
|
(showing {limit} of {value.length} items <button on:click={handleShowMore}>show more</button> <button on:click={handleShowAll}>show all</button>)
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="footer">
|
<div class="footer" style={getIndentationStyle(path.length)}>
|
||||||
<span class="delimiter">]</span>
|
<span class="delimiter">]</span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{:else if type === 'object'}
|
{:else if type === 'object'}
|
||||||
<div class='header'>
|
<div class='header' style={getIndentationStyle(path.length)}>
|
||||||
<button
|
<button
|
||||||
class='expand'
|
class='expand'
|
||||||
on:click={toggleExpand}
|
on:click={toggleExpand}
|
||||||
|
@ -287,15 +360,17 @@
|
||||||
onPatch={onPatch}
|
onPatch={onPatch}
|
||||||
onExpand={onExpand}
|
onExpand={onExpand}
|
||||||
onLimit={onLimit}
|
onLimit={onLimit}
|
||||||
|
onSelect={onSelect}
|
||||||
|
selectionMap={selectionMap}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
<div class="footer">
|
<div class="footer" style={getIndentationStyle(path.length)}>
|
||||||
<span class="delimiter">}</span>
|
<span class="delimiter">}</span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{:else}
|
{:else}
|
||||||
<div class="contents">
|
<div class="contents" style={getIndentationStyle(path.length)}>
|
||||||
{#if typeof key === 'string'}
|
{#if typeof key === 'string'}
|
||||||
<div
|
<div
|
||||||
class={keyClass}
|
class={keyClass}
|
||||||
|
|
|
@ -8,3 +8,5 @@ export const STATE_SEARCH_VALUE = Symbol('search:value')
|
||||||
export const SCROLL_DURATION = 300 // ms
|
export const SCROLL_DURATION = 300 // ms
|
||||||
export const DEBOUNCE_DELAY = 300
|
export const DEBOUNCE_DELAY = 300
|
||||||
export const DEFAULT_LIMIT = 100
|
export const DEFAULT_LIMIT = 100
|
||||||
|
|
||||||
|
export const INDENTATION_WIDTH = 18 // pixels IMPORTANT: keep in sync with sass constant $indentation-width
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
import { isEqual } from 'lodash-es'
|
||||||
|
import { STATE_PROPS } from './constants.js'
|
||||||
|
import { getIn } from './utils/immutabilityHelpers.js'
|
||||||
|
import { isObject } from './utils/typeUtils.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expand a selection start and end into an array containing all paths
|
||||||
|
* between (and including) start and end
|
||||||
|
*
|
||||||
|
* @param {JSON} doc
|
||||||
|
* @param {JSON} state
|
||||||
|
* @param {Path} anchorPath
|
||||||
|
* @param {Path} focusPath
|
||||||
|
* @return {Path[]} selection
|
||||||
|
*/
|
||||||
|
export function expandSelection (doc, state, anchorPath, focusPath) {
|
||||||
|
if (isEqual(anchorPath, focusPath)) {
|
||||||
|
// just a single node
|
||||||
|
return [ anchorPath ]
|
||||||
|
} else {
|
||||||
|
// multiple nodes
|
||||||
|
let sharedPath = findSharedPath(anchorPath, focusPath)
|
||||||
|
|
||||||
|
if (anchorPath.length === sharedPath.length || focusPath.length === sharedPath.length) {
|
||||||
|
// a parent and a child, like ['arr', 1] and ['arr']
|
||||||
|
return [ sharedPath ]
|
||||||
|
}
|
||||||
|
|
||||||
|
const anchorProp = anchorPath[sharedPath.length]
|
||||||
|
const focusProp = focusPath[sharedPath.length]
|
||||||
|
const value = getIn(doc, sharedPath)
|
||||||
|
|
||||||
|
if (isObject(value)) {
|
||||||
|
const props = getIn(state, sharedPath.concat(STATE_PROPS))
|
||||||
|
const anchorIndex = props.findIndex(prop => prop.key === anchorProp)
|
||||||
|
const focusIndex = props.findIndex(prop => prop.key === focusProp)
|
||||||
|
|
||||||
|
if (anchorIndex !== -1 && focusIndex !== -1) {
|
||||||
|
const startIndex = Math.min(anchorIndex, focusIndex)
|
||||||
|
const endIndex = Math.max(anchorIndex, focusIndex)
|
||||||
|
const paths = []
|
||||||
|
|
||||||
|
for (let i = startIndex; i <= endIndex; i++) {
|
||||||
|
paths.push(sharedPath.concat(props[i].key))
|
||||||
|
}
|
||||||
|
|
||||||
|
return paths
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
const startIndex = Math.min(anchorProp, focusProp)
|
||||||
|
const endIndex = Math.max(anchorProp, focusProp)
|
||||||
|
const paths = []
|
||||||
|
|
||||||
|
for (let i = startIndex; i <= endIndex; i++) {
|
||||||
|
paths.push(sharedPath.concat(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
return paths
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// should never happen
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the common path of two paths.
|
||||||
|
* For example findCommonRoot(['arr', '1', 'name'], ['arr', '1', 'address', 'contact']) returns ['arr', '1']
|
||||||
|
* @param {Path} path1
|
||||||
|
* @param {Path} path2
|
||||||
|
* @return {Path}
|
||||||
|
*/
|
||||||
|
// TODO: write unit tests for findSharedPath
|
||||||
|
export function findSharedPath (path1, path2) {
|
||||||
|
let i = 0;
|
||||||
|
while (i < path1.length && path1[i] === path2[i]) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return path1.slice(0, i)
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
import assert from 'assert'
|
||||||
|
import { expandSelection } from './selection.js'
|
||||||
|
import { syncState } from './utils/syncState.js'
|
||||||
|
|
||||||
|
describe ('selection', () => {
|
||||||
|
const doc = {
|
||||||
|
"obj": {
|
||||||
|
"arr": [1,2, {"first":3,"last":4}]
|
||||||
|
},
|
||||||
|
"str": "hello world",
|
||||||
|
"nill": null,
|
||||||
|
"bool": false
|
||||||
|
}
|
||||||
|
const state = syncState(doc, undefined, [], () => true)
|
||||||
|
|
||||||
|
it('should expand a selection (object)', () => {
|
||||||
|
const start = ['obj', 'arr', '2', 'last']
|
||||||
|
const end = ['nill']
|
||||||
|
|
||||||
|
const actual = expandSelection(doc, state, start, end)
|
||||||
|
assert.deepStrictEqual(actual, [
|
||||||
|
['obj'],
|
||||||
|
['str'],
|
||||||
|
['nill']
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should expand a selection (array)', () => {
|
||||||
|
const start = ['obj', 'arr', 1]
|
||||||
|
const end = ['obj', 'arr', 0] // note the "wrong" order of start and end
|
||||||
|
|
||||||
|
const actual = expandSelection(doc, state, start, end)
|
||||||
|
assert.deepStrictEqual(actual, [
|
||||||
|
['obj', 'arr', 0],
|
||||||
|
['obj', 'arr', 1]
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should expand a selection (array) (2)', () => {
|
||||||
|
const start = ['obj', 'arr', 1] // child
|
||||||
|
const end = ['obj', 'arr'] // parent
|
||||||
|
|
||||||
|
const actual = expandSelection(doc, state, start, end)
|
||||||
|
assert.deepStrictEqual(actual, [
|
||||||
|
['obj', 'arr']
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should expand a selection (value)', () => {
|
||||||
|
const start = ['obj', 'arr', 2, 'first']
|
||||||
|
const end = ['obj', 'arr', 2, 'first']
|
||||||
|
|
||||||
|
const actual = expandSelection(doc, state, start, end)
|
||||||
|
assert.deepStrictEqual(actual, [
|
||||||
|
['obj', 'arr', 2, 'first']
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should expand a selection (value)', () => {
|
||||||
|
const start = ['obj', 'arr']
|
||||||
|
const end = ['obj', 'arr']
|
||||||
|
|
||||||
|
const actual = expandSelection(doc, state, start, end)
|
||||||
|
assert.deepStrictEqual(actual, [
|
||||||
|
['obj', 'arr']
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,5 @@
|
||||||
|
export const singleton = {
|
||||||
|
mousedown: false,
|
||||||
|
selectionAnchor: null, // Path
|
||||||
|
selectionFocus: null // Path
|
||||||
|
}
|
|
@ -24,12 +24,14 @@ $error-border-color: #ffd700;
|
||||||
$gray: #9d9d9d;
|
$gray: #9d9d9d;
|
||||||
$gray-icon: $gray;
|
$gray-icon: $gray;
|
||||||
$light-gray: #c0c0c0;
|
$light-gray: #c0c0c0;
|
||||||
|
$selection-background: #e0e0e0;
|
||||||
|
|
||||||
$line-height: 18px;
|
$line-height: 18px;
|
||||||
$indentation-width: 18px;
|
$indentation-width: 18px; // IMPORTANT: keep in sync with js constant INDENTATION_WIDTH
|
||||||
$menu-button-size: 32px;
|
$menu-button-size: 32px;
|
||||||
$input-padding: 5px;
|
$input-padding: 5px;
|
||||||
$search-box-offset: 10px;
|
$search-box-offset: 10px;
|
||||||
$border-radius: 3px;
|
$border-radius: 3px;
|
||||||
|
|
||||||
$menu-padding: 5px;
|
$menu-padding: 5px;
|
||||||
|
$bottom-height: 5px;
|
||||||
|
|
|
@ -24,9 +24,9 @@ describe('syncState', () => {
|
||||||
const expectedState = {}
|
const expectedState = {}
|
||||||
expectedState[STATE_EXPANDED] = true
|
expectedState[STATE_EXPANDED] = true
|
||||||
expectedState[STATE_PROPS] = [
|
expectedState[STATE_PROPS] = [
|
||||||
{ 'id': '1', 'key': 'array' },
|
{ 'id': state[STATE_PROPS][0].id, 'key': 'array' },
|
||||||
{ 'id': '2', 'key': 'object' },
|
{ 'id': state[STATE_PROPS][1].id, 'key': 'object' },
|
||||||
{ 'id': '3', 'key': 'value' }
|
{ 'id': state[STATE_PROPS][2].id, 'key': 'value' }
|
||||||
]
|
]
|
||||||
expectedState.array = []
|
expectedState.array = []
|
||||||
expectedState.array[STATE_EXPANDED] = true
|
expectedState.array[STATE_EXPANDED] = true
|
||||||
|
@ -34,13 +34,13 @@ describe('syncState', () => {
|
||||||
expectedState.array[2] = {}
|
expectedState.array[2] = {}
|
||||||
expectedState.array[2][STATE_EXPANDED] = false
|
expectedState.array[2][STATE_EXPANDED] = false
|
||||||
expectedState.array[2][STATE_PROPS] = [
|
expectedState.array[2][STATE_PROPS] = [
|
||||||
{ 'id': '4', 'key': 'c' } // FIXME: props should not be created because node is not expande
|
{ 'id': state.array[2][STATE_PROPS][0].id, 'key': 'c' } // FIXME: props should not be created because node is not expanded
|
||||||
]
|
]
|
||||||
expectedState.object = {}
|
expectedState.object = {}
|
||||||
expectedState.object[STATE_EXPANDED] = true
|
expectedState.object[STATE_EXPANDED] = true
|
||||||
expectedState.object[STATE_PROPS] = [
|
expectedState.object[STATE_PROPS] = [
|
||||||
{ 'id': '5', 'key': 'a' },
|
{ 'id': state.object[STATE_PROPS][0].id, 'key': 'a' },
|
||||||
{ 'id': '6', 'key': 'b' }
|
{ 'id': state.object[STATE_PROPS][1].id, 'key': 'b' }
|
||||||
]
|
]
|
||||||
|
|
||||||
assert.deepStrictEqual(state, expectedState)
|
assert.deepStrictEqual(state, expectedState)
|
||||||
|
|
Loading…
Reference in New Issue