Skip to content

Commit 20dfaeb

Browse files
committed
test: added test cases for helper functions and zustand store
1 parent 0df57db commit 20dfaeb

13 files changed

+1296
-459
lines changed
File renamed without changes.

jest.setup.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import '@testing-library/jest-native/extend-expect';

package.json

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,24 +66,29 @@
6666
"@react-native-community/eslint-config": "^3.0.2",
6767
"@release-it/conventional-changelog": "^5.0.0",
6868
"@shopify/flash-list": "1.x.x",
69+
"@testing-library/jest-native": "^5.4.2",
70+
"@testing-library/react-native": "^12.1.2",
6971
"@types/color": "^3.0.3",
70-
"@types/jest": "^28.1.2",
72+
"@types/jest": "^29.5.3",
7173
"@types/react": "~17.0.21",
7274
"@types/react-native": "0.70.0",
7375
"@types/react-native-vector-icons": "^6.4.13",
76+
"@types/react-test-renderer": "^18.0.0",
7477
"commitlint": "^17.0.2",
7578
"del-cli": "^5.0.0",
7679
"eslint": "^8.4.1",
7780
"eslint-config-prettier": "^8.5.0",
7881
"eslint-plugin-prettier": "^4.0.0",
79-
"jest": "^28.1.1",
82+
"jest": "^29.6.1",
8083
"pod-install": "^0.1.0",
8184
"prettier": "^2.0.5",
8285
"react": "18.2.0",
8386
"react-native": "0.72.1",
8487
"react-native-builder-bob": "^0.20.0",
8588
"react-native-paper": "5.x.x",
89+
"react-test-renderer": "^18.2.0",
8690
"release-it": "^15.0.0",
91+
"ts-jest": "^29.1.1",
8792
"turbo": "^1.10.7",
8893
"typescript": "^5.0.2"
8994
},
@@ -103,6 +108,12 @@
103108
"packageManager": "^[email protected]",
104109
"jest": {
105110
"preset": "react-native",
111+
"setupFilesAfterEnv": [
112+
"./jest.setup.ts"
113+
],
114+
"transformIgnorePatterns": [
115+
"node_modules/(?!(jest-)?react-native|@react-native-community|@react-navigation|@react-native/js-polyfills)"
116+
],
106117
"modulePathIgnorePatterns": [
107118
"<rootDir>/example/node_modules",
108119
"<rootDir>/lib/"

src/__mocks__/generateTree.mock.ts

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import { TreeNode, __FlattenedTreeNode__ } from "../types/treeView.types";
2+
3+
export const tree3d2b = [
4+
{
5+
"id": "1",
6+
"name": "node1",
7+
"children": [
8+
{
9+
"id": "1.1",
10+
"name": "node1.1",
11+
"children": [
12+
{
13+
"id": "1.1.1",
14+
"name": "node1.1.1"
15+
},
16+
{
17+
"id": "1.1.2",
18+
"name": "node1.1.2"
19+
}
20+
]
21+
},
22+
{
23+
"id": "1.2",
24+
"name": "node1.2",
25+
"children": [
26+
{
27+
"id": "1.2.1",
28+
"name": "node1.2.1"
29+
},
30+
{
31+
"id": "1.2.2",
32+
"name": "node1.2.2"
33+
}
34+
]
35+
}
36+
]
37+
},
38+
{
39+
"id": "2",
40+
"name": "node2",
41+
"children": [
42+
{
43+
"id": "2.1",
44+
"name": "node2.1",
45+
"children": [
46+
{
47+
"id": "2.1.1",
48+
"name": "node2.1.1"
49+
},
50+
{
51+
"id": "2.1.2",
52+
"name": "node2.1.2"
53+
}
54+
]
55+
},
56+
{
57+
"id": "2.2",
58+
"name": "node2.2",
59+
"children": [
60+
{
61+
"id": "2.2.1",
62+
"name": "node2.2.1"
63+
},
64+
{
65+
"id": "2.2.2",
66+
"name": "node2.2.2"
67+
}
68+
]
69+
}
70+
]
71+
}
72+
];
73+
74+
export function generateTree(
75+
depth: number,
76+
breadth: number,
77+
prefix: string = ''
78+
): TreeNode[] {
79+
let nodes: TreeNode[] = [];
80+
81+
for (let i = 1; i <= breadth; i++) {
82+
let node: TreeNode = {
83+
id: `${prefix}${i}`,
84+
name: `node${prefix}${i}`
85+
};
86+
87+
if (depth > 1) {
88+
node.children = generateTree(depth - 1, breadth, `${prefix}${i}.`);
89+
}
90+
91+
nodes.push(node);
92+
}
93+
94+
return nodes;
95+
}
96+
97+
export function generateExpectedFlatTree(
98+
nodes: TreeNode[],
99+
expandedIds: Set<string>,
100+
level: number = 0
101+
): __FlattenedTreeNode__[] {
102+
let flattened: __FlattenedTreeNode__[] = [];
103+
for (let node of nodes) {
104+
flattened.push({ ...node, level: level });
105+
if (node.children && expandedIds.has(node.id)) {
106+
flattened = [
107+
...flattened,
108+
...generateExpectedFlatTree(node.children, expandedIds, level + 1)
109+
];
110+
}
111+
}
112+
return flattened;
113+
}
114+
115+
export function createRandomNumberSet() {
116+
let randomNumbers = new Set<string>();
117+
118+
while (randomNumbers.size < 10) {
119+
// Random number between 0 and 99
120+
let randomNumber = Math.floor(Math.random() * 100);
121+
randomNumbers.add(randomNumber.toString());
122+
}
123+
124+
return randomNumbers;
125+
}

src/__mocks__/zustand.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { act } from '@testing-library/react-native';
2+
import { StateCreator } from 'zustand';
3+
4+
const { create: actualCreate } = jest.requireActual('zustand');
5+
6+
// a variable to hold reset functions for all stores declared in the app
7+
const storeResetFns = new Set<() => void>();
8+
9+
// when creating a store, we get its initial state, create a reset function and add it in the set
10+
export const create = <S>(createState: StateCreator<S>) => {
11+
const store = actualCreate(createState);
12+
const initialState = store.getState();
13+
storeResetFns.add(() => store.setState(initialState, true));
14+
return store;
15+
};
16+
17+
// Reset all stores after each test run
18+
beforeEach(async () => {
19+
await act(() =>
20+
storeResetFns.forEach(resetFn => {
21+
resetFn();
22+
})
23+
);
24+
});
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
jest.mock('zustand');
2+
3+
import { useTreeViewStore } from '../store/treeView.store';
4+
import { tree3d2b } from "../__mocks__/generateTree.mock";
5+
import {
6+
collapseAll,
7+
expandAll,
8+
handleToggleExpand,
9+
initializeNodeMaps
10+
} from '../helpers';
11+
import { act } from 'react-test-renderer';
12+
import { TreeNode } from '../types/treeView.types';
13+
14+
describe('expandAll', () => {
15+
beforeEach(() => {
16+
useTreeViewStore.setState(useTreeViewStore.getState(), true);
17+
18+
// Setup mock tree
19+
useTreeViewStore.getState().updateInitialTreeViewData(tree3d2b);
20+
initializeNodeMaps(tree3d2b);
21+
});
22+
23+
it('calls expandAll on initial tree(all collapsed)', () => {
24+
act(() => {
25+
expandAll();
26+
});
27+
28+
const { expanded, nodeMap } = useTreeViewStore.getState();
29+
30+
// Convert nodeMap.keys() iterator to a Set for comparison
31+
const nodeKeys = new Set(nodeMap.keys());
32+
expect(expanded).toEqual(nodeKeys);
33+
});
34+
35+
it('expands all node in tree with some nodes which are already expanded', () => {
36+
act(() => {
37+
handleToggleExpand('1');
38+
handleToggleExpand('2');
39+
handleToggleExpand('1.1');
40+
handleToggleExpand('1.2');
41+
handleToggleExpand('1.1');
42+
43+
expandAll();
44+
});
45+
46+
const { expanded, nodeMap } = useTreeViewStore.getState();
47+
48+
// Convert nodeMap.keys() iterator to a Set for comparison
49+
const nodeKeys = new Set(nodeMap.keys());
50+
expect(expanded).toEqual(nodeKeys);
51+
});
52+
});
53+
54+
describe('collapseAll', () => {
55+
beforeEach(() => {
56+
useTreeViewStore.setState(useTreeViewStore.getState(), true);
57+
58+
// Setup mock tree
59+
useTreeViewStore.getState().updateInitialTreeViewData(tree3d2b);
60+
initializeNodeMaps(tree3d2b);
61+
});
62+
63+
it('calls collapseAll on initial tree(all collapsed)', () => {
64+
act(() => {
65+
collapseAll();
66+
});
67+
68+
const { expanded } = useTreeViewStore.getState();
69+
expect(expanded).toEqual(new Set<string>());
70+
});
71+
72+
it('collapses all node in tree with some nodes which are already expanded', () => {
73+
act(() => {
74+
handleToggleExpand('1');
75+
handleToggleExpand('2');
76+
handleToggleExpand('1.1');
77+
handleToggleExpand('1.2');
78+
handleToggleExpand('1.1');
79+
80+
collapseAll();
81+
});
82+
83+
const { expanded } = useTreeViewStore.getState();
84+
85+
expect(expanded).toEqual(new Set<string>());
86+
});
87+
});
88+
89+
describe('handleToggleExpand', () => {
90+
beforeEach(() => {
91+
useTreeViewStore.setState(useTreeViewStore.getState(), true);
92+
});
93+
94+
test('handleToggleExpand correctly toggles the expanded state of a tree node', () => {
95+
const initialData: TreeNode[] = [
96+
{
97+
id: '1',
98+
name: 'Node 1',
99+
children: [
100+
{
101+
id: '1.1',
102+
name: 'Node 1.1'
103+
},
104+
{
105+
id: '1.2',
106+
name: 'Node 1.2',
107+
children: [{
108+
id: '1.2.1',
109+
name: 'Node 1.2.1'
110+
}]
111+
},
112+
],
113+
},
114+
{
115+
id: '2',
116+
name: 'Node 2'
117+
},
118+
];
119+
120+
act(() => {
121+
useTreeViewStore.getState().updateInitialTreeViewData(initialData);
122+
initializeNodeMaps(initialData);
123+
});
124+
125+
act(() => {
126+
handleToggleExpand('1');
127+
});
128+
129+
// Node '1' should now be expanded
130+
let { expanded } = useTreeViewStore.getState();
131+
expect(expanded.has('1')).toBeTruthy();
132+
133+
act(() => {
134+
handleToggleExpand('1.2');
135+
});
136+
137+
// Node '1.2' should now be expanded, Node '1'(parent) should remain expanded
138+
expanded = useTreeViewStore.getState().expanded;
139+
expect(expanded.has('1.2')).toBeTruthy();
140+
expect(expanded.has('1')).toBeTruthy();
141+
142+
act(() => {
143+
handleToggleExpand('1');
144+
handleToggleExpand('2');
145+
});
146+
147+
// Node '1' and its descendants should now be collapsed but Node '2' should remain expanded
148+
expanded = useTreeViewStore.getState().expanded;
149+
150+
expect(expanded.has('1')).toBeFalsy();
151+
expect(expanded.has('1.1')).toBeFalsy();
152+
expect(expanded.has('1.2')).toBeFalsy();
153+
expect(expanded.has('1.2.1')).toBeFalsy();
154+
155+
expect(expanded.has('2')).toBeTruthy();
156+
});
157+
});

0 commit comments

Comments
 (0)