Skip to content

Commit 0f69d7b

Browse files
authored
test: add missing coverage for json parser, url utils, and generator helpers (#679)
1 parent 69ac8a2 commit 0f69d7b

File tree

3 files changed

+181
-1
lines changed

3 files changed

+181
-1
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import assert from 'node:assert/strict';
2+
import { describe, it, mock } from 'node:test';
3+
4+
let content;
5+
mock.module('../../utils/parser.mjs', {
6+
namedExports: {
7+
loadFromURL: async () => content,
8+
},
9+
});
10+
11+
const { parseTypeMap } = await import('../json.mjs');
12+
13+
describe('parseTypeMap', () => {
14+
it('should return an empty object when path is falsy', async () => {
15+
for (const falsy of [undefined, null, '']) {
16+
assert.deepStrictEqual(await parseTypeMap(falsy), {});
17+
}
18+
});
19+
20+
it('should parse and return the JSON content from a given path', async () => {
21+
content = JSON.stringify({ Buffer: 'buffer.md', fs: 'fs.md' });
22+
const result = await parseTypeMap('/some/path/types.json');
23+
assert.deepStrictEqual(result, { Buffer: 'buffer.md', fs: 'fs.md' });
24+
});
25+
26+
it('should throw a SyntaxError when content is not valid JSON', async () => {
27+
content = 'not valid json';
28+
await assert.rejects(
29+
() => parseTypeMap('/some/path/types.json'),
30+
SyntaxError
31+
);
32+
});
33+
});

src/utils/__tests__/generators.test.mjs

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import assert from 'node:assert/strict';
2-
import { describe, it } from 'node:test';
2+
import { describe, it, mock, afterEach } from 'node:test';
33

44
import {
55
groupNodesByModule,
66
getVersionFromSemVer,
77
coerceSemVer,
88
getCompatibleVersions,
9+
legacyToJSON,
10+
buildApiDocURL,
11+
createLazyGenerator,
912
} from '../generators.mjs';
1013

1114
describe('groupNodesByModule', () => {
@@ -79,3 +82,94 @@ describe('getCompatibleVersions', () => {
7982
assert.equal(result.length, 2);
8083
});
8184
});
85+
86+
describe('legacyToJSON', () => {
87+
const base = {
88+
type: 'module',
89+
source: 'lib/fs.js',
90+
introduced_in: 'v0.10.0',
91+
meta: {},
92+
stability: 2,
93+
stabilityText: 'Stable',
94+
classes: [],
95+
methods: ['readFile'],
96+
properties: [],
97+
miscs: [],
98+
modules: ['fs'],
99+
globals: [],
100+
};
101+
102+
it('serialises a normal section with all keys', () => {
103+
const result = JSON.parse(legacyToJSON({ ...base, api: 'fs' }));
104+
assert.ok('type' in result);
105+
assert.ok('methods' in result);
106+
assert.ok('modules' in result);
107+
});
108+
109+
it('omits modules key for index sections', () => {
110+
const result = JSON.parse(legacyToJSON({ ...base, api: 'index' }));
111+
assert.ok(!('modules' in result));
112+
});
113+
114+
it('uses all.json key order when api is null', () => {
115+
const result = JSON.parse(legacyToJSON({ ...base, api: null }));
116+
// all.json only includes miscs, modules, classes, globals, methods
117+
assert.ok('miscs' in result);
118+
assert.ok('modules' in result);
119+
assert.ok(!('type' in result));
120+
assert.ok(!('source' in result));
121+
});
122+
123+
it('passes extra args to JSON.stringify (e.g. indentation)', () => {
124+
const result = legacyToJSON({ ...base, api: 'fs' }, null, 2);
125+
assert.ok(result.includes('\n'));
126+
});
127+
});
128+
129+
describe('buildApiDocURL', () => {
130+
const entry = { api: 'fs' };
131+
const base = 'https://nodejs.org';
132+
133+
it('builds a .md URL by default', () => {
134+
const url = buildApiDocURL(entry, base);
135+
assert.ok(url instanceof URL);
136+
assert.ok(url.pathname.endsWith('.md'));
137+
assert.ok(url.pathname.includes('/fs'));
138+
});
139+
140+
it('builds a .html URL when useHtml is true', () => {
141+
const url = buildApiDocURL(entry, base, true);
142+
assert.ok(url.pathname.endsWith('.html'));
143+
});
144+
});
145+
146+
describe('createLazyGenerator', () => {
147+
afterEach(() => mock.restoreAll());
148+
149+
it('spreads metadata properties onto the returned object', () => {
150+
const metadata = { name: 'ast', version: '1.0.0', dependsOn: undefined };
151+
const gen = createLazyGenerator(metadata);
152+
assert.equal(gen.name, 'ast');
153+
assert.equal(gen.version, '1.0.0');
154+
});
155+
156+
it('exposes generate and processChunk functions that delegate to the lazily loaded module', async () => {
157+
// Both exports are mocked in a single mock.module() call to avoid ESM import
158+
// cache collisions that occur when re-mocking the same specifier across two it() blocks.
159+
const specifier = import.meta.resolve('../../generators/ast/generate.mjs');
160+
const fakeGenerate = async input => `processed:${input}`;
161+
const fakeProcessChunk = async (input, indices) =>
162+
indices.map(i => input[i]);
163+
mock.module(specifier, {
164+
namedExports: { generate: fakeGenerate, processChunk: fakeProcessChunk },
165+
});
166+
167+
const gen = createLazyGenerator({ name: 'ast' });
168+
169+
const generateResult = await gen.generate('hello');
170+
assert.equal(generateResult, 'processed:hello');
171+
172+
const processChunkResult = await gen.processChunk(['a', 'b', 'c'], [0, 2]);
173+
assert.deepStrictEqual(processChunkResult, ['a', 'c']);
174+
});
175+
});

src/utils/__tests__/url.test.mjs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import assert from 'node:assert/strict';
2+
import { describe, it, mock, afterEach } from 'node:test';
3+
4+
let fileContent = 'hello from file';
5+
mock.module('node:fs/promises', {
6+
namedExports: {
7+
readFile: async () => fileContent,
8+
},
9+
});
10+
11+
const { toParsedURL, loadFromURL } = await import('../url.mjs');
12+
13+
describe('toParsedURL', () => {
14+
it('should return the same URL instance when given a URL object', () => {
15+
const url = new URL('https://nodejs.org');
16+
assert.strictEqual(toParsedURL(url), url);
17+
});
18+
19+
it('should parse a valid URL string into a URL object', () => {
20+
const result = toParsedURL('https://nodejs.org/api');
21+
assert.ok(result instanceof URL);
22+
assert.strictEqual(result.hostname, 'nodejs.org');
23+
});
24+
25+
it('should return null for a string that cannot be parsed as a URL', () => {
26+
assert.strictEqual(toParsedURL('not-a-url'), null);
27+
});
28+
});
29+
30+
describe('loadFromURL', () => {
31+
afterEach(() => mock.restoreAll());
32+
33+
it('should read content from the filesystem for a plain file path', async () => {
34+
fileContent = 'file content';
35+
const result = await loadFromURL('/some/path/file.txt');
36+
assert.strictEqual(result, 'file content');
37+
});
38+
39+
it('should read content from the filesystem for a file: URL', async () => {
40+
fileContent = 'from file url';
41+
const result = await loadFromURL(new URL('file:///some/file.txt'));
42+
assert.strictEqual(result, 'from file url');
43+
});
44+
45+
it('should fetch content from an http URL', async () => {
46+
mock.method(globalThis, 'fetch', async () => ({
47+
text: async () => 'fetched content',
48+
}));
49+
50+
const result = await loadFromURL('https://nodejs.org/data.txt');
51+
assert.strictEqual(result, 'fetched content');
52+
});
53+
});

0 commit comments

Comments
 (0)