Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: super basic test suite support #4

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 130 additions & 0 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
name: Checks

on:
push:
branches:
- main
- renovate/**
pull_request:
branches:
- main

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
build:
if: github.event_name == 'push' || (github.event_name == 'pull_request' && !startsWith(github.head_ref, 'renovate/'))

name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
with:
check-latest: true
node-version: lts/*
- run: corepack enable
- run: yarn install
- run: yarn build --sourcemap
- uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4
with:
name: build-${{ github.sha }}
path: build

check:
if: github.event_name == 'push' || (github.event_name == 'pull_request' && !startsWith(github.head_ref, 'renovate/'))

name: Check
needs:
- build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
with:
check-latest: true
node-version: lts/*
- run: corepack enable
- run: yarn install
- uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
with:
name: build-${{ github.sha }}
path: build
- run: yarn check:spelling
- run: yarn check:types

quality:
if: github.event_name == 'push' || (github.event_name == 'pull_request' && !startsWith(github.head_ref, 'renovate/'))

name: Code Quality
needs:
- build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
with:
check-latest: true
node-version: lts/*
- run: corepack enable
- run: yarn install
- uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
with:
name: build-${{ github.sha }}
path: build
- run: yarn biome ci --error-on-warnings

test-node:
if: github.event_name == 'push' || (github.event_name == 'pull_request' && !startsWith(github.head_ref, 'renovate/'))

name: Test Build on Node.js v${{ matrix.node-version }}
needs:
- build
strategy:
fail-fast: false
matrix:
node-version: ['18.19', '18.x', '20.x', '22.x', '23.x']
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
with:
check-latest: true
node-version: ${{ matrix.node-version }}
- run: corepack enable
- run: yarn install
- uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
with:
name: build-${{ github.sha }}
path: build
- run: yarn test

test-os:
if: github.event_name == 'push' || (github.event_name == 'pull_request' && !startsWith(github.head_ref, 'renovate/'))

name: Test Build on ${{ matrix.os }}
needs:
- build
strategy:
fail-fast: false
matrix:
os: [macos-latest, windows-latest]
runs-on: ${{ matrix.os }}

steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
with:
check-latest: true
node-version: lts/*
- run: corepack enable
- run: yarn install
- uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
with:
name: build-${{ github.sha }}
path: build
- run: yarn test
46 changes: 46 additions & 0 deletions .mocharc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const console = require("node:console");
const process = require("node:process");

let showSpec = false;
const args = process.argv.slice(2);
/** @type {string[]} */
const positionals = [];
while (args.length > 0) {
const arg = args.shift() ?? "";
if (arg.startsWith("--")) {
if (arg.startsWith("--show-spec")) {
showSpec = true;
}
if (!arg.includes("=")) {
args.shift();
}
} else if (!arg.startsWith("-") && arg !== ".") {
positionals.push(arg);
}
}
let spec = [
// "**/*.test.js",
// "**/*.test.mjs",
// "**/*.test.cjs",
"**/*.test.ts",
];
if (positionals.length > 0) {
spec = positionals;
}

if (showSpec) {
console.log(`spec: ${JSON.stringify(spec)}`);
}

/**
* @type {{checkLeaks:boolean,ignore:string,loader?:string,"node-option"?:string[],recursive:boolean,spec:string[]}}
*/
const config = {
checkLeaks: true,
ignore: "node_modules/**/*",
"node-option": ["import=tsx", "import=source-map-support"],
recursive: true,
spec,
};

