From b02a30a80cb72349b35ce3c27eaee64f9b3a2c33 Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Sun, 22 Dec 2024 17:34:41 +0100 Subject: [PATCH] Add some typescript tests (#173) So far these more or less duplicate a subset of the tests in `test_typedoc_analysis` so they don't provide that much added value because they are still basically integration tests with respect to the typescript logic. But this is a first step and will allow finer grained typescript tests in the future. --- .github/workflows/ci.yml | 6 ++ noxfile.py | 33 ++++++++++- sphinx_js/js/importHooks.mjs | 1 + tests/test.ts | 103 +++++++++++++++++++++++++++++++++++ 4 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 tests/test.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eaf64861..0e28b571 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,6 +31,7 @@ jobs: run: | python -m pip install --upgrade pip pip install nox + - name: Test run: nox -s tests-${{ matrix.python-version }} @@ -60,6 +61,11 @@ jobs: steps: - uses: actions/checkout@v3.1.0 + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: 22 + - name: Set up Python uses: actions/setup-python@v4.3.0 with: diff --git a/noxfile.py b/noxfile.py index 51900577..2abf80a6 100644 --- a/noxfile.py +++ b/noxfile.py @@ -1,8 +1,11 @@ from pathlib import Path +from textwrap import dedent import nox from nox.sessions import Session +PROJECT_ROOT = Path(__file__).parent + @nox.session(python=["3.10", "3.11", "3.12", "3.13"]) def tests(session: Session) -> None: @@ -25,15 +28,41 @@ def tests(session: Session) -> None: @nox.session(python=["3.12"]) @nox.parametrize("typedoc", ["0.25", "0.26"]) def test_typedoc(session: Session, typedoc: str) -> None: + # Install python dependencies session.install("-r", "requirements_dev.txt") venvroot = Path(session.bin).parent - (venvroot / "node_modules").mkdir() + node_modules = (venvroot / "node_modules").resolve() + node_modules.mkdir() with session.chdir(venvroot): + # Install node dependencies session.run( - "npm", "i", "--no-save", "jsdoc@4.0.0", f"typedoc@{typedoc}", external=True + "npm", + "i", + "--no-save", + "tsx", + "jsdoc@4.0.0", + f"typedoc@{typedoc}", + external=True, ) session.run("npx", "tsc", "--version", external=True) session.run("npx", "typedoc", "--version", external=True) + # Run typescript tests + test_file = (PROJECT_ROOT / "tests/test.ts").resolve() + register_import_hook = PROJECT_ROOT / "sphinx_js/js/registerImportHook.mjs" + ts_tests = Path(venvroot / "ts_tests") + # Write script to a file so that it is easy to rerun without reinstalling dependencies. + ts_tests.write_text( + dedent( + f"""\ + #!/bin/sh + TYPEDOC_NODE_MODULES={venvroot} node --import {register_import_hook} --import {node_modules/"tsx/dist/loader.mjs"} --test {test_file} + """ + ) + ) + ts_tests.chmod(0o777) + session.run(ts_tests, external=True) + + # Run Python tests session.run("pytest", "--junitxml=test-results.xml", "-k", "not js") diff --git a/sphinx_js/js/importHooks.mjs b/sphinx_js/js/importHooks.mjs index 3df0556a..68f59caa 100644 --- a/sphinx_js/js/importHooks.mjs +++ b/sphinx_js/js/importHooks.mjs @@ -18,6 +18,7 @@ export async function resolve(specifier, context, nextResolve) { for (const parentURL of [origURL, fallbackURL]) { context.parentURL = parentURL; const res = await tryResolve(specifier, context, nextResolve); + context.parentURL = origURL; if (res) { return res; } diff --git a/tests/test.ts b/tests/test.ts new file mode 100644 index 00000000..17c87769 --- /dev/null +++ b/tests/test.ts @@ -0,0 +1,103 @@ +import assert from "node:assert"; +import { test, suite, before } from "node:test"; +import { run } from "../sphinx_js/js/cli"; +import { TopLevelIR, Type } from "../sphinx_js/js/ir"; +import { Application } from "typedoc"; + +function joinType(t: Type): string { + return t.map((x) => (typeof x === "string" ? x : x.name)).join(""); +} + +function resolveFile(path: string): string { + return import.meta.dirname + "/" + path; +} + +suite("types.ts", async () => { + let app: Application; + let results: TopLevelIR[]; + let map: Map; + before(async () => { + [app, results] = await run([ + "--sphinxJsConfig", + resolveFile("sphinxJsConfig.ts"), + "--entryPointStrategy", + "expand", + "--tsconfig", + resolveFile("test_typedoc_analysis/source/tsconfig.json"), + "--basePath", + resolveFile("test_typedoc_analysis/source"), + resolveFile("test_typedoc_analysis/source/types.ts"), + ]); + map = new Map(results.map((res) => [res.name, res])); + }); + function getObject(name: string): TopLevelIR { + const obj = map.get(name); + assert(obj); + return obj!; + } + suite("basic", async () => { + for (const [obj_name, type_name] of [ + ["bool", "boolean"], + ["num", "number"], + ["str", "string"], + ["array", "number[]"], + ["genericArray", "number[]"], + ["tuple", "[string, number]"], + ["color", "Color"], + ["unk", "unknown"], + ["whatever", "any"], + ["voidy", "void"], + ["undef", "undefined"], + ["nully", "null"], + ["nev", "never"], + ["obj", "object"], + ["sym", "symbol"], + ]) { + await test(obj_name, () => { + const obj = getObject(obj_name); + assert.strictEqual(obj.kind, "attribute"); + assert.strictEqual(joinType(obj.type), type_name); + }); + } + }); + await test("named_interface", () => { + const obj = getObject("interfacer"); + assert.strictEqual(obj.kind, "function"); + assert.deepStrictEqual(obj.params[0].type, [ + { + name: "Interface", + path: ["./", "types.", "Interface"], + type: "internal", + }, + ]); + }); + await test("interface_readonly_member", () => { + const obj = getObject("Interface"); + assert.strictEqual(obj.kind, "interface"); + const readOnlyNum = obj.members[0]; + assert.strictEqual(readOnlyNum.kind, "attribute"); + assert.strictEqual(readOnlyNum.name, "readOnlyNum"); + assert.deepStrictEqual(readOnlyNum.type, [ + { name: "number", type: "intrinsic" }, + ]); + }); + await test("array", () => { + const obj = getObject("overload"); + assert.strictEqual(obj.kind, "function"); + assert.deepStrictEqual(obj.params[0].type, [ + { name: "string", type: "intrinsic" }, + "[]", + ]); + }); + await test("literal_types", () => { + const obj = getObject("certainNumbers"); + assert.strictEqual(obj.kind, "attribute"); + assert.deepStrictEqual(obj.type, [ + { + name: "CertainNumbers", + path: ["./", "types.", "CertainNumbers"], + type: "internal", + }, + ]); + }); +});