0
# Cleanup Operations
1
2
Utility functions for maintaining snapshot files and removing orphaned snapshots that no longer have corresponding test files. Essential for keeping snapshot directories clean and avoiding stale snapshot accumulation.
3
4
## Capabilities
5
6
### cleanup Function
7
8
Removes orphaned snapshot files that no longer have corresponding test files, with support for ignore patterns and different update modes.
9
10
```typescript { .api }
11
/**
12
* Removes orphaned snapshot files that no longer have corresponding test files
13
* @param fileSystem - File system interface for file operations
14
* @param update - Snapshot update mode controlling cleanup behavior
15
* @param snapshotResolver - Resolver for converting between test and snapshot paths
16
* @param testPathIgnorePatterns - Optional patterns for ignoring test paths
17
* @returns Summary of cleanup results
18
*/
19
function cleanup(
20
fileSystem: FileSystem,
21
update: Config.SnapshotUpdateState,
22
snapshotResolver: SnapshotResolver,
23
testPathIgnorePatterns?: string[]
24
): {
25
filesRemoved: number;
26
filesRemovedList: Array<string>;
27
};
28
29
type Config.SnapshotUpdateState = 'all' | 'new' | 'none';
30
```
31
32
**Usage Examples:**
33
34
```typescript
35
import { cleanup, buildSnapshotResolver } from "jest-snapshot";
36
37
// File system implementation (typically jest-haste-map's HasteFS)
38
const fileSystem = {
39
exists: (path) => fs.existsSync(path),
40
matchFiles: (pattern) => glob.sync(pattern, { cwd: projectRoot })
41
};
42
43
// Build snapshot resolver
44
const snapshotResolver = await buildSnapshotResolver({
45
rootDir: '/project/root',
46
snapshotResolver: undefined
47
});
48
49
// Clean up orphaned snapshots
50
const result = cleanup(
51
fileSystem,
52
'all', // Update mode: 'all' enables cleanup
53
snapshotResolver,
54
['**/node_modules/**'] // Ignore patterns
55
);
56
57
console.log(`Removed ${result.filesRemoved} orphaned snapshot files`);
58
console.log('Files removed:', result.filesRemovedList);
59
60
// Example output:
61
// Removed 3 orphaned snapshot files
62
// Files removed: [
63
// '/project/src/__snapshots__/DeletedComponent.test.js.snap',
64
// '/project/src/__snapshots__/RenamedFile.spec.js.snap',
65
// '/project/tests/__snapshots__/old-test.js.snap'
66
// ]
67
```
68
69
### Cleanup Process
70
71
The cleanup function follows a systematic process:
72
73
1. **Find Snapshot Files**: Uses the file system to find all files matching the snapshot pattern (`.snap` files)
74
2. **Resolve Test Paths**: For each snapshot file, uses the snapshot resolver to determine the corresponding test file path
75
3. **Check Test File Existence**: Verifies whether the test file still exists
76
4. **Apply Ignore Patterns**: Skips snapshots whose test paths match ignore patterns
77
5. **Remove Orphaned Files**: In 'all' update mode, removes snapshot files without corresponding tests
78
79
```typescript
80
// Internal cleanup logic (simplified)
81
const pattern = `\\.${EXTENSION}$`; // '\.snap$'
82
const snapshotFiles = fileSystem.matchFiles(pattern);
83
84
const orphanedFiles = snapshotFiles.filter(snapshotFile => {
85
// Get corresponding test path
86
const testPath = snapshotResolver.resolveTestPath(snapshotFile);
87
88
// Check ignore patterns
89
if (testIgnorePatternsRegex && testIgnorePatternsRegex.test(testPath)) {
90
return false; // Skip ignored test paths
91
}
92
93
// Check if test file exists
94
return !fileSystem.exists(testPath);
95
});
96
97
// Remove files if in 'all' update mode
98
if (update === 'all') {
99
orphanedFiles.forEach(file => fs.unlinkSync(file));
100
}
101
```
102
103
### Update Mode Behavior
104
105
The cleanup behavior depends on the snapshot update mode:
106
107
**`'all'` Mode**: Actively removes orphaned snapshot files
108
```typescript
109
const result = cleanup(fileSystem, 'all', resolver);
110
// Physically deletes orphaned .snap files
111
// result.filesRemoved > 0 if files were removed
112
```
113
114
**`'new'` or `'none'` Mode**: Only reports orphaned files without removing them
115
```typescript
116
const result = cleanup(fileSystem, 'none', resolver);
117
// Reports orphaned files but doesn't delete them
118
// result.filesRemoved = 0, but filesRemovedList shows what would be removed
119
```
120
121
### Ignore Patterns
122
123
Use ignore patterns to exclude certain test paths from cleanup consideration:
124
125
```typescript
126
// Common ignore patterns
127
const ignorePatterns = [
128
'**/node_modules/**', // Ignore dependencies
129
'**/dist/**', // Ignore build output
130
'**/*.d.ts', // Ignore TypeScript declarations
131
'**/fixtures/**', // Ignore test fixtures
132
'**/coverage/**' // Ignore coverage reports
133
];
134
135
const result = cleanup(
136
fileSystem,
137
'all',
138
resolver,
139
ignorePatterns
140
);
141
142
// Snapshots for tests matching these patterns won't be removed
143
// even if the test files appear to be missing
144
```
145
146
### FileSystem Interface
147
148
The cleanup function expects a file system interface that provides file existence checking and pattern matching:
149
150
```typescript { .api }
151
interface FileSystem {
152
/**
153
* Checks if a file exists at the given path
154
* @param path - File path to check
155
* @returns True if file exists, false otherwise
156
*/
157
exists(path: string): boolean;
158
159
/**
160
* Finds files matching a pattern
161
* @param pattern - RegExp or string pattern to match files
162
* @returns Array of matching file paths
163
*/
164
matchFiles(pattern: RegExp | string): Array<string>;
165
}
166
```
167
168
**Common FileSystem Implementations:**
169
170
```typescript
171
// Using Node.js fs and glob
172
const nodeFileSystem = {
173
exists: (path) => require('fs').existsSync(path),
174
matchFiles: (pattern) => require('glob').sync(pattern)
175
};
176
177
// Using jest-haste-map (typical in Jest environments)
178
const hasteFileSystem = {
179
exists: (path) => hasteFs.exists(path),
180
matchFiles: (pattern) => hasteFs.matchFiles(pattern)
181
};
182
183
// Custom implementation with caching
184
const cachedFileSystem = {
185
exists: (path) => {
186
if (!existsCache.has(path)) {
187
existsCache.set(path, fs.existsSync(path));
188
}
189
return existsCache.get(path);
190
},
191
matchFiles: (pattern) => glob.sync(pattern, { cache: globCache })
192
};
193
```
194
195
### Integration with Test Runners
196
197
Cleanup is typically integrated into test runner workflows:
198
199
```typescript
200
// Jest integration example
201
module.exports = {
202
// Jest configuration
203
setupFilesAfterEnv: ['<rootDir>/cleanup-snapshots.js']
204
};
205
206
// cleanup-snapshots.js
207
afterAll(async () => {
208
if (process.env.UPDATE_SNAPSHOTS === 'true') {
209
const result = cleanup(fileSystem, 'all', resolver);
210
console.log(`Cleaned up ${result.filesRemoved} orphaned snapshots`);
211
}
212
});
213
214
// CI/CD integration
215
if (process.env.CI && process.env.UPDATE_SNAPSHOTS) {
216
const result = cleanup(fileSystem, 'all', resolver);
217
218
if (result.filesRemoved > 0) {
219
console.log('Orphaned snapshots removed:', result.filesRemovedList);
220
process.exit(1); // Fail CI to highlight the cleanup
221
}
222
}
223
```
224
225
### Error Handling
226
227
Cleanup operations can encounter various error conditions:
228
229
```typescript
230
// File system errors
231
try {
232
const result = cleanup(fileSystem, 'all', resolver);
233
} catch (error) {
234
if (error.code === 'ENOENT') {
235
console.log('Snapshot directory not found');
236
} else if (error.code === 'EPERM') {
237
console.log('Permission denied removing snapshot file');
238
}
239
}
240
241
// Invalid resolver
242
const result = cleanup(fileSystem, 'all', null);
243
// Error: Invalid snapshot resolver
244
245
// Pattern matching errors
246
const faultyFileSystem = {
247
exists: () => true,
248
matchFiles: () => { throw new Error('Pattern error'); }
249
};
250
// Error: Failed to match snapshot files
251
```
252
253
### Cleanup Reporting
254
255
Detailed reporting helps understand what was cleaned up:
256
257
```typescript
258
const result = cleanup(fileSystem, 'all', resolver, ignorePatterns);
259
260
console.log(`Cleanup Summary:
261
Files Removed: ${result.filesRemoved}
262
Ignore Patterns: ${ignorePatterns?.length || 0}
263
264
Removed Files:`);
265
266
result.filesRemovedList.forEach(file => {
267
const testPath = resolver.resolveTestPath(file);
268
console.log(` ${file} (test: ${testPath})`);
269
});
270
```
271
272
## Types
273
274
```typescript { .api }
275
interface CleanupResult {
276
filesRemoved: number; // Count of files actually removed
277
filesRemovedList: Array<string>; // Paths of removed files
278
}
279
280
type SnapshotUpdateState = 'all' | 'new' | 'none';
281
282
interface FileSystem {
283
exists(path: string): boolean;
284
matchFiles(pattern: RegExp | string): Array<string>;
285
}
286
287
interface SnapshotResolver {
288
resolveTestPath(snapshotPath: string, snapshotExtension?: string): string;
289
resolveSnapshotPath(testPath: string, snapshotExtension?: string): string;
290
testPathForConsistencyCheck: string;
291
}
292
```