diff --git a/src/generators/ast/__tests__/generate.test.mjs b/src/generators/ast/__tests__/generate.test.mjs new file mode 100644 index 00000000..846433dc --- /dev/null +++ b/src/generators/ast/__tests__/generate.test.mjs @@ -0,0 +1,70 @@ +'use strict'; + +import assert from 'node:assert/strict'; +import { describe, it, mock } from 'node:test'; + +// Mock fs/promises so processChunk doesn't touch the real filesystem +mock.module('node:fs/promises', { + namedExports: { + readFile: async () => '# Hello', + }, +}); + +// Mock remark to avoid parsing overhead +mock.module('../../../utils/remark.mjs', { + namedExports: { + getRemark: () => ({ parse: () => ({ type: 'root', children: [] }) }), + }, +}); + +// Mock queries to avoid regex replacements interfering +mock.module('../../../utils/queries/index.mjs', { + namedExports: { + QUERIES: { + standardYamlFrontmatter: /(?!x)x/, // never matches + stabilityIndexPrefix: /(?!x)x/, // never matches + }, + }, +}); + +const { processChunk } = await import('../generate.mjs'); + +describe('processChunk path computation', () => { + it('strips /index suffix from a top-level index file', async () => { + const results = await processChunk([['doc/api/index.md', 'doc/api']], [0]); + assert.strictEqual(results[0].path, '/'); + }); + + it('strips /index suffix from a nested index file', async () => { + const results = await processChunk( + [['doc/api/sub/index.md', 'doc/api']], + [0] + ); + assert.strictEqual(results[0].path, '/sub'); + }); + + it('keeps path unchanged for non-index files', async () => { + const results = await processChunk([['doc/api/fs.md', 'doc/api']], [0]); + assert.strictEqual(results[0].path, '/fs'); + }); + + it('keeps path unchanged for files whose name contains index but is not index', async () => { + const results = await processChunk( + [['doc/api/indexes.md', 'doc/api']], + [0] + ); + assert.strictEqual(results[0].path, '/indexes'); + }); + + it('processes multiple files correctly', async () => { + const input = [ + ['doc/api/index.md', 'doc/api'], + ['doc/api/fs.md', 'doc/api'], + ['doc/api/sub/index.md', 'doc/api'], + ]; + const results = await processChunk(input, [0, 1, 2]); + assert.strictEqual(results[0].path, '/'); + assert.strictEqual(results[1].path, '/fs'); + assert.strictEqual(results[2].path, '/sub'); + }); +}); diff --git a/src/generators/ast/generate.mjs b/src/generators/ast/generate.mjs index e0c5a6c5..3be5e192 100644 --- a/src/generators/ast/generate.mjs +++ b/src/generators/ast/generate.mjs @@ -35,11 +35,16 @@ export async function processChunk(inputSlice, itemIndices) { match => `[${match}](${STABILITY_INDEX_URL})` ); - const relativePath = sep + withExt(relative(parent, path)); + const strippedPath = withExt(relative(parent, path)); + // Treat index files as the directory root (e.g. /index → /, /api/index → /api) + const relativePath = + strippedPath === 'index' + ? sep + : sep + strippedPath.replace(/(\/|^)index$/, ''); results.push({ tree: remark().parse(value), - // The path is the relative path minus the extension + // The path is the relative path minus the extension (and /index suffix) path: relativePath, }); } diff --git a/src/generators/metadata/utils/parse.mjs b/src/generators/metadata/utils/parse.mjs index 29192653..7436e601 100644 --- a/src/generators/metadata/utils/parse.mjs +++ b/src/generators/metadata/utils/parse.mjs @@ -42,7 +42,8 @@ export const parseApiDoc = ({ path, tree }, typeMap) => { const nodeSlugger = createNodeSlugger(); // Slug the API (We use a non-class slugger, since we are fairly certain that `path` is unique) - const api = slug(path.slice(1).replace(sep, '-')); + // When path is the root ('/'), the file is an index and the api identifier falls back to 'index' + const api = slug(path.slice(1).replace(sep, '-')) || 'index'; // Get all Markdown Footnote definitions from the tree const markdownDefinitions = selectAll('definition', tree); @@ -83,7 +84,7 @@ export const parseApiDoc = ({ path, tree }, typeMap) => { const metadata = /** @type {import('../types').MetadataEntry} */ ({ api, path, - basename: basename(path), + basename: basename(path) || 'index', heading: headingNode, });