Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions src/generators/ast/__tests__/generate.test.mjs
Original file line number Diff line number Diff line change
@@ -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');
});
});
9 changes: 7 additions & 2 deletions src/generators/ast/generate.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});
}
Expand Down
5 changes: 3 additions & 2 deletions src/generators/metadata/utils/parse.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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,
});

Expand Down
Loading