-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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.
- Loading branch information
Showing
5 changed files
with
362 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,280 @@ | ||
'use strict' | ||
|
||
import R from 'ramda' | ||
import { fromJS } from 'immutable' | ||
import * as zip from '../..' | ||
import skeleZip from '..' | ||
import * as data from '../../../data' | ||
import * as propNames from '../../../propNames' | ||
|
||
const childCollectionKind = '@@skele/child-collection' | ||
|
||
describe('Skele Zipper', () => { | ||
const singleChild = { | ||
kind: 'parent', | ||
[propNames.children]: 'children', | ||
children: [ | ||
{ | ||
kind: 'lvl1', | ||
[propNames.children]: 'children', | ||
children: [ | ||
{ | ||
kind: 'lvl2', | ||
}, | ||
], | ||
}, | ||
], | ||
} | ||
|
||
it('zipper should correctly navigate up and down', () => { | ||
const zipper = skeleZip(fromJS(singleChild)) | ||
|
||
expect(zip.node(zipper).get('kind')).toEqual('parent') | ||
expect(data.isOfKind(childCollectionKind, zip.node(zip.down(zipper)))).toBe( | ||
true | ||
) | ||
expect( | ||
R.pipe( | ||
zip.down, | ||
zip.down, | ||
zip.node | ||
)(zipper).get('kind') | ||
).toEqual('lvl1') | ||
|
||
expect( | ||
data.isOfKind( | ||
childCollectionKind, | ||
|
||
R.pipe( | ||
zip.down, | ||
zip.down, | ||
zip.down, | ||
zip.node | ||
)(zipper) | ||
) | ||
).toBe(true) | ||
expect( | ||
R.pipe( | ||
zip.down, | ||
zip.down, | ||
zip.down, | ||
zip.down, | ||
zip.node | ||
)(zipper).get('kind') | ||
).toEqual('lvl2') | ||
expect( | ||
R.pipe( | ||
zip.down, | ||
zip.down, | ||
zip.down, | ||
zip.down, | ||
zip.down | ||
)(zipper) | ||
).toBeNull() | ||
expect( | ||
R.pipe( | ||
zip.down, | ||
zip.up, | ||
zip.node | ||
)(zipper).get('kind') | ||
).toEqual('parent') | ||
expect( | ||
R.pipe( | ||
zip.down, | ||
zip.down, | ||
zip.down, | ||
zip.up, | ||
zip.node | ||
)(zipper).get('kind') | ||
).toEqual('lvl1') | ||
}) | ||
|
||
const multipleChildren = { | ||
id: 1, | ||
kind: 't', | ||
[propNames.children]: 'children', | ||
children: [ | ||
{ | ||
id: 2, | ||
kind: 't', | ||
[propNames.children]: 'children', | ||
children: [ | ||
{ | ||
id: 3, | ||
kind: 't', | ||
}, | ||
{ | ||
kind: 't', | ||
id: 4, | ||
}, | ||
], | ||
}, | ||
{ | ||
id: 5, | ||
kind: 't', | ||
[propNames.children]: 'children', | ||
children: [ | ||
{ | ||
kind: 't', | ||
id: 6, | ||
}, | ||
{ | ||
kind: 't', | ||
id: 7, | ||
}, | ||
], | ||
}, | ||
{ | ||
id: 8, | ||
kind: 't', | ||
[propNames.children]: 'children', | ||
children: [ | ||
{ | ||
kind: 't', | ||
id: 9, | ||
}, | ||
{ | ||
kind: 't', | ||
id: 10, | ||
}, | ||
], | ||
}, | ||
], | ||
} | ||
|
||
it('zipper should correctly navigate up down left and right', () => { | ||
const zipper = skeleZip(fromJS(multipleChildren)) | ||
|
||
expect(zip.node(zipper).get('id')).toEqual(1) | ||
expect(data.isOfKind(childCollectionKind, zip.node(zip.down(zipper)))).toBe( | ||
true | ||
) | ||
expect( | ||
R.pipe( | ||
zip.down, | ||
zip.down, | ||
zip.node | ||
)(zipper).get('id') | ||
).toEqual(2) | ||
expect( | ||
R.pipe( | ||
zip.down, | ||
zip.down, | ||
zip.right, | ||
zip.node | ||
)(zipper).get('id') | ||
).toEqual(5) | ||
expect( | ||
R.pipe( | ||
zip.down, | ||
zip.down, | ||
zip.right, | ||
zip.right, | ||
zip.node | ||
)(zipper).get('id') | ||
).toEqual(8) | ||
expect( | ||
R.pipe( | ||
zip.down, | ||
zip.down, | ||
zip.right, | ||
zip.right, | ||
zip.left, | ||
zip.node | ||
)(zipper).get('id') | ||
).toEqual(5) | ||
expect( | ||
data.isOfKind( | ||
childCollectionKind, | ||
R.pipe( | ||
zip.down, | ||
zip.down, | ||
zip.right, | ||
zip.right, | ||
zip.left, | ||
zip.up, | ||
zip.node | ||
)(zipper) | ||
) | ||
).toBe(true) | ||
expect( | ||
R.pipe( | ||
zip.down, | ||
zip.down, | ||
zip.right, | ||
zip.right, | ||
zip.left, | ||
zip.up, | ||
zip.up, | ||
zip.node | ||
)(zipper).get('id') | ||
).toEqual(1) | ||
}) | ||
|
||
const multipleChildrenElements = { | ||
id: 1, | ||
kind: 't', | ||
[propNames.children]: ['left', 'right'], | ||
left: [ | ||
{ | ||
kind: 't', | ||
id: 2, | ||
}, | ||
], | ||
right: [ | ||
{ | ||
kind: 't', | ||
id: 3, | ||
}, | ||
{ | ||
kind: 't', | ||
id: 4, | ||
}, | ||
], | ||
} | ||
|
||
it('zipper multiple children elements', () => { | ||
const zipper = skeleZip(fromJS(multipleChildrenElements)) | ||
|
||
expect(zip.node(zipper).get('id')).toEqual(1) | ||
expect( | ||
R.pipe( | ||
zip.down, | ||
zip.node | ||
)(zipper).get('propertyName') | ||
).toEqual('left') | ||
|
||
expect( | ||
R.pipe( | ||
zip.down, | ||
zip.right, | ||
zip.node | ||
)(zipper).get('propertyName') | ||
).toEqual('right') | ||
|
||
expect( | ||
R.pipe( | ||
zip.down, | ||
zip.down, | ||
zip.node | ||
)(zipper).get('id') | ||
).toEqual(2) | ||
expect( | ||
R.pipe( | ||
zip.down, | ||
zip.right, | ||
zip.down, | ||
zip.node | ||
)(zipper).get('id') | ||
).toEqual(3) | ||
expect( | ||
R.pipe( | ||
zip.down, | ||
zip.right, | ||
zip.down, | ||
zip.right, | ||
zip.node | ||
)(zipper).get('id') | ||
).toEqual(4) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
'use strict' | ||
import skeleZip from './zipperImpl' | ||
|
||
export default skeleZip |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
'use strict' | ||
|
||
import { zipper } from '../../zip' | ||
import { Iterable, List, Map } from 'immutable' | ||
import { isOfKind, asList, childPositions } from '../../data' | ||
|
||
const isBranch = element => { | ||
if (isOfKind('@@skele/child-collection', element)) { | ||
return true | ||
} | ||
|
||
const positions = childPositions(element) | ||
|
||
return positions != null && !positions.isEmpty() | ||
} | ||
|
||
const getChildren = element => { | ||
if (isOfKind('@@skele/child-collection', element)) { | ||
return element.get('children').toArray() | ||
} | ||
// at a children collection level | ||
const positions = childPositions(element) | ||
|
||
const children = positions | ||
.reduce( | ||
(children, p) => | ||
element.get(p) | ||
? children.push(makeChildCollection(p, element.get(p))) | ||
: children, | ||
List() | ||
) | ||
.toArray() | ||
|
||
return children | ||
} | ||
|
||
const makeChildCollection = (p, children) => | ||
Map({ | ||
kind: '@@skele/child-collection', | ||
propertyName: p, | ||
isSingle: !Iterable.isIndexed(children), | ||
children: asList(children), | ||
}) | ||
|
||
const makeNode = (element, children) => { | ||
if (isOfKind('@@skele/child-collection', element)) { | ||
return element.set('children', List(children)) | ||
} | ||
return children.reduce( | ||
(el, childColl) => | ||
el.set( | ||
childColl.get('propertyName'), | ||
singleChild(childColl) | ||
? childColl.getIn(['children', 0]) | ||
: childColl.get('children') | ||
), | ||
element | ||
) | ||
} | ||
|
||
const singleChild = childColl => | ||
childColl.get('isSingle') && childColl.get('children').count() === 1 | ||
|
||
/** | ||
* Creates a zipper over a Skele state tree. | ||
* | ||
* @param root the root node of the state tree | ||
*/ | ||
export default zipper.bind(undefined, isBranch, getChildren, makeNode) |