import {
  BaseReporter,
  colors,
  icons
} from "./chunk-L7YZLDZD.js";

// src/reporters/dot.ts
var DotReporter = class extends BaseReporter {
  /**
   * When a test ended
   */
  onTestEnd(payload) {
    let output = "";
    if (payload.isTodo) {
      output = colors.cyan(icons.info);
    } else if (payload.hasError) {
      output = colors.red(icons.cross);
    } else if (payload.isSkipped) {
      output = colors.yellow(icons.bullet);
    } else if (payload.isFailing) {
      output = colors.magenta(icons.squareSmallFilled);
    } else {
      output = colors.green(icons.tick);
    }
    process.stdout.write(`${output}`);
  }
  /**
   * When test runner ended
   */
  async end() {
    console.log("");
    await this.printSummary(this.runner.getSummary());
  }
};

// src/reporters/spec.ts
import ms from "ms";
import { relative } from "path";
var SpecReporter = class extends BaseReporter {
  /**
   * Tracking if the first event we get is for a test without any parent group
   * We need this to decide the display style for tests without groups.
   */
  #isFirstLoneTest = true;
  /**
   * Returns the icon for the test
   */
  #getTestIcon(payload) {
    if (payload.isTodo) {
      return colors.cyan(icons.info);
    }
    if (payload.hasError) {
      return colors.red(icons.cross);
    }
    if (payload.isSkipped) {
      return colors.yellow(icons.bullet);
    }
    if (payload.isFailing) {
      return colors.magenta(icons.squareSmallFilled);
    }
    return colors.green(icons.tick);
  }
  /**
   * Returns the test message
   */
  #getTestMessage(payload) {
    const message = payload.title.expanded;
    if (payload.isTodo) {
      return colors.blue(message);
    }
    if (payload.hasError) {
      return colors.red(message);
    }
    if (payload.isSkipped) {
      return colors.yellow(message);
    }
    if (payload.isFailing) {
      return colors.magenta(message);
    }
    return colors.grey(message);
  }
  /**
   * Returns the subtext message for the test
   */
  #getSubText(payload) {
    if (payload.isSkipped && payload.skipReason) {
      return colors.dim(`${icons.branch} ${colors.italic(payload.skipReason)}`);
    }
    if (!payload.isFailing) {
      return;
    }
    if (payload.hasError) {
      const message = payload.errors.find((error) => error.phase === "test")?.error.message ?? `Test marked with ".fails()" must finish with an error`;
      return colors.dim(`${icons.branch} ${colors.italic(message)}`);
    }
    if (payload.failReason) {
      return colors.dim(`${icons.branch} ${colors.italic(payload.failReason)}`);
    }
    const testErrorMessage = payload.errors.find((error) => error.phase === "test");
    if (testErrorMessage && testErrorMessage.error) {
      return colors.dim(`${icons.branch} ${colors.italic(testErrorMessage.error.message)}`);
    }
  }
  /**
   * Returns the filename relative from the current working dir
   */
  #getRelativeFilename(fileName) {
    return relative(process.cwd(), fileName);
  }
  /**
   * Prints the test details
   */
  #printTest(payload) {
    const icon = this.#getTestIcon(payload);
    const message = this.#getTestMessage(payload);
    const prefix = payload.isPinned ? colors.yellow("[PINNED] ") : "";
    const indentation = this.currentFileName || this.currentGroupName ? "  " : "";
    const duration = colors.dim(`(${ms(Number(payload.duration.toFixed(2)))})`);
    const retries = payload.retryAttempt && payload.retryAttempt > 1 ? colors.dim(`(x${payload.retryAttempt}) `) : "";
    let subText = this.#getSubText(payload);
    subText = subText ? `
${indentation}  ${subText}` : "";
    console.log(`${indentation}${icon} ${prefix}${retries}${message} ${duration}${subText}`);
  }
  /**
   * Prints the group name
   */
  #printGroup(payload) {
    const title = this.currentSuiteName !== "default" ? `${this.currentSuiteName} / ${payload.title}` : payload.title;
    const suffix = this.currentFileName ? colors.dim(` (${this.#getRelativeFilename(this.currentFileName)})`) : "";
    console.log(`
${title}${suffix}`);
  }
  onTestStart() {
    if (this.currentFileName && this.#isFirstLoneTest) {
      console.log(`
${colors.dim(this.#getRelativeFilename(this.currentFileName))}`);
    }
    this.#isFirstLoneTest = false;
  }
  onTestEnd(payload) {
    this.#printTest(payload);
  }
  onGroupStart(payload) {
    this.#isFirstLoneTest = false;
    this.#printGroup(payload);
  }
  onGroupEnd() {
    this.#isFirstLoneTest = true;
  }
  async end() {
    const summary = this.runner.getSummary();
    await this.printSummary(summary);
  }
};

