Skip to content

Commit 623818f

Browse files
committed
feat: 🎸 added deleteNodeAt to TreeStateModifiers
added deleteNodeAt, a function that generates a new state after deleting a node at a supplied index
1 parent d467c8f commit 623818f

File tree

4 files changed

+695
-5
lines changed

4 files changed

+695
-5
lines changed

index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ export interface TreeState {
149149

150150
export interface TreeStateModifiers {
151151
editNodeAt: (state: State, index: number, setNode: (oldNode: Node) => Node) => State;
152+
deleteNodeAt: (state: State, index: number) => State;
152153
}
153154

154155
export const selectors: Selectors;

src/state/TreeStateModifiers.js

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1-
import {getFlattenedTreePaths, doesChangeAffectFlattenedTree, isNodeExpanded} from '../selectors/getFlattenedTree';
2-
import TreeState, {validateState, State} from './TreeState';
3-
import {replaceNodeFromTree} from '../selectors/nodes';
1+
import {
2+
getFlattenedTreePaths,
3+
doesChangeAffectFlattenedTree,
4+
isNodeExpanded,
5+
nodeHasChildren,
6+
} from '../selectors/getFlattenedTree';
7+
import TreeState, {State} from './TreeState';
8+
import {replaceNodeFromTree, deleteNodeFromTree} from '../selectors/nodes';
49

510
/**
611
* @callback setNode
@@ -20,8 +25,6 @@ export default class TreeStateModifiers {
2025
* @return {State} An internal state representation
2126
*/
2227
static editNodeAt = (state, index, setNode) => {
23-
validateState(state);
24-
2528
const node = TreeState.getNodeAt(state, index);
2629
const updatedNode = setNode(node);
2730
const flattenedTree = [...state.flattenedTree];
@@ -44,4 +47,28 @@ export default class TreeStateModifiers {
4447

4548
return new State(tree, flattenedTree);
4649
};
50+
51+
/**
52+
* Given a state, deletes a node
53+
* @param {State} state - The current state
54+
* @param {number} index - The visible row index
55+
* @return {State} An internal state representation
56+
*/
57+
static deleteNodeAt = (state, index) => {
58+
const node = TreeState.getNodeAt(state, index);
59+
60+
const flattenedTree = [...state.flattenedTree];
61+
const flattenedNodeMap = flattenedTree[index];
62+
const parents = flattenedNodeMap.slice(0, flattenedNodeMap.length - 1);
63+
64+
const numberOfVisibleDescendants = nodeHasChildren(node)
65+
? TreeState.getNumberOfVisibleDescendants(state, index)
66+
: 0;
67+
68+
flattenedTree.splice(index, 1 + numberOfVisibleDescendants);
69+
70+
const tree = deleteNodeFromTree(state.tree, {...node, parents});
71+
72+
return new State(tree, flattenedTree);
73+
};
4774
}

src/state/__tests__/TreeStateModifiers.test.js

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,4 +252,115 @@ describe('TreeStateModifiers', () => {
252252
});
253253
});
254254
});
255+
256+
describe('deleteNodeAt', () => {
257+
test('should fail when invalid state is supplied', () => {
258+
expect(() => TreeStateModifiers.deleteNodeAt('state', 0)).toThrowError(
259+
'Expected a State instance but got string',
260+
);
261+
expect(() => TreeStateModifiers.deleteNodeAt(1225, 0)).toThrowError('Expected a State instance but got number');
262+
expect(() => TreeStateModifiers.deleteNodeAt([], 0)).toThrowError('Expected a State instance but got object');
263+
expect(() => TreeStateModifiers.deleteNodeAt({}, 0)).toThrowError('Expected a State instance but got object');
264+
expect(() => TreeStateModifiers.deleteNodeAt(true, 0)).toThrowError('Expected a State instance but got boolean');
265+
expect(() => TreeStateModifiers.deleteNodeAt(() => {}, 0)).toThrowError(
266+
'Expected a State instance but got function',
267+
);
268+
});
269+
270+
test('should fail with descriptive error when node at index does not exist', () => {
271+
expect(() => TreeStateModifiers.deleteNodeAt(TreeState.createFromTree(Nodes), 20)).toThrowErrorMatchingSnapshot();
272+
});
273+
274+
describe('flattened tree', () => {
275+
test('should delete a root node with expanded children', () => {
276+
const state = TreeState.createFromTree(Nodes);
277+
278+
deepFreeze(state);
279+
280+
const {flattenedTree} = TreeStateModifiers.deleteNodeAt(state, 0);
281+
282+
expect(flattenedTree).toMatchSnapshot();
283+
});
284+
285+
test('should delete a root node without expanded children', () => {
286+
const state = TreeState.createFromTree(Nodes);
287+
288+
deepFreeze(state);
289+
290+
const {flattenedTree} = TreeStateModifiers.deleteNodeAt(state, 5);
291+
292+
expect(flattenedTree).toMatchSnapshot();
293+
});
294+
295+
test('should delete a child node with expanded children', () => {
296+
const state = TreeState.createFromTree(Nodes);
297+
298+
deepFreeze(state);
299+
300+
const {flattenedTree} = TreeStateModifiers.deleteNodeAt(state, 1);
301+
302+
expect(flattenedTree).toMatchSnapshot();
303+
});
304+
305+
test('should delete a child node without expanded children', () => {
306+
const state = TreeState.createFromTree(Nodes);
307+
308+
deepFreeze(state);
309+
310+
const {flattenedTree} = TreeStateModifiers.deleteNodeAt(state, 2);
311+
312+
expect(flattenedTree).toMatchSnapshot();
313+
});
314+
});
315+
316+
describe('tree', () => {
317+
test('should delete a root node with expanded children', () => {
318+
const state = TreeState.createFromTree(Nodes);
319+
320+
deepFreeze(state);
321+
322+
const {tree} = TreeStateModifiers.deleteNodeAt(state, 0);
323+
324+
const changes = diff(state.tree, tree);
325+
326+
expect(changes).toMatchSnapshot();
327+
});
328+
329+
test('should delete a root node without expanded children', () => {
330+
const state = TreeState.createFromTree(Nodes);
331+
332+
deepFreeze(state);
333+
334+
const {tree} = TreeStateModifiers.deleteNodeAt(state, 6);
335+
336+
const changes = diff(state.tree, tree);
337+
338+
expect(changes).toMatchSnapshot();
339+
});
340+
341+
test('should delete a child node without expanded children', () => {
342+
const state = TreeState.createFromTree(Nodes);
343+
344+
deepFreeze(state);
345+
346+
const {tree} = TreeStateModifiers.deleteNodeAt(state, 2);
347+
348+
const changes = diff(state.tree, tree);
349+
350+
expect(changes).toMatchSnapshot();
351+
});
352+
353+
test('should delete a child node with expanded children', () => {
354+
const state = TreeState.createFromTree(Nodes);
355+
356+
deepFreeze(state);
357+
358+
const {tree} = TreeStateModifiers.deleteNodeAt(state, 1);
359+
360+
const changes = diff(state.tree, tree);
361+
362+
expect(changes).toMatchSnapshot();
363+
});
364+
});
365+
});
255366
});

0 commit comments

Comments
 (0)