Skip to content

Commit 33933d0

Browse files
committed
feat: 🎸 added TreeState
added TreeState, an immutable strucuture that represents the tree state
1 parent a941029 commit 33933d0

File tree

4 files changed

+228
-0
lines changed

4 files changed

+228
-0
lines changed

index.d.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,4 +135,15 @@ interface Selectors {
135135
updateNode: (node: FlattenedNode, state: {[stateKey: string]: any}) => NodeAction;
136136
}
137137

138+
interface State {
139+
flattenedTree: Array<number | string>[];
140+
tree: Node[];
141+
}
142+
143+
export interface TreeState {
144+
getNodeAt: (state: State, index: number) => Node;
145+
getTree: (state: State) => Node[];
146+
createFromTree: (tree: Node[]) => State;
147+
}
148+
138149
export const selectors: Selectors;

src/state/TreeState.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import {getFlattenedTreePaths} from '../selectors/getFlattenedTree';
2+
import {getNodeFromPath} from '../selectors/nodes';
3+
4+
class State {
5+
flattenedTree = null;
6+
tree = null;
7+
8+
constructor(tree, flattenedTree) {
9+
this.tree = tree;
10+
this.flattenedTree = flattenedTree || getFlattenedTreePaths(tree);
11+
}
12+
}
13+
14+
export const validateState = state => {
15+
if (!(state instanceof State)) {
16+
throw new Error(`Expected a State instance but got ${typeof state}`);
17+
}
18+
};
19+
20+
/**
21+
* Immutable structure that represents the TreeState.
22+
*/
23+
export default class TreeState {
24+
/**
25+
* Given a state, finds a node at a certain row index.
26+
* @param {State} state - The current state
27+
* @param {number} index - The visible row index
28+
* @return {State} An internal state representation
29+
*/
30+
static getNodeAt = (state, index) => {
31+
validateState(state);
32+
33+
const rowPath = state.flattenedTree[index];
34+
35+
if (!rowPath) {
36+
throw Error(
37+
`Tried to get node at row "${index}" but got nothing, the tree are ${state.flattenedTree.length} visible rows`,
38+
);
39+
}
40+
41+
return getNodeFromPath(rowPath, state.tree);
42+
};
43+
44+
/**
45+
* Given a state, gets the tree
46+
* @param {State} state - The current state
47+
* @return {Node[]} The tree
48+
*/
49+
static getTree = state => {
50+
validateState(state);
51+
52+
return state.tree;
53+
};
54+
55+
/**
56+
* Creates an instance of state.
57+
* @param {Node[]} tree - The original tree
58+
* @return {State} An internal state representation
59+
*/
60+
static createFromTree = tree => {
61+
if (!tree) {
62+
throw Error('A falsy tree was supplied in tree creation');
63+
}
64+
65+
if (!Array.isArray(tree)) {
66+
throw Error('An invalid tree was supplied in creation');
67+
}
68+
69+
return new State(tree);
70+
};
71+
}

