CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-jest-snapshot

Jest snapshot testing utilities that enable capturing component output, API responses, or any serializable values as snapshots for regression testing and change detection

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

cleanup.mddocs/

Cleanup Operations

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.

Capabilities

cleanup Function

Removes orphaned snapshot files that no longer have corresponding test files, with support for ignore patterns and different update modes.

/**
 * Removes orphaned snapshot files that no longer have corresponding test files
 * @param fileSystem - File system interface for file operations
 * @param update - Snapshot update mode controlling cleanup behavior
 * @param snapshotResolver - Resolver for converting between test and snapshot paths
 * @param testPathIgnorePatterns - Optional patterns for ignoring test paths
 * @returns Summary of cleanup results
 */
function cleanup(
  fileSystem: FileSystem,
  update: Config.SnapshotUpdateState,
  snapshotResolver: SnapshotResolver,
  testPathIgnorePatterns?: string[]
): {
  filesRemoved: number;
  filesRemovedList: Array<string>;
};

type Config.SnapshotUpdateState = 'all' | 'new' | 'none';

Usage Examples:

import { cleanup, buildSnapshotResolver } from "jest-snapshot";

// File system implementation (typically jest-haste-map's HasteFS)
const fileSystem = {
  exists: (path) => fs.existsSync(path),
  matchFiles: (pattern) => glob.sync(pattern, { cwd: projectRoot })
};

// Build snapshot resolver
const snapshotResolver = await buildSnapshotResolver({
  rootDir: '/project/root',
  snapshotResolver: undefined
});

// Clean up orphaned snapshots
const result = cleanup(
  fileSystem,
  'all',                    // Update mode: 'all' enables cleanup
  snapshotResolver,
  ['**/node_modules/**']    // Ignore patterns
);

console.log(`Removed ${result.filesRemoved} orphaned snapshot files`);
console.log('Files removed:', result.filesRemovedList);

// Example output:
// Removed 3 orphaned snapshot files
// Files removed: [
//   '/project/src/__snapshots__/DeletedComponent.test.js.snap',
//   '/project/src/__snapshots__/RenamedFile.spec.js.snap',
//   '/project/tests/__snapshots__/old-test.js.snap'
// ]

Cleanup Process

The cleanup function follows a systematic process:

  1. Find Snapshot Files: Uses the file system to find all files matching the snapshot pattern (.snap files)
  2. Resolve Test Paths: For each snapshot file, uses the snapshot resolver to determine the corresponding test file path
  3. Check Test File Existence: Verifies whether the test file still exists
  4. Apply Ignore Patterns: Skips snapshots whose test paths match ignore patterns
  5. Remove Orphaned Files: In 'all' update mode, removes snapshot files without corresponding tests
// Internal cleanup logic (simplified)
const pattern = `\\.${EXTENSION}$`;  // '\.snap$'
const snapshotFiles = fileSystem.matchFiles(pattern);

const orphanedFiles = snapshotFiles.filter(snapshotFile => {
  // Get corresponding test path
  const testPath = snapshotResolver.resolveTestPath(snapshotFile);
  
  // Check ignore patterns
  if (testIgnorePatternsRegex && testIgnorePatternsRegex.test(testPath)) {
    return false; // Skip ignored test paths
  }
  
  // Check if test file exists
  return !fileSystem.exists(testPath);
});

// Remove files if in 'all' update mode
if (update === 'all') {
  orphanedFiles.forEach(file => fs.unlinkSync(file));
}

Update Mode Behavior

The cleanup behavior depends on the snapshot update mode:

'all' Mode: Actively removes orphaned snapshot files

const result = cleanup(fileSystem, 'all', resolver);
// Physically deletes orphaned .snap files
// result.filesRemoved > 0 if files were removed

'new' or 'none' Mode: Only reports orphaned files without removing them

const result = cleanup(fileSystem, 'none', resolver);
// Reports orphaned files but doesn't delete them
// result.filesRemoved = 0, but filesRemovedList shows what would be removed

Ignore Patterns

Use ignore patterns to exclude certain test paths from cleanup consideration:

// Common ignore patterns
const ignorePatterns = [
  '**/node_modules/**',     // Ignore dependencies
  '**/dist/**',             // Ignore build output
  '**/*.d.ts',              // Ignore TypeScript declarations 
  '**/fixtures/**',         // Ignore test fixtures
  '**/coverage/**'          // Ignore coverage reports
];

