Skip to content

Commit 2e8bb2d

Browse files
committed
test: add reducer tests for recursive file deletion
1 parent 03cbf2b commit 2e8bb2d

1 file changed

Lines changed: 252 additions & 0 deletions

File tree

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
import files, { initialState } from './files';
2+
import * as ActionTypes from '../../../constants';
3+
4+
describe('files reducer', () => {
5+
describe('DELETE_FILE action', () => {
6+
it('removes file from state and parent children array', () => {
7+
const state = initialState();
8+
const sketchFile = state.find((f) => f.name === 'sketch.js');
9+
const rootId = state.find((f) => f.name === 'root').id;
10+
11+
const action = {
12+
type: ActionTypes.DELETE_FILE,
13+
id: sketchFile.id,
14+
parentId: rootId
15+
};
16+
17+
const newState = files(state, action);
18+
19+
// File should be removed from state
20+
expect(newState.find((f) => f.id === sketchFile.id)).toBeUndefined();
21+
expect(newState).toHaveLength(state.length - 1);
22+
23+
// Parent's children array should be updated
24+
const root = newState.find((f) => f.id === rootId);
25+
expect(root.children).not.toContain(sketchFile.id);
26+
expect(root.children).toHaveLength(2); // Originally 3 children
27+
});
28+
29+
it('recursively deletes all descendants when deleting folder', () => {
30+
// Setup: Create folder with nested file
31+
let state = initialState();
32+
const rootId = state.find((f) => f.name === 'root').id;
33+
34+
state = files(state, {
35+
type: ActionTypes.CREATE_FILE,
36+
id: 'folder1',
37+
_id: 'folder1',
38+
name: 'components',
39+
content: '',
40+
children: [],
41+
fileType: 'folder',
42+
parentId: rootId
43+
});
44+
45+
state = files(state, {
46+
type: ActionTypes.CREATE_FILE,
47+
id: 'child1',
48+
_id: 'child1',
49+
name: 'Button.jsx',
50+
content: '',
51+
children: [],
52+
parentId: 'folder1'
53+
});
54+
55+
const lengthBefore = state.length;
56+
57+
// Act: Delete folder
58+
const action = {
59+
type: ActionTypes.DELETE_FILE,
60+
id: 'folder1',
61+
parentId: rootId
62+
};
63+
64+
const newState = files(state, action);
65+
66+
// Assert: Both folder and child should be deleted
67+
expect(newState.find((f) => f.id === 'folder1')).toBeUndefined();
68+
expect(newState.find((f) => f.id === 'child1')).toBeUndefined();
69+
expect(newState).toHaveLength(lengthBefore - 2);
70+
71+
// Assert: Parent cleanup
72+
const root = newState.find((f) => f.id === rootId);
73+
expect(root.children).not.toContain('folder1');
74+
});
75+
76+
it('handles deeply nested folder hierarchies', () => {
77+
// Setup: root > folder1 > folder2 > deepFile
78+
let state = initialState();
79+
const rootId = state.find((f) => f.name === 'root').id;
80+
81+
state = files(state, {
82+
type: ActionTypes.CREATE_FILE,
83+
id: 'folder1',
84+
_id: 'folder1',
85+
name: 'src',
86+
content: '',
87+
children: [],
88+
fileType: 'folder',
89+
parentId: rootId
90+
});
91+
92+
state = files(state, {
93+
type: ActionTypes.CREATE_FILE,
94+
id: 'folder2',
95+
_id: 'folder2',
96+
name: 'utils',
97+
content: '',
98+
children: [],
99+
fileType: 'folder',
100+
parentId: 'folder1'
101+
});
102+
103+
state = files(state, {
104+
type: ActionTypes.CREATE_FILE,
105+
id: 'deepFile',
106+
_id: 'deepFile',
107+
name: 'helper.js',
108+
content: '',
109+
children: [],
110+
parentId: 'folder2'
111+
});
112+
113+
const lengthBefore = state.length;
114+
115+
// Act: Delete folder1 (should cascade to folder2 and deepFile)
116+
const action = {
117+
type: ActionTypes.DELETE_FILE,
118+
id: 'folder1',
119+
parentId: rootId
120+
};
121+
122+
const newState = files(state, action);
123+
124+
// Assert: All three items deleted
125+
expect(newState.find((f) => f.id === 'folder1')).toBeUndefined();
126+
expect(newState.find((f) => f.id === 'folder2')).toBeUndefined();
127+
expect(newState.find((f) => f.id === 'deepFile')).toBeUndefined();
128+
expect(newState).toHaveLength(lengthBefore - 3);
129+
});
130+
131+
it('handles empty folder deletion', () => {
132+
let state = initialState();
133+
const rootId = state.find((f) => f.name === 'root').id;
134+
135+
// Create empty folder
136+
state = files(state, {
137+
type: ActionTypes.CREATE_FILE,
138+
id: 'emptyFolder',
139+
_id: 'emptyFolder',
140+
name: 'docs',
141+
content: '',
142+
children: [],
143+
fileType: 'folder',
144+
parentId: rootId
145+
});
146+
147+
const action = {
148+
type: ActionTypes.DELETE_FILE,
149+
id: 'emptyFolder',
150+
parentId: rootId
151+
};
152+
153+
const newState = files(state, action);
154+
155+
// Folder should be removed
156+
expect(newState.find((f) => f.id === 'emptyFolder')).toBeUndefined();
157+
158+
// Parent should be updated
159+
const root = newState.find((f) => f.id === rootId);
160+
expect(root.children).not.toContain('emptyFolder');
161+
});
162+
163+
it('does not affect sibling files when deleting', () => {
164+
const state = initialState();
165+
const sketchFile = state.find((f) => f.name === 'sketch.js');
166+
const htmlFile = state.find((f) => f.name === 'index.html');
167+
const cssFile = state.find((f) => f.name === 'style.css');
168+
const rootId = state.find((f) => f.name === 'root').id;
169+
170+
const action = {
171+
type: ActionTypes.DELETE_FILE,
172+
id: sketchFile.id,
173+
parentId: rootId
174+
};
175+
176+
const newState = files(state, action);
177+
178+
// Siblings should still exist with unchanged content
179+
const htmlInNewState = newState.find((f) => f.id === htmlFile.id);
180+
const cssInNewState = newState.find((f) => f.id === cssFile.id);
181+
182+
expect(htmlInNewState).toBeDefined();
183+
expect(cssInNewState).toBeDefined();
184+
expect(htmlInNewState.content).toBe(htmlFile.content);
185+
expect(htmlInNewState.name).toBe('index.html');
186+
});
187+
188+
it('maintains state consistency after folder with multiple children deleted', () => {
189+
let state = initialState();
190+
const rootId = state.find((f) => f.name === 'root').id;
191+
192+
// Create folder with multiple children
193+
state = files(state, {
194+
type: ActionTypes.CREATE_FILE,
195+
id: 'components',
196+
_id: 'components',
197+
name: 'components',
198+
content: '',
199+
children: [],
200+
fileType: 'folder',
201+
parentId: rootId
202+
});
203+
204+
state = files(state, {
205+
type: ActionTypes.CREATE_FILE,
206+
id: 'button',
207+
_id: 'button',
208+
name: 'Button.jsx',
209+
content: '',
210+
children: [],
211+
parentId: 'components'
212+
});
213+
214+
state = files(state, {
215+
type: ActionTypes.CREATE_FILE,
216+
id: 'input',
217+
_id: 'input',
218+
name: 'Input.jsx',
219+
content: '',
220+
children: [],
221+
parentId: 'components'
222+
});
223+
224+
const lengthBefore = state.length;
225+
226+
// Delete folder
227+
const action = {
228+
type: ActionTypes.DELETE_FILE,
229+
id: 'components',
230+
parentId: rootId
231+
};
232+
233+
const newState = files(state, action);
234+
235+
// All three files should be gone
236+
expect(newState).toHaveLength(lengthBefore - 3);
237+
238+
// No orphaned files (all remaining files should be reachable from root)
239+
const referencedIds = new Set();
240+
newState.forEach((file) => {
241+
if (file.fileType === 'folder') {
242+
file.children.forEach((childId) => referencedIds.add(childId));
243+
}
244+
});
245+
246+
const nonRootFiles = newState.filter((f) => f.name !== 'root');
247+
nonRootFiles.forEach((file) => {
248+
expect(referencedIds.has(file.id)).toBe(true);
249+
});
250+
});
251+
});
252+
});

0 commit comments

Comments
 (0)