From 38d824ec2080707c2fd39ed3c71a258cca89cbb1 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Sat, 17 Jan 2026 19:20:25 +0100 Subject: [PATCH 1/4] test: add some validation for JSON doc output --- doc/api/environment_variables.md | 4 +- test/parallel/test-doc-api-json.mjs | 151 ++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+), 2 deletions(-) create mode 100644 test/parallel/test-doc-api-json.mjs diff --git a/doc/api/environment_variables.md b/doc/api/environment_variables.md index de1fc0a396c0e4..114287251f91a2 100644 --- a/doc/api/environment_variables.md +++ b/doc/api/environment_variables.md @@ -1,5 +1,7 @@ # Environment Variables + + Environment variables are variables associated to the environment the Node.js process runs in. ## CLI Environment Variables @@ -20,8 +22,6 @@ Set of utilities for dealing with additional environment variables defined in `. > Stability: 2 - Stable - - ### .env files `.env` files (also known as dotenv files) are files that define environment variables, diff --git a/test/parallel/test-doc-api-json.mjs b/test/parallel/test-doc-api-json.mjs new file mode 100644 index 00000000000000..fef64f3a666ecc --- /dev/null +++ b/test/parallel/test-doc-api-json.mjs @@ -0,0 +1,151 @@ +import '../common/index.mjs'; + +import assert from 'node:assert'; +import { existsSync } from 'node:fs'; +import fs from 'node:fs/promises'; +import path from 'node:path'; + +// This tests that `make doc` generates the JSON documentation properly. +// Note that for this test to pass, `make doc` must be run first. + +function validateModule(module) { + assert.strictEqual(typeof module, 'object'); + assert.strictEqual(module.type, 'module'); + assert.ok(module.name); + assert.ok(module.textRaw); +} + +function validateMisc(misc) { + assert.strictEqual(typeof misc, 'object'); + assert.strictEqual(misc.type, 'misc'); + assert.ok(misc.name); + assert.strictEqual(typeof misc.name, 'string'); + assert.ok(misc.textRaw); + assert.strictEqual(typeof misc.textRaw, 'string'); +} + +let numberOfDeprecatedSections = 0; +let numberOfRemovedAPIs = 0; + +const metaExpectedKeys = new Set([ + 'added', + 'changes', + 'deprecated', + 'napiVersion', + 'removed', +]); + +function validateMeta(meta) { + assert.partialDeepStrictEqual(metaExpectedKeys, new Set(Object.keys(meta))); + assert.ok(!Object.hasOwn(meta, 'added') || Array.isArray(meta.added) || typeof meta.added === 'string'); + if (meta.deprecated) { + numberOfDeprecatedSections++; + assert.ok(Array.isArray(meta.deprecated) || typeof meta.deprecated === 'string'); + } + if (meta.removed) { + numberOfRemovedAPIs++; + assert.ok(Array.isArray(meta.removed) || typeof meta.removed === 'string'); + } + + assert.ok(!Object.hasOwn(meta, 'napiVersion') || Number(meta.napiVersion)); + assert.ok(Array.isArray(meta.changes)); +} + +function findAllKeys(obj, allKeys = new Set()) { + for (const [key, value] of Object.entries(obj)) { + if (Number.isNaN(Number(key))) allKeys.add(key); + if (typeof value === 'object') findAllKeys(value, allKeys); + + if (key === 'miscs') { + assert.ok(Array.isArray(value)); + assert.ok(value.length); + value.forEach(validateMisc); + } else if (key === 'modules') { + assert.ok(Array.isArray(value)); + assert.ok(value.length); + value.forEach(validateModule); + } else if (key === 'meta') { + validateMeta(value); + } + } + return allKeys; +} + +const allExpectedKeys = new Set([ + 'added', + 'changes', + 'classes', + 'classMethods', + 'commit', + 'ctors', + 'default', + 'deprecated', + 'desc', + 'description', + 'displayName', + 'events', + 'examples', + 'globals', + 'introduced_in', + 'meta', + 'methods', + 'miscs', + 'modules', + 'name', + 'napiVersion', + 'options', + 'params', + 'pr-url', + 'properties', + 'removed', + 'return', + 'shortDesc', + 'signatures', + 'source', + 'stability', + 'stabilityText', + 'textRaw', + 'type', + 'version', +]); + +for await (const dirent of await fs.opendir(new URL('../../out/doc/api/', import.meta.url))) { + if (!dirent.name.endsWith('.md')) continue; + + const jsonPath = path.join(dirent.parentPath, dirent.name.slice(0, -2) + 'json'); + const expectedSource = `doc/api/${dirent.name}`; + if (dirent.name === 'quic.md') { + assert.ok(!existsSync(jsonPath)); // QUIC documentation is not public yet + continue; + } + + console.log('testing', jsonPath, 'based on', expectedSource); + const json = JSON.parse(await fs.readFile(jsonPath, 'utf8')); + + assert.strictEqual(json.type, 'module'); + assert.strictEqual(json.source, expectedSource); + if (dirent.name !== 'index.md') { + assert.ok(json.introduced_in || Object.values(json).at(-1)?.[0].introduced_in); + } + assert.deepStrictEqual(Object.keys(json), ['type', 'source', ...({ + 'addons.md': ['introduced_in', 'miscs'], + 'cli.md': ['introduced_in', 'miscs'], + 'debugger.md': ['introduced_in', 'stability', 'stabilityText', 'miscs'], + 'deprecations.md': ['introduced_in', 'miscs'], + 'documentation.md': ['introduced_in', 'miscs'], + 'errors.md': ['introduced_in', 'classes', 'miscs'], + 'esm.md': ['introduced_in', 'meta', 'stability', 'stabilityText', 'properties', 'miscs'], + 'globals.md': ['introduced_in', 'stability', 'stabilityText', 'classes', 'methods', 'miscs'], + 'index.md': [], + 'intl.md': ['introduced_in', 'miscs'], + 'n-api.md': ['introduced_in', 'stability', 'stabilityText', 'miscs'], + 'packages.md': ['introduced_in', 'meta', 'miscs'], + 'process.md': ['globals'], + 'report.md': ['introduced_in', 'stability', 'stabilityText', 'meta', 'miscs'], + }[dirent.name] ?? ['modules'])]); + + assert.partialDeepStrictEqual(allExpectedKeys, findAllKeys(json)); +} + +assert.strictEqual(numberOfDeprecatedSections, 39); // Increase this number every time a new API is deprecated. +assert.strictEqual(numberOfRemovedAPIs, 46); // Increase this number every time a section is marked as removed. From 4cbf07825b38554b8538d872a501b03363581414 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Sun, 18 Jan 2026 22:27:11 +0100 Subject: [PATCH 2/4] fixup! test: add some validation for JSON doc output --- test/parallel/test-doc-api-json.mjs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/test/parallel/test-doc-api-json.mjs b/test/parallel/test-doc-api-json.mjs index fef64f3a666ecc..dc8379b5cf3218 100644 --- a/test/parallel/test-doc-api-json.mjs +++ b/test/parallel/test-doc-api-json.mjs @@ -1,4 +1,4 @@ -import '../common/index.mjs'; +import * as common from '../common/index.mjs'; import assert from 'node:assert'; import { existsSync } from 'node:fs'; @@ -8,6 +8,10 @@ import path from 'node:path'; // This tests that `make doc` generates the JSON documentation properly. // Note that for this test to pass, `make doc` must be run first. +if (common.isWindows) { + common.skip('`make doc` does not run on Windows'); +} + function validateModule(module) { assert.strictEqual(typeof module, 'object'); assert.strictEqual(module.type, 'module'); @@ -120,7 +124,14 @@ for await (const dirent of await fs.opendir(new URL('../../out/doc/api/', import } console.log('testing', jsonPath, 'based on', expectedSource); - const json = JSON.parse(await fs.readFile(jsonPath, 'utf8')); + + const fileContent = await fs.readFile(jsonPath, 'utf8'); + // A proxy to check if the file is human readable is to count if it contains + // at least 3 line return. + assert.strictEqual(fileContent.split('\n', 3).length, 3); + assert.ok(fileContent.endsWith('\n'), 'EOL at EOF'); + + const json = JSON.parse(fileContent); assert.strictEqual(json.type, 'module'); assert.strictEqual(json.source, expectedSource); From bb9a39be256d7588c7d7853289a13878c7e8d9ba Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Sun, 18 Jan 2026 22:29:27 +0100 Subject: [PATCH 3/4] fixup! test: add some validation for JSON doc output --- test/parallel/test-doc-api-json.mjs | 1 - 1 file changed, 1 deletion(-) diff --git a/test/parallel/test-doc-api-json.mjs b/test/parallel/test-doc-api-json.mjs index dc8379b5cf3218..0d4dcf1de7206a 100644 --- a/test/parallel/test-doc-api-json.mjs +++ b/test/parallel/test-doc-api-json.mjs @@ -129,7 +129,6 @@ for await (const dirent of await fs.opendir(new URL('../../out/doc/api/', import // A proxy to check if the file is human readable is to count if it contains // at least 3 line return. assert.strictEqual(fileContent.split('\n', 3).length, 3); - assert.ok(fileContent.endsWith('\n'), 'EOL at EOF'); const json = JSON.parse(fileContent); From 6519805dfffff36e92a1fa9329cfe10b81f36faa Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Mon, 19 Jan 2026 00:19:13 +0100 Subject: [PATCH 4/4] squash! lint --- test/parallel/test-doc-api-json.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/parallel/test-doc-api-json.mjs b/test/parallel/test-doc-api-json.mjs index 0d4dcf1de7206a..6e94a79fe75c20 100644 --- a/test/parallel/test-doc-api-json.mjs +++ b/test/parallel/test-doc-api-json.mjs @@ -1,4 +1,4 @@ -import * as common from '../common/index.mjs'; +import * as common from '../common/index.mjs'; import assert from 'node:assert'; import { existsSync } from 'node:fs';