From d9b8476f977dd9a34fe1f9a223155eddfc06aaa1 Mon Sep 17 00:00:00 2001 From: quality <280983584+hivecommons-hive[bot]@users.noreply.github.com> Date: Thu, 12 Nov 2026 23:11:15 -0500 Subject: [PATCH] test: cover docusaurus.config.ts markdown preprocessor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds lib/docusaurusConfigPreprocessor.test.mts, exercising both branches of the inline markdown.preprocessor function (root README.md import vs. pass-through) directly against the real docusaurus.config.ts. docusaurus.config.ts imports its ./lib/* helpers without file extensions, which only Docusaurus's own webpack bundler resolves — node --experimental-strip-types --test fails with ERR_MODULE_NOT_FOUND on a direct import. lib/testHelpers/resolveExtensionlessTs.mjs is a test-only Node module-resolution hook (registerHooks) that falls back to .ts / /index.ts, so the test can import the real config file without any change to its production imports. Because docusaurus.config.ts also contains several unrelated, env-dependent branches (isProd/DEPLOY_PRIME_URL ternaries) that this test does not and should not target, package.json's test:coverage excludes docusaurus.config.ts and lib/testHelpers/** from the --check-coverage thresholds added in #403, so the existing 97%+ branch/line/function/statement gate keeps passing. Signed-off-by: quality <280983584+hivecommons-hive[bot]@users.noreply.github.com> --- lib/docusaurusConfigPreprocessor.test.mts | 41 ++++++++++++++++++++++ lib/testHelpers/resolveExtensionlessTs.mjs | 34 ++++++++++++++++++ package.json | 2 +- 3 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 lib/docusaurusConfigPreprocessor.test.mts create mode 100644 lib/testHelpers/resolveExtensionlessTs.mjs diff --git a/lib/docusaurusConfigPreprocessor.test.mts b/lib/docusaurusConfigPreprocessor.test.mts new file mode 100644 index 00000000..45b47471 --- /dev/null +++ b/lib/docusaurusConfigPreprocessor.test.mts @@ -0,0 +1,41 @@ +import { registerHooks } from 'node:module'; +import path from 'node:path'; +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { resolve } from './testHelpers/resolveExtensionlessTs.mjs'; + +// docusaurus.config.ts imports its ./lib/* helpers without file extensions, +// which only Docusaurus's own webpack bundler resolves. Register a +// test-only hook (see testHelpers/resolveExtensionlessTs.mjs) so this file +// can import the real docusaurus.config.ts directly, instead of duplicating +// its markdown.preprocessor logic. +registerHooks({ resolve }); + +const { default: config } = await import('../docusaurus.config.ts'); +const { preprocessor } = config.markdown; +const importedReadmePath = path.resolve('README.md'); + +test('markdown.preprocessor (root README.md import)', async (t) => { + await t.test('strips .md from /docs and /analyses links', () => { + const result = preprocessor({ + fileContent: + 'See [docs](/docs/foo.md) and [analyses](/analyses/bar.md#section).', + filePath: importedReadmePath, + }); + + assert.equal( + result, + 'See [docs](/docs/foo/) and [analyses](/analyses/bar/#section).', + ); + }); + + await t.test('leaves non-README imports untouched', () => { + const fileContent = 'See [docs](/docs/foo.md).'; + const result = preprocessor({ + fileContent, + filePath: path.resolve('docs/other.md'), + }); + + assert.equal(result, fileContent); + }); +}); diff --git a/lib/testHelpers/resolveExtensionlessTs.mjs b/lib/testHelpers/resolveExtensionlessTs.mjs new file mode 100644 index 00000000..0f114976 --- /dev/null +++ b/lib/testHelpers/resolveExtensionlessTs.mjs @@ -0,0 +1,34 @@ +// Minimal Node.js module-resolution hook used only by tests. +// +// docusaurus.config.ts imports its ./lib/* helpers without file extensions +// (e.g. `./lib/getBuildContext`), which Docusaurus's own webpack bundler +// resolves fine but Node's native ESM loader does not. This hook teaches +// Node to fall back to `.ts` (and `/index.ts` for +// directory imports) so docusaurus.config.ts can be imported directly in +// `node --test`, without changing any production import statement. +import { existsSync, statSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; + +export function resolve(specifier, context, nextResolve) { + try { + return nextResolve(specifier, context); + } catch (err) { + if (!specifier.startsWith('.')) throw err; + + if (err.code === 'ERR_MODULE_NOT_FOUND') { + const candidate = new URL(specifier + '.ts', context.parentURL); + if (existsSync(fileURLToPath(candidate))) { + return nextResolve(specifier + '.ts', context); + } + } + + if (err.code === 'ERR_UNSUPPORTED_DIR_IMPORT') { + const dirPath = fileURLToPath(new URL(specifier, context.parentURL)); + if (existsSync(dirPath) && statSync(dirPath).isDirectory()) { + return nextResolve(specifier + '/index.ts', context); + } + } + + throw err; + } +} diff --git a/package.json b/package.json index 6f9ef2a8..5beb9545 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "precheck:links": "npm run build", "seq": "bash -c 'for cmd in \"$@\"; do npm run $cmd || exit 1; done' - ", "serve": "npm run docus:serve", - "test:coverage": "c8 --reporter=text --reporter=lcov --check-coverage --lines 99 --statements 99 --branches 97 --functions 100 npm run test:unit", + "test:coverage": "c8 --reporter=text --reporter=lcov --exclude 'docusaurus.config.ts' --exclude 'lib/testHelpers/**' --check-coverage --lines 99 --statements 99 --branches 97 --functions 100 npm run test:unit", "test:unit": "node --experimental-strip-types --experimental-test-module-mocks --test 'lib/**/*.test.mts' 'scripts/**/*.test.mjs'", "test": "npm run check && npm run test:unit", "typecheck": "tsc",