Hone logo
Hone
Problems

Jest Coverage Injection

This challenge focuses on understanding and manipulating Jest's coverage reporting mechanism. You will learn how to programmatically inject coverage data into Jest, allowing for more advanced testing scenarios or custom reporting tools.

Problem Description

Jest's built-in coverage reporting uses Istanbul (v8) under the hood to collect and analyze code coverage. Your task is to simulate this coverage injection process. Specifically, you need to create a Jest transformer that, when processing a TypeScript file, injects a mock coverage object for that file into a predefined global variable. This mock coverage object should mimic the structure expected by Jest's coverage reporter.

Key Requirements:

  1. Create a Jest Transformer: Implement a custom Jest transformer for TypeScript files (.ts). This transformer will be responsible for modifying the source code.
  2. Inject Mock Coverage Data: For each processed TypeScript file, inject a JavaScript code snippet that assigns a mock coverage object to a global variable (e.g., __JEST_COVERAGE__).
  3. Mock Coverage Object Structure: The injected mock coverage object should resemble the structure of a coverage report generated by Istanbul, specifically including:
    • path: The absolute path to the file.
    • hash: A hash of the file's content (you can use a simple placeholder or a basic hash function for this challenge).
    • data: An object containing coverage details for the file, including:
      • lines: An object with count (total lines) and hit (covered lines).
      • functions: An object with count (total functions) and hit (covered functions).
      • branches: An object with count (total branches) and hit (covered branches).
      • s: A map of statement coverage (e.g., { "0": 1, "1": 0 }, where keys are statement indices and values are hit status).
      • f: A map of function coverage (e.g., { "0": 1 }).
      • b: A map of branch coverage (e.g., { "0": [1, 0] }).
  4. Jest Configuration: Configure Jest to use your custom transformer and to output coverage reports.
  5. Verification: Write a test that verifies your injection mechanism works as expected by checking the content of the global __JEST_COVERAGE__ variable after your transformer has run.

Expected Behavior:

When Jest runs tests in a project configured with your transformer, each transpiled TypeScript file will have a global variable __JEST_COVERAGE__ populated with mock coverage data before the actual test code executes for that file.

Examples

Example 1:

Let's consider a simple TypeScript file:

src/example.ts

function greet(name: string): string {
  return `Hello, ${name}!`;
}

console.log(greet("World"));

Input to Transformer: The raw content of src/example.ts.

Output from Transformer (Conceptual): The transformer will wrap the original code and inject the coverage logic.

// Injected coverage data
global.__JEST_COVERAGE__ = {
  path: "/path/to/your/project/src/example.ts", // Absolute path
  hash: "some_hash_value", // Placeholder hash
  data: {
    lines: { count: 4, hit: 3 }, // Example counts
    functions: { count: 1, hit: 1 },
    branches: { count: 0, hit: 0 },
    s: { "0": 1, "1": 1, "2": 1, "3": 1 }, // Example statement coverage
    f: { "0": 1 }, // Example function coverage
    b: {} // Example branch coverage
  }
};

// Original transpiled code (simplified)
function greet(name) {
  return "Hello, " + name + "!";
}
console.log(greet("World"));

Explanation: The transformer intercepts src/example.ts, generates mock coverage data, and prepends it as executable JavaScript code. The path should be the absolute path of the file. The hash can be a placeholder. The data object contains simplified coverage metrics.

Example 2:

Consider a more complex scenario with conditional logic:

src/conditional.ts

function checkValue(value: number): string {
  if (value > 10) {
    return "High";
  } else {
    return "Low";
  }
}

console.log(checkValue(5));
console.log(checkValue(15));

Input to Transformer: The raw content of src/conditional.ts.

Output from Transformer (Conceptual):

// Injected coverage data
global.__JEST_COVERAGE__ = {
  path: "/path/to/your/project/src/conditional.ts",
  hash: "another_hash_value",
  data: {
    lines: { count: 6, hit: 6 }, // All lines executed
    functions: { count: 1, hit: 1 },
    branches: { count: 2, hit: 2 }, // Two branches in the if/else
    s: { "0": 1, "1": 1, "2": 1, "3": 1, "4": 1, "5": 1 },
    f: { "0": 1 },
    b: { "0": [1, 1] } // Example: Branch 0 has 2 outcomes, both hit (conceptually, for test purposes)
  }
};

// Original transpiled code (simplified)
function checkValue(value) {
  if (value > 10) {
    return "High";
  } else {
    return "Low";
  }
}
console.log(checkValue(5));
console.log(checkValue(15));

Explanation: The mock coverage reflects the presence of branches in the if/else statement and the total number of lines.

Constraints

  • The custom transformer must be compatible with Jest's transformer API.
  • The injected JavaScript code should not alter the execution of the original TypeScript code.
  • The mock coverage data should be injected into a global variable named __JEST_COVERAGE__.
  • The solution should be written in TypeScript.
  • Your solution should work with Jest v27+ and TypeScript v4+.
  • You are not expected to implement a full, accurate Istanbul coverage calculation. Focus on the injection mechanism and the structure of the coverage object. You can use placeholder values for hash and calculate simple counts for lines, functions, and branches. The s, f, and b maps can be populated with dummy data representing the presence of statements/functions/branches.

Notes

  • You'll need to set up a Jest project with TypeScript support. A jest.config.js or jest.config.ts file will be crucial.
  • Consider how Jest's ts-jest preset or a custom transformer for babel-jest would interact with your custom transformer. For this challenge, you can assume you are creating a transformer that replaces or complements ts-jest. A simple approach is to create a transformer that first transpiles TS to JS and then injects the coverage.
  • The path property of the coverage object should be an absolute path. You can use Node.js's path module for this.
  • You can simplify the generation of s, f, and b maps. For instance, you can simply assign 1 to all encountered statements and functions. For branches, if you detect an if or else, you can assume a branch count.
  • To verify your injection, you'll likely need a test file that imports the transformed file and then asserts the content of global.__JEST_COVERAGE__. Jest's coverage reporting itself won't directly show this injected data, so you're essentially testing your transformer's side effect.
Loading editor...
typescript