Skip to content

Commit 15491d5

Browse files
committed
Refactor DELETE_FILE tests for reducer isolation and improved coverage
1 parent a62d9a9 commit 15491d5

1 file changed

Lines changed: 136 additions & 199 deletions

File tree

Lines changed: 136 additions & 199 deletions
Original file line numberDiff line numberDiff line change
@@ -1,252 +1,189 @@
1-
import files, { initialState } from './files';
1+
import files from './files';
22
import * as ActionTypes from '../../../constants';
33

4+
// Helper to build test state without dispatching actions
5+
// Keeps DELETE_FILE tests isolated
6+
function createTestState(fileDescriptors) {
7+
return fileDescriptors.map((f) => ({
8+
id: f.id,
9+
_id: f.id,
10+
name: f.name,
11+
content: f.content ?? '',
12+
fileType: f.fileType ?? 'file',
13+
children: f.children ?? [],
14+
parentId: f.parentId ?? null
15+
}));
16+
}
17+
418
describe('files reducer', () => {
519
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;
20+
it('removes file and updates parent children array', () => {
21+
const state = createTestState([
22+
{
23+
id: 'root',
24+
name: 'root',
25+
fileType: 'folder',
26+
children: ['sketch', 'html', 'css']
27+
},
28+
{
29+
id: 'sketch',
30+
name: 'sketch.js',
31+
content: 'draw()',
32+
parentId: 'root'
33+
},
34+
{ id: 'html', name: 'index.html', content: '<html>', parentId: 'root' },
35+
{ id: 'css', name: 'style.css', content: 'body {}', parentId: 'root' }
36+
]);
1037

1138
const action = {
1239
type: ActionTypes.DELETE_FILE,
13-
id: sketchFile.id,
14-
parentId: rootId
40+
id: 'sketch',
41+
parentId: 'root'
1542
};
1643

1744
const newState = files(state, action);
1845

1946
// File should be removed from state
20-
expect(newState.find((f) => f.id === sketchFile.id)).toBeUndefined();
47+
expect(newState.find((f) => f.id === 'sketch')).toBeUndefined();
2148
expect(newState).toHaveLength(state.length - 1);
2249

2350
// 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-
});
51+
const root = newState.find((f) => f.id === 'root');
52+
expect(root.children).not.toContain('sketch');
53+
expect(root.children).toHaveLength(2);
4454

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-
});
55+
// Siblings should still exist with unchanged content
56+
const htmlFile = newState.find((f) => f.id === 'html');
57+
const cssFile = newState.find((f) => f.id === 'css');
58+
expect(htmlFile).toBeDefined();
59+
expect(cssFile).toBeDefined();
60+
expect(htmlFile.content).toBe('<html>');
61+
expect(htmlFile.name).toBe('index.html');
62+
expect(cssFile.content).toBe('body {}');
63+
});
5464

55-
const lengthBefore = state.length;
65+
it('recursively deletes folder and all descendants', () => {
66+
const state = createTestState([
67+
{
68+
id: 'root',
69+
name: 'root',
70+
fileType: 'folder',
71+
children: ['components']
72+
},
73+
{
74+
id: 'components',
75+
name: 'components',
76+
fileType: 'folder',
77+
children: ['button', 'input'],
78+
parentId: 'root'
79+
},
80+
{ id: 'button', name: 'Button.jsx', parentId: 'components' },
81+
{ id: 'input', name: 'Input.jsx', parentId: 'components' }
82+
]);
5683

57-
// Act: Delete folder
5884
const action = {
5985
type: ActionTypes.DELETE_FILE,
60-
id: 'folder1',
61-
parentId: rootId
86+
id: 'components',
87+
parentId: 'root'
6288
};
6389

6490
const newState = files(state, action);
6591

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);
92+
// All three items should be deleted
93+
expect(newState.find((f) => f.id === 'components')).toBeUndefined();
94+
expect(newState.find((f) => f.id === 'button')).toBeUndefined();
95+
expect(newState.find((f) => f.id === 'input')).toBeUndefined();
96+
expect(newState).toHaveLength(state.length - 3);
7097

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-
});
98+
// Parent cleanup
99+
const root = newState.find((f) => f.id === 'root');
100+
expect(root.children).not.toContain('components');
91101

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'
102+
// No orphaned files, every non root file should be referenced in some parent's children array
103+
const referencedIds = new Set();
104+
newState.forEach((file) => {
105+
if (file.children) {
106+
file.children.forEach((childId) => referencedIds.add(childId));
107+
}
101108
});
102109

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'
110+
const nonRootFiles = newState.filter((f) => f.name !== 'root');
111+
nonRootFiles.forEach((file) => {
112+
expect(referencedIds.has(file.id)).toBe(true);
111113
});
114+
});
112115

113-
const lengthBefore = state.length;
116+
it('handles deeply nested folder hierarchies', () => {
117+
const state = createTestState([
118+
{
119+
id: 'root',
120+
name: 'root',
121+
fileType: 'folder',
122+
children: ['src']
123+
},
124+
{
125+
id: 'src',
126+
name: 'src',
127+
fileType: 'folder',
128+
children: ['utils'],
129+
parentId: 'root'
130+
},
131+
{
132+
id: 'utils',
133+
name: 'utils',
134+
fileType: 'folder',
135+
children: ['helper'],
136+
parentId: 'src'
137+
},
138+
{ id: 'helper', name: 'helper.js', parentId: 'utils' }
139+
]);
114140

115-
// Act: Delete folder1 (should cascade to folder2 and deepFile)
116141
const action = {
117142
type: ActionTypes.DELETE_FILE,
118-
id: 'folder1',
119-
parentId: rootId
143+
id: 'src',
144+
parentId: 'root'
120145
};
121146

122147
const newState = files(state, action);
123148

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);
149+
// All three nested items should be deleted
150+
expect(newState.find((f) => f.id === 'src')).toBeUndefined();
151+
expect(newState.find((f) => f.id === 'utils')).toBeUndefined();
152+
expect(newState.find((f) => f.id === 'helper')).toBeUndefined();
153+
expect(newState).toHaveLength(state.length - 3);
129154
});
130155

131156
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-
});
157+
const state = createTestState([
158+
{
159+
id: 'root',
160+
name: 'root',
161+
fileType: 'folder',
162+
children: ['docs']
163+
},
164+
{
165+
id: 'docs',
166+
name: 'docs',
167+
fileType: 'folder',
168+
children: [],
169+
parentId: 'root'
170+
}
171+
]);
146172

147173
const action = {
148174
type: ActionTypes.DELETE_FILE,
149-
id: 'emptyFolder',
150-
parentId: rootId
175+
id: 'docs',
176+
parentId: 'root'
151177
};
152178

153179
const newState = files(state, action);
154180

155181
// Folder should be removed
156-
expect(newState.find((f) => f.id === 'emptyFolder')).toBeUndefined();
182+
expect(newState.find((f) => f.id === 'docs')).toBeUndefined();
157183

158184
// 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-
});
185+
const root = newState.find((f) => f.id === 'root');
186+
expect(root.children).not.toContain('docs');
250187
});
251188
});
252189
});

0 commit comments

Comments
 (0)