Hone logo
Hone
Problems

Jest Dependency Graph Generator

Creating a dependency graph of your Jest tests can be incredibly valuable for understanding test relationships, identifying potential test order issues, and optimizing test execution. This challenge asks you to build a utility that analyzes your Jest configuration and test files to generate a visual dependency graph representing how your tests depend on each other. This is particularly useful for large projects where test dependencies can become complex and difficult to manage.

Problem Description

You need to create a TypeScript function generateDependencyGraph that takes a Jest configuration object and an array of test file paths as input and returns a dependency graph represented as an adjacency list. The adjacency list should be a dictionary (object) where keys are test file paths (strings) and values are arrays of test file paths that the key test file depends on. Dependencies are determined by import statements within the test files.

Key Requirements:

  • Input:
    • jestConfig: A Jest configuration object (e.g., the result of jest.config.js). You can assume it contains a moduleNameMapper property, which maps module paths to mock paths. This is used to resolve relative imports.
    • testFiles: An array of strings, where each string is the absolute path to a Jest test file.
  • Output: An adjacency list (object) representing the dependency graph.
  • Dependency Detection: The function should parse each test file and identify dependencies based on import statements. It should resolve relative imports using the moduleNameMapper from the Jest configuration.
  • Error Handling: If a test file cannot be read or parsed, the function should log an error and continue processing other files. Do not throw an error.
  • Circular Dependencies: The function should handle circular dependencies gracefully. The graph should accurately represent the dependencies, even if they form cycles.
  • No External Parsing Libraries: You are not allowed to use external parsing libraries like esprima or babel-parser. You must use built-in JavaScript string manipulation techniques (e.g., indexOf, substring, regular expressions) to parse the test files.

Expected Behavior:

  • The function should return an empty adjacency list if testFiles is empty.
  • The function should correctly identify dependencies between test files.
  • The function should handle different import syntaxes (e.g., import x from 'y', import { x } from 'y').
  • The function should resolve relative imports correctly using the moduleNameMapper.
  • The function should not modify the original jestConfig object.

Edge Cases to Consider:

  • Test files with no imports.
  • Test files with invalid syntax.
  • Relative imports that are not defined in moduleNameMapper.
  • Circular dependencies.
  • Large test files.

Examples

Example 1:

Input:
jestConfig = {
  moduleNameMapper: {
    '^@/components/(.*)$': '<rootDir>/src/components/$1',
  }
}
testFiles = [
  'src/components/Button.test.ts',
  'src/components/Input.test.ts',
  'src/utils/helpers.test.ts'
]

src/components/Button.test.ts:
```typescript
import { Input } from '@/components/Input';
import { formatName } from '@/utils/helpers';

src/components/Input.test.ts:
```typescript
import { formatName } from '@/utils/helpers';

src/utils/helpers.test.ts:
```typescript
// No imports

Output:

{
  "src/components/Button.test.ts": [
    "src/components/Input.test.ts",
    "src/utils/helpers.test.ts"
  ],
  "src/components/Input.test.ts": [
    "src/utils/helpers.test.ts"
  ],
  "src/utils/helpers.test.ts": []
}

Explanation: Button.test.ts depends on Input.test.ts and helpers.test.ts. Input.test.ts depends on helpers.test.ts. helpers.test.ts has no dependencies.

Example 2:

Input:
jestConfig = {}
testFiles = ['src/test/example.test.ts']

src/test/example.test.ts:
```typescript
import './utils';

Output:

{
  "src/test/example.test.ts": [
    "src/test/utils.ts"
  ]
}

Explanation: example.test.ts depends on utils.ts. Since moduleNameMapper is empty, the relative import is resolved literally.

Example 3: (Edge Case - Invalid File)

Input:
jestConfig = {}
testFiles = ['src/test/invalid.test.ts']

src/test/invalid.test.ts:
```typescript
import "not a valid module";

Output:

{}

Explanation: The function should log an error about the invalid file and return an empty graph. The error should not crash the program.

Constraints

  • Time Complexity: The function should complete within a reasonable time (e.g., under 1 second) for a project with 100 test files.
  • Memory Complexity: The function should not consume excessive memory.
  • Input Size: The maximum size of a test file is 10KB.
  • Jest Configuration: The moduleNameMapper property in the jestConfig is guaranteed to be an object.
  • File System: You can assume that the test files exist and are readable.

Notes

  • Focus on accurately identifying dependencies using string manipulation. Regular expressions will be helpful.
  • Consider using a recursive approach to handle circular dependencies.
  • Error handling is important. Log errors to the console but do not throw exceptions.
  • The moduleNameMapper is used to resolve relative imports. Make sure to handle cases where a relative import is not defined in the moduleNameMapper.
  • This is a challenging problem that requires careful attention to detail. Break down the problem into smaller, manageable steps. Start by parsing a single test file and identifying its dependencies. Then, extend your solution to handle multiple files and circular dependencies.
Loading editor...
typescript