src/state/__tests__/TreeState.test.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import TreeState from '../TreeState';
2+
import {Nodes} from '../../../testData/sampleTree';
3+
4+
describe('TreeState', () => {
5+
describe('createFromTree', () => {
6+
test('should fail when falsy value is supplied', () => {
7+
expect(() => TreeState.createFromTree()).toThrowError('A falsy tree was supplied in tree creation');
8+
expect(() => TreeState.createFromTree('')).toThrowError('A falsy tree was supplied in tree creation');
9+
expect(() => TreeState.createFromTree(0)).toThrowError('A falsy tree was supplied in tree creation');
10+
expect(() => TreeState.createFromTree(false)).toThrowError('A falsy tree was supplied in tree creation');
11+
});
12+
13+
test('should fail when an invalid value is supplied', () => {
14+
expect(() => TreeState.createFromTree({})).toThrowError('An invalid tree was supplied in creation');
15+
expect(() => TreeState.createFromTree('tree')).toThrowError('An invalid tree was supplied in creation');
16+
expect(() => TreeState.createFromTree(1234)).toThrowError('An invalid tree was supplied in creation');
17+
expect(() => TreeState.createFromTree(true)).toThrowError('An invalid tree was supplied in creation');
18+
});
19+
20+
test('should create state when a valid tree is supplied', () => {
21+
const {tree, flattenedTree} = TreeState.createFromTree(Nodes);
22+
23+
expect(tree).toEqual(Nodes);
24+
expect(flattenedTree).toMatchSnapshot();
25+
});
26+
});
27+
28+
describe('getNodeAt', () => {
29+
test('should get a for an existing rowId', () => {
30+
expect(TreeState.getNodeAt(TreeState.createFromTree(Nodes), 0)).toBe(Nodes[0]);
31+
expect(TreeState.getNodeAt(TreeState.createFromTree(Nodes), 1)).toMatchSnapshot('2nd row');
32+
expect(TreeState.getNodeAt(TreeState.createFromTree(Nodes), 2)).toMatchSnapshot('3rd row');
33+
expect(TreeState.getNodeAt(TreeState.createFromTree(Nodes), 6)).toMatchSnapshot('7th row');
34+
});
35+
36+
test('should fail with a custom error when supplied rowId does not exist', () => {
37+
expect(() => TreeState.getNodeAt(TreeState.createFromTree(Nodes), 25)).toThrowErrorMatchingSnapshot();
38+
});
39+
40+
test('should fail for when invalid state is supplied', () => {
41+
expect(() => TreeState.getNodeAt('state', 0)).toThrowError('Expected a State instance but got string');
42+
expect(() => TreeState.getNodeAt(1225, 0)).toThrowError('Expected a State instance but got number');
43+
expect(() => TreeState.getNodeAt([], 0)).toThrowError('Expected a State instance but got object');
44+
expect(() => TreeState.getNodeAt({}, 0)).toThrowError('Expected a State instance but got object');
45+
expect(() => TreeState.getNodeAt(true, 0)).toThrowError('Expected a State instance but got boolean');
46+
expect(() => TreeState.getNodeAt(() => {}, 0)).toThrowError('Expected a State instance but got function');
47+
});
48+
});
49+
50+
describe('getTree', () => {
51+
test('should get a tree', () => {
52+
expect(TreeState.getTree(TreeState.createFromTree(Nodes))).toEqual(Nodes);
53+
});
54+
55+
test('should fail for when invalid state is supplied', () => {
56+
expect(() => TreeState.getTree('state')).toThrowError('Expected a State instance but got string');
57+
expect(() => TreeState.getTree(1225)).toThrowError('Expected a State instance but got number');
58+
expect(() => TreeState.getTree([])).toThrowError('Expected a State instance but got object');
59+
expect(() => TreeState.getTree({})).toThrowError('Expected a State instance but got object');
60+
expect(() => TreeState.getTree(true)).toThrowError('Expected a State instance but got boolean');
61+
expect(() => TreeState.getTree(() => {})).toThrowError('Expected a State instance but got function');
62+
});
63+
});
64+
});
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`TreeState createFromTree should create state when a valid tree is supplied 1`] = `
4+
Array [
5+
Array [
6+
0,
7+
],
8+
Array [
9+
0,
10+
2,
11+
],
12+
Array [
13+
0,
14+
2,
15+
3,
16+
],
17+
Array [
18+
0,
19+
2,
20+
4,
21+
],
22+
Array [
23+
0,
24+
5,
25+
],
26+
Array [
27+
1,
28+
],
29+
Array [
30+
"z",
31+
],
32+
]
33+
`;
34+
35+
exports[`TreeState getNodeAt should fail with a custom error when supplied rowId does not exist 1`] = `"Tried to get node at row \\"25\\" but got nothing, the tree are 7 visible rows"`;
36+
37+
exports[`TreeState getNodeAt should get a for an existing rowId: 2nd row 1`] = `
38+
Object {
39+
"children": Array [
40+
Object {
41+
"id": 3,
42+
"name": "Leaf 3",
43+
"state": Object {
44+
"deletable": true,
45+
"favorite": true,
46+
},
47+
},
48+
Object {
49+
"id": 4,
50+
"name": "Leaf 4",
51+
},
52+
],
53+
"id": 2,
54+
"name": "Leaf 2",
55+
"state": Object {
56+
"deletable": true,
57+
"expanded": true,
58+
},
59+
}
60+
`;
61+
62+
exports[`TreeState getNodeAt should get a for an existing rowId: 3rd row 1`] = `
63+
Object {
64+
"id": 3,
65+
"name": "Leaf 3",
66+
"state": Object {
67+
"deletable": true,
68+
"favorite": true,
69+
},
70+
}
71+
`;
72+
73+
exports[`TreeState getNodeAt should get a for an existing rowId: 7th row 1`] = `
74+
Object {
75+
"id": "z",
76+
"name": "Leaf z",
77+
"state": Object {
78+
"deletable": true,
79+
"favorite": true,
80+
},
81+
}
82+
`;

0 commit comments

Comments
 (0)