From 20aae69eb481563e519fc64f1d3fea4ee1419277 Mon Sep 17 00:00:00 2001 From: quality <280983584+hivecommons-hive[bot]@users.noreply.github.com> Date: Thu, 12 Nov 2026 16:42:33 -0500 Subject: [PATCH] test: cover add-frontmatter.mjs main() catch block and empty-frontmatter branch - Mocks the glob dependency to deterministically exercise main()'s top-level try/catch, which was previously unreachable because glob() never rejects for missing/unreadable directories. - Adds a fixture for frontmatter that exists but is empty, covering the falsy side of the frontmatterWithoutTitle ternary. - Requires node's --experimental-test-module-mocks flag (added to test:unit) for mock.module() support. scripts/add-frontmatter.mjs and lib/ now report 100% statement/branch/ function/line coverage under c8 (was 97%/90.9%/100%/97% for scripts/, 100%/100%/100%/100% for lib/). Signed-off-by: quality <280983584+hivecommons-hive[bot]@users.noreply.github.com> --- package.json | 2 +- scripts/add-frontmatter.test.mjs | 45 +++++++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 752c0922..eda96b62 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "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 npm run test:unit", - "test:unit": "node --experimental-strip-types --test 'lib/**/*.test.mts' 'scripts/**/*.test.mjs'", + "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", "update:packages": "npx npm-check-updates -u" diff --git a/scripts/add-frontmatter.test.mjs b/scripts/add-frontmatter.test.mjs index e3ccc31f..56e2ea12 100644 --- a/scripts/add-frontmatter.test.mjs +++ b/scripts/add-frontmatter.test.mjs @@ -1,5 +1,5 @@ import assert from 'node:assert/strict'; -import { describe, it } from 'node:test'; +import { describe, it, mock } from 'node:test'; import { execFileSync, spawnSync } from 'node:child_process'; import { mkdtemp, mkdir, readFile, writeFile, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; @@ -83,6 +83,19 @@ describe('add-frontmatter.mjs', () => { assert.equal(results['no-heading.md'], original); }); + it('inserts a title into frontmatter that is present but empty', async () => { + // Exercises the falsy branch of the `frontmatterWithoutTitle ? ... : ''` + // ternary: frontmatter exists (so the title-less remainder is rebuilt + // from it) but has no other keys to preserve alongside the new title. + const results = await runOnFixture({ + 'empty-frontmatter.md': '---\n\n---\n\n# My Page\n\nBody text.\n', + }); + assert.match( + results['empty-frontmatter.md'], + /^---\ntitle: My Page\n---\n\n# My Page\n\nBody text\.\n$/, + ); + }); + it('skips a file whose frontmatter title already matches the heading', async () => { const original = '---\ntitle: My Page\n---\n\n# My Page\n\nBody text.\n'; const results = await runOnFixture({ @@ -107,4 +120,34 @@ describe('add-frontmatter.mjs', () => { await rm(dir, { recursive: true, force: true }); } }); + + it("reports an error instead of crashing when the glob dependency's call itself rejects", async () => { + // Exercises main()'s own top-level try/catch (distinct from + // processFile()'s per-file catch above), which is otherwise unreachable + // because glob() never rejects for missing/unreadable directories - + // mock the dependency to force the failure path deterministically. + const globMock = mock.module('glob', { + exports: { + glob: async () => { + throw new Error('boom'); + }, + }, + }); + const exitMock = mock.method(process, 'exit', () => {}); + const errorMock = mock.method(console, 'error', () => {}); + try { + // Cache-bust so re-running this test file re-executes main(). + await import(`${url.pathToFileURL(scriptPath).href}?t=${Date.now()}`); + // main()'s async body needs a tick to run past its awaited glob() call. + await new Promise((resolve) => setTimeout(resolve, 0)); + + assert.deepEqual(exitMock.mock.calls[0]?.arguments, [1]); + assert.equal(errorMock.mock.calls[0]?.arguments[0], 'Error:'); + assert.match(errorMock.mock.calls[0]?.arguments[1]?.message, /boom/); + } finally { + globMock.restore(); + exitMock.mock.restore(); + errorMock.mock.restore(); + } + }); });