module.exports = config;
3 changes: 2 additions & 1 deletion biome.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"files": {
"ignore": [".cache/**", ".idea/**", ".yarn/**", "build/**", "coverage/**", "node_modules/**"]
"ignore": [".cache/**", ".idea/**", ".yarn/**", "build/**", "coverage/**", "node_modules/**"],
rickosborne marked this conversation as resolved.
Show resolved Hide resolved
"ignoreUnknown": true
},
"formatter": {
"enabled": true,
Expand Down
22 changes: 12 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,7 @@
"version": "1.0.0",
"private": true,
"description": "Run tstyche tests with a mocha-lookalike runner, allowing for IDE and mocha reporter integration",
"keywords": [
"typescript",
"test",
"runner",
"tstyche",
"mocha"
],
"keywords": ["typescript", "test", "runner", "tstyche", "mocha"],
rickosborne marked this conversation as resolved.
Show resolved Hide resolved
"homepage": "https://tstyche.org",
"bugs": {
"url": "https://github.com/tstyche/tstyche-as-mocha/issues"
Expand All @@ -34,21 +28,29 @@
"check:spelling": "cspell --config cspell.config.json --quiet",
"check:types": "tsc --noEmit --project tsconfig.json",
"clean": "rimraf build --preserve-root",
"format": "biome format --write",
"format": "biome format --write --verbose",
"lint": "biome lint --write",
"prepublish": "yarn clean && yarn build",
"test": "echo '🚧 TODO: tests 🚧'"
"prepublish": "yarn build",
"test": "yarn test:mocha && yarn test:tstyche && yarn test:tstyche-as-mocha",
"test:mocha": "mocha",
"test:tstyche": "tstyche type-test",
"test:tstyche-as-mocha": "tsx ./source/mocha.ts --reporter ../test/test-reporter.mjs"
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@types/chai": "5.0.1",
"@types/mocha": "10.0.10",
"@types/node": "22.10.5",
"chai": "5.1.2",
"cpr": "3.0.1",
"cspell": "8.17.1",
"mkdirp": "3.0.1",
"mocha": "11.0.1",
"mocha-reporter-gha": "1.1.1",
"rimraf": "6.0.1",
"source-map-support": "0.5.21",
"tstyche": "3.3.1",
"tsx": "4.19.2",
"typescript": "5.7.2"
},
"packageManager": "[email protected]",
Expand Down
98 changes: 98 additions & 0 deletions source/AsMochaReporter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import * as console from "node:console";
import * as process from "node:process";
import type * as M from "mocha";
import type { ResolvedConfig } from "tstyche/tstyche";
import type { ReporterEvent } from "tstyche/tstyche";
import { BaseReporter } from "tstyche/tstyche";

export type LoadReporterOptions = {
reporter?: string;
verbose?: boolean;
};

export type RunnerHandler = (runner: M.Runner) => void;

// noinspection JSUnusedGlobalSymbols
export default class AsMochaReporter extends BaseReporter {
public static readonly CANNOT_FIND_ERROR: string = "Cannot find: --reporter or -R";

/**
* Find the path to the mocha (not TSTyche) reporter. This may be a
* filesystem path, or it may be a package name. This is *not* the
* path to `tstyche-as-mocha` — it is the path to the IDE's reporter
* UI script.
* @remarks
* For now, TSTyche doesn't have a concept of "Reporter options", and
* we don't really want to import all possible mocha CLI params into
* TSTyche param space. Thus, we do a simple argv extraction.
* @privateRemarks
* Long term, maybe we add a TSTyche CLI param like `--reporter-config`
* or something.
* @example
* When configuring this in the IDE, this will look something like:
* ```
* --reporter /absolute/path/to/reporter.js
* --reporter=/absolute/path/to/reporter.js
* -R /absolute/path/to/reporter.js
* ```
*/
public static getReporterScriptPath(argv: Array<string> = process.argv.slice(2)): string {
const reporterArgs = argv
.map((arg, index) => {
if ((index > 0 && argv[index - 1] === "--reporter") || argv[index - 1] === "-R") {
return arg.trim();
}
if (arg.startsWith("--reporter=")) {
return arg.substring(11).trim();
}
return undefined;
})
.filter((a) => a != null && a !== "");
if (reporterArgs.length !== 1) {
throw new ReferenceError(AsMochaReporter.CANNOT_FIND_ERROR);
}
return reporterArgs.at(0) as string;
}

/**
* Load the specified reporter. Reporter scripts (or packages) should
* have a single default export, which is a function which accepts a
* mocha Runner as its only param/arg.
* @example
* For modern JS:
* ```javascript
* /\*\* \@param {import("mocha").Runner} runner \*\/
* const reporter = (runner) => { };
* export default reporter;
* ```
* Using `module.exports`:
* ```javascript
* /\*\* \@param {import("mocha").Runner} runner \*\/
* const reporter = (runner) => { };
* module.exports = reporter;
* ```
*/
public static async loadReporter(options: LoadReporterOptions = {}): Promise<RunnerHandler> {
const reporterName = options.reporter ?? AsMochaReporter.getReporterScriptPath();
const reporterModule = (await import(reporterName)).default as unknown;
if (options.verbose) {
console.log(`Reporter loaded: ${reporterName}`);
}
if (typeof reporterModule !== "function") {
throw new Error(`Imported reporter does not have a default export: ${reporterName}`);
}
return reporterModule as RunnerHandler;
}

constructor(config: ResolvedConfig) {
super(config);
AsMochaReporter.loadReporter().then(() => {
console.log("Reporter loaded");
});
}

public override on([event, _payload]: ReporterEvent): void {
console.log(event);
// TODO
}
}
1 change: 1 addition & 0 deletions source/AssertionError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type { Diagnostic } from "tstyche/tstyche";
* `expected` values.
*/
export class AssertionError extends Error {
public override readonly name = "AssertionError";
public override readonly stack: string;
public readonly actualType: string;
public readonly matcher: string;
Expand Down
24 changes: 13 additions & 11 deletions source/mocha.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
import * as path from "node:path";
import * as process from "node:process";
import * as util from "node:util";
// import type * as M from "mocha";
import { type CommandLineOptions, Config, Runner, Select } from "tstyche/tstyche";

let scriptDir: string;
if (typeof global.__dirname === "string") {
scriptDir = global.__dirname;
} else if (typeof global.require?.main?.path === "string") {
scriptDir = path.basename(global.require.main.path);
} else if (typeof import.meta?.dirname === "string") {
scriptDir = import.meta.dirname;
} else {
scriptDir = process.cwd();
}

const {
values: { config: configArg, grep, help, reporter: reporterArg },
positionals,
Expand Down Expand Up @@ -83,15 +93,10 @@ if (reporterArg == null) {
}

const run = async () => {
const reporterModule = (await import(reporterArg)).default as unknown;
if (typeof reporterModule !== "function") {
throw new Error(`Imported reporter does not have a default export: ${reporterArg}`);
}
// const fn = reporterModule as (runner: M.Runner) => void;
const configOptions = await Config.parseConfigFile(configArg);
const commandLineOptions: CommandLineOptions = {};
if (grep != null && grep !== "") {
// tstyche doesn't support patterns, which IJ tries to add by default.
// TSTyche doesn't support patterns, which IJ tries to add by default.
// Strip it down to support just a substring match. Won't handle multiple
// values!
commandLineOptions.only = grep.replace(/^\^|\$$/g, "");
Expand All @@ -109,12 +114,9 @@ const run = async () => {
...(pathMatch == null ? {} : { pathMatch }),
});
// Clear out the default Line and Summary reporters.
resolvedConfig.reporters = [];
resolvedConfig.reporters = [path.resolve(scriptDir, "AsMochaReporter.js")];
const testFiles = await Select.selectFiles(resolvedConfig);
const runner = new Runner(resolvedConfig);
// Add in the shim.
// const reporter = new MochaReporter(resolvedConfig, fn);
// runner.addReporter(reporter);
// Ready? Go!!!!
await runner.run(testFiles);
};
Expand Down
Loading
Loading