Skip to content

Commit 85d91c8

Browse files
authored
feat(core): skeleZip (#129)
A simplified zipper over the Skele tree structure. It is meant to repalce `elementZipper` which is now deprecated. `skeleZip` does not take any configuration and **requires** explicite annotation of children properties (using `@@skele/children`) in the tree.
1 parent 948afed commit 85d91c8

File tree

5 files changed

+362
-1
lines changed

5 files changed

+362
-1
lines changed

packages/core/src/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import * as data from './data'
44
import * as registry from './registry'
55
import * as zip from './zip'
6+
import skeleZip from './zip/skele'
67
import * as log from './log'
78
import * as propNames from './propNames'
89
import Cursor from './vendor/cursor'
@@ -16,6 +17,7 @@ export default {
1617
data,
1718
registry,
1819
zip,
20+
skeleZip,
1921
log,
2022
propNames,
2123
internal,

packages/core/src/zip/elementZipper.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict'
22

33
import { makeZipper } from '../zip'
4+
import deprecated from '../log/deprecated'
45
import { Iterable, List, Map } from 'immutable'
56
import * as R from 'ramda'
67
import {
@@ -77,7 +78,7 @@ const singleChild = childColl =>
7778
*
7879
* @param config, configuration for the object, currently supports only the `defaultChildPositions` property
7980
*/
80-
export default function elementZipper(config) {
81+
function elementZipper(config) {
8182
const { defaultChildPositions, makeZipperOverride } = config
8283
const dcp = asList(defaultChildPositions)
8384

@@ -90,3 +91,8 @@ export default function elementZipper(config) {
9091

9192
return ElementZipperType.from.bind(ElementZipperType)
9293
}
94+
95+
export default deprecated(
96+
'elementZipper is deprecated, use `skeleZip` instead',
97+
elementZipper
98+
)
Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
'use strict'
2+
3+
import R from 'ramda'
4+
import { fromJS } from 'immutable'
5+
import * as zip from '../..'
6+
import skeleZip from '..'
7+
import * as data from '../../../data'
8+
import * as propNames from '../../../propNames'
9+
10+
const childCollectionKind = '@@skele/child-collection'
11+
12+
describe('Skele Zipper', () => {
13+
const singleChild = {
14+
kind: 'parent',
15+
[propNames.children]: 'children',
16+
children: [
17+
{
18+
kind: 'lvl1',
19+
[propNames.children]: 'children',
20+
children: [
21+
{
22+
kind: 'lvl2',
23+
},
24+
],
25+
},
26+
],
27+
}
28+
29+
it('zipper should correctly navigate up and down', () => {
30+
const zipper = skeleZip(fromJS(singleChild))
31+
32+
expect(zip.node(zipper).get('kind')).toEqual('parent')
33+
expect(data.isOfKind(childCollectionKind, zip.node(zip.down(zipper)))).toBe(
34+
true
35+
)
36+
expect(
37+
R.pipe(
38+
zip.down,
39+
zip.down,
40+
zip.node
41+
)(zipper).get('kind')
42+
).toEqual('lvl1')
43+
44+
expect(
45+
data.isOfKind(
46+
childCollectionKind,
47+
48+
R.pipe(
49+
zip.down,
50+
zip.down,
51+
zip.down,
52+
zip.node
53+
)(zipper)
54+
)
55+
).toBe(true)
56+
expect(
57+
R.pipe(
58+
zip.down,
59+
zip.down,
60+
zip.down,
61+
zip.down,
62+
zip.node
63+
)(zipper).get('kind')
64+
).toEqual('lvl2')
65+
expect(
66+
R.pipe(
67+
zip.down,
68+
zip.down,
69+
zip.down,
70+
zip.down,
71+
zip.down
72+
)(zipper)
73+
).toBeNull()
74+
expect(
75+
R.pipe(
76+
zip.down,
77+
zip.up,
78+
zip.node
79+
)(zipper).get('kind')
80+
).toEqual('parent')
81+
expect(
82+
R.pipe(
83+
zip.down,
84+
zip.down,
85+
zip.down,
86+
zip.up,
87+
zip.node
88+
)(zipper).get('kind')
89+
).toEqual('lvl1')
90+
})
91+
92+
const multipleChildren = {
93+
id: 1,
94+
kind: 't',
95+
[propNames.children]: 'children',
96+
children: [
97+
{
98+
id: 2,
99+
kind: 't',
100+
[propNames.children]: 'children',
101+
children: [
102+
{
103+
id: 3,
104+
kind: 't',
105+
},
106+
{
107+
kind: 't',
108+
id: 4,
109+
},
110+
],
111+
},
112+
{
113+
id: 5,
114+
kind: 't',
115+
[propNames.children]: 'children',
116+
children: [
117+
{
118+
kind: 't',
119+
id: 6,
120+
},
121+
{
122+
kind: 't',
123+
id: 7,
124+
},
125+
],
126+
},
127+
{
128+
id: 8,
129+
kind: 't',
130+
[propNames.children]: 'children',
131+
children: [
132+
{
133+
kind: 't',
134+
id: 9,
135+
},
136+
{
137+
kind: 't',
138+
id: 10,
139+
},
140+
],
141+
},
142+
],
143+
}
144+
145+
it('zipper should correctly navigate up down left and right', () => {
146+
const zipper = skeleZip(fromJS(multipleChildren))
147+
148+
expect(zip.node(zipper).get('id')).toEqual(1)
149+
expect(data.isOfKind(childCollectionKind, zip.node(zip.down(zipper)))).toBe(
150+
true
151+
)
152+
expect(
153+
R.pipe(
154+
zip.down,
155+
zip.down,
156+
zip.node
157+
)(zipper).get('id')
158+
).toEqual(2)
159+
expect(
160+
R.pipe(
161+
zip.down,
162+
zip.down,
163+
zip.right,
164+
zip.node
165+
)(zipper).get('id')
166+
).toEqual(5)
167+
expect(
168+
R.pipe(
169+
zip.down,
170+
zip.down,
171+
zip.right,
172+
zip.right,
173+
zip.node
174+
)(zipper).get('id')
175+
).toEqual(8)
176+
expect(
177+
R.pipe(
178+
zip.down,
179+
zip.down,
180+
zip.right,
181+
zip.right,
182+
zip.left,
183+
zip.node
184+
)(zipper).get('id')
185+
).toEqual(5)
186+
expect(
187+
data.isOfKind(
188+
childCollectionKind,
189+
R.pipe(
190+
zip.down,
191+
zip.down,
192+
zip.right,
193+
zip.right,
194+
zip.left,
195+
zip.up,
196+
zip.node
197+
)(zipper)
198+
)
199+
).toBe(true)
200+
expect(
201+
R.pipe(
202+
zip.down,
203+
zip.down,
204+
zip.right,
205+
zip.right,
206+
zip.left,
207+
zip.up,
208+
zip.up,
209+
zip.node
210+
)(zipper).get('id')
211+
).toEqual(1)
212+
})
213+
214+
const multipleChildrenElements = {
215+
id: 1,
216+
kind: 't',
217+
[propNames.children]: ['left', 'right'],
218+
left: [
219+
{
220+
kind: 't',
221+
id: 2,
222+
},
223+
],
224+
right: [
225+
{
226+
kind: 't',
227+
id: 3,
228+
},
229+
{
230+
kind: 't',
231+
id: 4,
232+
},
233+
],
234+
}
235+
236+
it('zipper multiple children elements', () => {
237+
const zipper = skeleZip(fromJS(multipleChildrenElements))
238+
239+
expect(zip.node(zipper).get('id')).toEqual(1)
240+
expect(
241+
R.pipe(
242+
zip.down,
243+
zip.node
244+
)(zipper).get('propertyName')
245+
).toEqual('left')
246+
247+
expect(
248+
R.pipe(
249+
zip.down,
250+
zip.right,
251+
zip.node
252+
)(zipper).get('propertyName')
253+
).toEqual('right')
254+
255+
expect(
256+
R.pipe(
257+
zip.down,
258+
zip.down,
259+
zip.node
260+
)(zipper).get('id')
261+
).toEqual(2)
262+
expect(
263+
R.pipe(
264+
zip.down,
265+
zip.right,
266+
zip.down,
267+
zip.node
268+
)(zipper).get('id')
269+
).toEqual(3)
270+
expect(
271+
R.pipe(
272+
zip.down,
273+
zip.right,
274+
zip.down,
275+
zip.right,
276+
zip.node
277+
)(zipper).get('id')
278+
).toEqual(4)
279+
})
280+
})

packages/core/src/zip/skele/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
'use strict'
2+
import skeleZip from './zipperImpl'
3+
4+
export default skeleZip
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
'use strict'
2+
3+
import { zipper } from '../../zip'
4+
import { Iterable, List, Map } from 'immutable'
5+
import { isOfKind, asList, childPositions } from '../../data'
6+
7+
const isBranch = element => {
8+
if (isOfKind('@@skele/child-collection', element)) {
9+
return true
10+
}
11+
12+
const positions = childPositions(element)
13+
14+
return positions != null && !positions.isEmpty()
15+
}
16+
17+
const getChildren = element => {
18+
if (isOfKind('@@skele/child-collection', element)) {
19+
return element.get('children').toArray()
20+
}
21+
// at a children collection level
22+
const positions = childPositions(element)
23+
24+
const children = positions
25+
.reduce(
26+
(children, p) =>
27+
element.get(p)
28+
? children.push(makeChildCollection(p, element.get(p)))
29+
: children,
30+
List()
31+
)
32+
.toArray()
33+
34+
return children
35+
}
36+
37+
const makeChildCollection = (p, children) =>
38+
Map({
39+
kind: '@@skele/child-collection',
40+
propertyName: p,
41+
isSingle: !Iterable.isIndexed(children),
42+
children: asList(children),
43+
})
44+
45+
const makeNode = (element, children) => {
46+
if (isOfKind('@@skele/child-collection', element)) {
47+
return element.set('children', List(children))
48+
}
49+
return children.reduce(
50+
(el, childColl) =>
51+
el.set(
52+
childColl.get('propertyName'),
53+
singleChild(childColl)
54+
? childColl.getIn(['children', 0])
55+
: childColl.get('children')
56+
),
57+
element
58+
)
59+
}
60+
61+
const singleChild = childColl =>
62+
childColl.get('isSingle') && childColl.get('children').count() === 1
63+
64+
/**
65+
* Creates a zipper over a Skele state tree.
66+
*
67+
* @param root the root node of the state tree
68+
*/
69+
export default zipper.bind(undefined, isBranch, getChildren, makeNode)

0 commit comments

Comments
 (0)