ui/json_formatter.js

import { createRequire } from 'node:module';
import { Formatter } from './formatter.js';

const require = createRequire(import.meta.url);
const { name: toolName, version: toolVersion } = require('../../package.json');

/**
 * I accumulate test results as the run progresses and emit a single structured JSON document
 * when the run finishes. Status tokens are language-neutral; failure messages are resolved in
 * the configured language.
 */
export class JsonFormatter extends Formatter {
  #suites;
  #currentSuite;
  #startTime;

  constructor(console, i18n) {
    super(console, i18n);
    this.#suites = [];
    this.#currentSuite = undefined;
    this.#startTime = 0;
  }

  startTimer() {
    this.#startTime = Date.now();
  }

  displaySuiteStart(suite) {
    this.#currentSuite = { name: suite.name(), file: suite.locationPath(), tests: [] };
    this.#suites.push(this.#currentSuite);
  }

  displaySuccessResult(test) {
    this.#record(test, 'passed');
  }

  displaySkippedResult(test) {
    this.#record(test, 'skipped');
  }

  displayPendingResult(test) {
    this.#record(test, 'pending');
  }

  displayFailureResult(test, failType) {
    const status = failType === 'error' ? 'errored' : 'failed';
    this.#record(test, status, {
      message: this.internationalized(test.result().failureMessage()),
      location: this.internationalized(test.result().location()),
    });
  }

  displayRunnerEnd(runner) {
    const report = {
      tool: toolName,
      version: toolVersion,
      summary: {
        total: runner.totalCount(),
        passed: runner.successCount(),
        failed: runner.failuresCount(),
        errored: runner.errorsCount(),
        pending: runner.pendingCount(),
        skipped: runner.skippedCount(),
        durationMs: Date.now() - this.#startTime,
      },
      suites: this.#suites,
    };
    this.print(JSON.stringify(report));
  }

  #record(test, status, failure = undefined) {
    const entry = { name: test.name(), status };
    if (failure !== undefined) {
      entry.failure = failure;
    }
    this.#currentSuite.tests.push(entry);
  }
}