// src/reporters/ndjson.ts
import { relative as relative2 } from "path";
import { serializeError } from "serialize-error";
var NdJSONReporter = class extends BaseReporter {
  /**
   * Returns the filename relative from the current working dir
   */
  #getRelativeFilename(fileName) {
    return relative2(process.cwd(), fileName);
  }
  /**
   * Serialize errors to JSON
   */
  #serializeErrors(errors) {
    return errors.map((error) => ({
      phase: error.phase,
      error: serializeError(error.error)
    }));
  }
  onTestEnd(payload) {
    console.log(
      JSON.stringify({
        event: "test:end",
        filePath: this.currentFileName,
        relativePath: this.currentFileName ? this.#getRelativeFilename(this.currentFileName) : void 0,
        title: payload.title,
        duration: payload.duration,
        failReason: payload.failReason,
        isFailing: payload.isFailing,
        skipReason: payload.skipReason,
        isSkipped: payload.isSkipped,
        isTodo: payload.isTodo,
        isPinned: payload.isPinned,
        retryAttempt: payload.retryAttempt,
        retries: payload.retries,
        errors: this.#serializeErrors(payload.errors)
      })
    );
  }
  onGroupStart(payload) {
    console.log(
      JSON.stringify({
        event: "group:start",
        title: payload.title
      })
    );
  }
  onGroupEnd(payload) {
    JSON.stringify({
      event: "group:end",
      title: payload.title,
      errors: this.#serializeErrors(payload.errors)
    });
  }
  onSuiteStart(payload) {
    console.log(
      JSON.stringify({
        event: "suite:start",
        ...payload
      })
    );
  }
  onSuiteEnd(payload) {
    console.log(
      JSON.stringify({
        event: "suite:end",
        name: payload.name,
        hasError: payload.hasError,
        errors: this.#serializeErrors(payload.errors)
      })
    );
  }
  async end() {
    const summary = this.runner.getSummary();
    console.log(
      JSON.stringify({
        aggregates: summary.aggregates,
        duration: summary.duration,
        failedTestsTitles: summary.failedTestsTitles,
        hasError: summary.hasError
      })
    );
  }
};

// src/reporters/github.ts
import slash from "slash";
import { relative as relative3 } from "path";
import { stripVTControlCharacters } from "util";
import { ErrorsPrinter } from "@japa/errors-printer";
var GithubReporter = class extends BaseReporter {
  /**
   * Performs string escape on annotation message as per
   * https://github.com/actions/toolkit/blob/f1d9b4b985e6f0f728b4b766db73498403fd5ca3/packages/core/src/command.ts#L80-L85
   */
  escapeMessage(value) {
    return value.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A");
  }
  /**
   * Performs string escape on annotation properties as per
   * https://github.com/actions/toolkit/blob/f1d9b4b985e6f0f728b4b766db73498403fd5ca3/packages/core/src/command.ts#L80-L85
   */
  escapeProperty(value) {
    return value.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A").replace(/:/g, "%3A").replace(/,/g, "%2C");
  }
  /**
   * Formats the message as per the Github annotations spec.
   * https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-error-message
   */
  formatMessage({
    command,
    properties,
    message
  }) {
    let result = `::${command}`;
    Object.entries(properties).forEach(([k, v], i) => {
      result += i === 0 ? " " : ",";
      result += `${k}=${this.escapeProperty(v)}`;
    });
    result += `::${this.escapeMessage(message)}`;
    return result;
  }
  /**
   * Prints Github annotation for a given error
   */
  async getErrorAnnotation(printer, error) {
    const parsedError = await printer.parseError(error.error);
    if (!("frames" in parsedError)) {
      return;
    }
    const mainFrame = parsedError.frames.find((frame) => frame.type === "app");
    if (!mainFrame) {
      return;
    }
    return this.formatMessage({
      command: "error",
      properties: {
        file: slash(relative3(process.cwd(), mainFrame.fileName)),
        title: error.title,
        line: String(mainFrame.lineNumber),
        column: String(mainFrame.columnNumber)
      },
      message: stripVTControlCharacters(parsedError.message)
    });
  }
  async end() {
    const summary = this.runner.getSummary();
    const errorsList = this.aggregateErrors(summary);
    const errorPrinter = new ErrorsPrinter(this.options);
    for (let error of errorsList) {
      const formatted = await this.getErrorAnnotation(errorPrinter, error);
      if (formatted) {
        console.log(`
${formatted}`);
      }
    }
  }
};

// src/reporters/main.ts
var spec = (options) => {
  return {
    name: "spec",
    handler: (...args) => new SpecReporter(options).boot(...args)
  };
};
var dot = (options) => {
  return {
    name: "dot",
    handler: (...args) => new DotReporter(options).boot(...args)
  };
};
var ndjson = (options) => {
  return {
    name: "ndjson",
    handler: (...args) => new NdJSONReporter(options).boot(...args)
  };
};
var github = (options) => {
  return {
    name: "github",
    handler: (...args) => new GithubReporter(options).boot(...args)
  };
};

export {
  spec,
  dot,
  ndjson,
  github
};