const result = cleanup(
  fileSystem,
  'all',
  resolver,
  ignorePatterns
);

// Snapshots for tests matching these patterns won't be removed
// even if the test files appear to be missing

FileSystem Interface

The cleanup function expects a file system interface that provides file existence checking and pattern matching:

interface FileSystem {
  /**
   * Checks if a file exists at the given path
   * @param path - File path to check
   * @returns True if file exists, false otherwise
   */
  exists(path: string): boolean;
  
  /**
   * Finds files matching a pattern
   * @param pattern - RegExp or string pattern to match files
   * @returns Array of matching file paths
   */
  matchFiles(pattern: RegExp | string): Array<string>;
}

Common FileSystem Implementations:

// Using Node.js fs and glob
const nodeFileSystem = {
  exists: (path) => require('fs').existsSync(path),
  matchFiles: (pattern) => require('glob').sync(pattern)
};

// Using jest-haste-map (typical in Jest environments)
const hasteFileSystem = {
  exists: (path) => hasteFs.exists(path),
  matchFiles: (pattern) => hasteFs.matchFiles(pattern)
};

// Custom implementation with caching
const cachedFileSystem = {
  exists: (path) => {
    if (!existsCache.has(path)) {
      existsCache.set(path, fs.existsSync(path));
    }
    return existsCache.get(path);
  },
  matchFiles: (pattern) => glob.sync(pattern, { cache: globCache })
};

Integration with Test Runners

Cleanup is typically integrated into test runner workflows:

// Jest integration example
module.exports = {
  // Jest configuration
  setupFilesAfterEnv: ['<rootDir>/cleanup-snapshots.js']
};

// cleanup-snapshots.js
afterAll(async () => {
  if (process.env.UPDATE_SNAPSHOTS === 'true') {
    const result = cleanup(fileSystem, 'all', resolver);
    console.log(`Cleaned up ${result.filesRemoved} orphaned snapshots`);
  }
});

// CI/CD integration
if (process.env.CI && process.env.UPDATE_SNAPSHOTS) {
  const result = cleanup(fileSystem, 'all', resolver);
  
  if (result.filesRemoved > 0) {
    console.log('Orphaned snapshots removed:', result.filesRemovedList);
    process.exit(1); // Fail CI to highlight the cleanup
  }
}

Error Handling

Cleanup operations can encounter various error conditions:

// File system errors
try {
  const result = cleanup(fileSystem, 'all', resolver);
} catch (error) {
  if (error.code === 'ENOENT') {
    console.log('Snapshot directory not found');
  } else if (error.code === 'EPERM') {
    console.log('Permission denied removing snapshot file');
  }
}

// Invalid resolver
const result = cleanup(fileSystem, 'all', null);
// Error: Invalid snapshot resolver

// Pattern matching errors
const faultyFileSystem = {
  exists: () => true,
  matchFiles: () => { throw new Error('Pattern error'); }
};
// Error: Failed to match snapshot files

Cleanup Reporting

Detailed reporting helps understand what was cleaned up:

const result = cleanup(fileSystem, 'all', resolver, ignorePatterns);

console.log(`Cleanup Summary:
  Files Removed: ${result.filesRemoved}
  Ignore Patterns: ${ignorePatterns?.length || 0}
  
  Removed Files:`);

result.filesRemovedList.forEach(file => {
  const testPath = resolver.resolveTestPath(file);
  console.log(`  ${file} (test: ${testPath})`);
});

Types

interface CleanupResult {
  filesRemoved: number;           // Count of files actually removed
  filesRemovedList: Array<string>; // Paths of removed files
}

type SnapshotUpdateState = 'all' | 'new' | 'none';

interface FileSystem {
  exists(path: string): boolean;
  matchFiles(pattern: RegExp | string): Array<string>;
}

interface SnapshotResolver {
  resolveTestPath(snapshotPath: string, snapshotExtension?: string): string;
  resolveSnapshotPath(testPath: string, snapshotExtension?: string): string;
  testPathForConsistencyCheck: string;
}

docs

cleanup.md

error-snapshots.md

index.md

path-resolution.md

serialization.md

snapshot-matchers.md

state-management.md

tile.json