Skip to content

Commit ba07721

Browse files
committed
unit tests
1 parent b3f8062 commit ba07721

File tree

8 files changed

+249
-7
lines changed

8 files changed

+249
-7
lines changed

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@
1010
"format": "prettier .",
1111
"format:write": "prettier --write .",
1212
"format:check": "prettier --check .",
13-
"test": "node --test",
13+
"test": "node --experimental-test-module-mocks --test",
1414
"test:coverage": "c8 npm test",
15-
"test:ci": "c8 --reporter=lcov node --test --test-reporter=@reporters/github --test-reporter-destination=stdout --test-reporter=junit --test-reporter-destination=junit.xml --test-reporter=spec --test-reporter-destination=stdout",
16-
"test:update-snapshots": "node --test --test-update-snapshots",
17-
"test:watch": "node --test --watch",
15+
"test:ci": "c8 --reporter=lcov node --experimental-test-module-mocks --test --test-reporter=@reporters/github --test-reporter-destination=stdout --test-reporter=junit --test-reporter-destination=junit.xml --test-reporter=spec --test-reporter-destination=stdout",
16+
"test:update-snapshots": "node --experimental-test-module-mocks --test --test-update-snapshots",
17+
"test:watch": "node --experimental-test-module-mocks --test --watch",
1818
"prepare": "husky",
1919
"run": "node bin/cli.mjs",
2020
"watch": "node --watch bin/cli.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 } from 'node:test';
3+
4+
import { buildHierarchicalTitle } from '../index.mjs';
5+
6+
describe('buildHierarchicalTitle', () => {
7+
const mockHeadings = [
8+
{ heading: { data: { name: 'Module' }, depth: 1 } },
9+
{ heading: { data: { name: 'Class' }, depth: 2 } },
10+
{ heading: { data: { name: 'Method' }, depth: 3 } },
11+
{ heading: { data: { name: 'Parameter' }, depth: 4 } },
12+
];
13+
14+
it('should build single level title', () => {
15+
const result = buildHierarchicalTitle(mockHeadings, 0);
16+
assert.equal(result, 'Module');
17+
});
18+
19+
it('should build two level hierarchy', () => {
20+
const result = buildHierarchicalTitle(mockHeadings, 1);
21+
assert.equal(result, 'Module > Class');
22+
});
23+
24+
it('should build three level hierarchy', () => {
25+
const result = buildHierarchicalTitle(mockHeadings, 2);
26+
assert.equal(result, 'Module > Class > Method');
27+
});
28+
29+
it('should build full hierarchy', () => {
30+
const result = buildHierarchicalTitle(mockHeadings, 3);
31+
assert.equal(result, 'Module > Class > Method > Parameter');
32+
});
33+
34+
it('should handle non-sequential depths', () => {
35+
const headings = [
36+
{ heading: { data: { name: 'Root' }, depth: 1 } },
37+
{ heading: { data: { name: 'Deep' }, depth: 4 } },
38+
];
39+
40+
const result = buildHierarchicalTitle(headings, 1);
41+
assert.equal(result, 'Root > Deep');
42+
});
43+
44+
it('should handle same depth headings', () => {
45+
const headings = [
46+
{ heading: { data: { name: 'First' }, depth: 2 } },
47+
{ heading: { data: { name: 'Second' }, depth: 2 } },
48+
];
49+
50+
const result = buildHierarchicalTitle(headings, 1);
51+
assert.equal(result, 'Second');
52+
});
53+
});

src/generators/orama-db/index.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { groupNodesByModule } from '../../utils/generators.mjs';
1313
* @param {number} currentIndex - Index of current heading
1414
* @returns {string} Hierarchical title
1515
*/
16-
function buildHierarchicalTitle(headings, currentIndex) {
16+
export function buildHierarchicalTitle(headings, currentIndex) {
1717
const currentNode = headings[currentIndex];
1818
const titleChain = [currentNode.heading.data.name];
1919
let targetDepth = currentNode.heading.depth - 1;
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import assert from 'node:assert/strict';
2+
import { describe, it, mock } from 'node:test';
3+
4+
mock.module('@node-core/rehype-shiki', {
5+
namedExports: {
6+
shiki: {
7+
getLoadedLanguages: () => ['javascript', 'python'],
8+
getLanguage: lang => ({
9+
name: lang,
10+
_grammar: {
11+
aliases: lang === 'javascript' ? ['js'] : [],
12+
displayName: lang === 'javascript' ? 'JavaScript' : 'Python',
13+
},
14+
}),
15+
},
16+
},
17+
});
18+
19+
const { createStaticData } = await import('../data.mjs');
20+
21+
describe('createStaticData', () => {
22+
it('should return shikiDisplayNameMap array', () => {
23+
const result = createStaticData();
24+
25+
assert.ok(Array.isArray(result.shikiDisplayNameMap));
26+
assert.equal(result.shikiDisplayNameMap.length, 2);
27+
});
28+
29+
it('should format language data correctly', () => {
30+
const result = createStaticData();
31+
const [jsAliases, jsDisplayName] = result.shikiDisplayNameMap[0];
32+
33+
assert.deepEqual(jsAliases, ['js', 'javascript']);
34+
assert.equal(jsDisplayName, 'JavaScript');
35+
});
36+
37+
it('should handle languages without aliases', () => {
38+
const result = createStaticData();
39+
const pythonEntry = result.shikiDisplayNameMap.find(([aliases]) =>
40+
aliases.includes('python')
41+
);
42+
43+
assert.deepEqual(pythonEntry[0], ['python']);
44+
assert.equal(pythonEntry[1], 'Python');
45+
});
46+
});
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import assert from 'node:assert/strict';
2+
import { describe, it } from 'node:test';
3+
4+
import * as t from '@babel/types';
5+
6+
import createAstBuilder, { createImportDeclaration } from '../generate.mjs';
7+
8+
describe('createImportDeclaration', () => {
9+
it('should create default import', () => {
10+
const ast = createImportDeclaration('React', 'react');
11+
12+
assert.equal(ast.type, 'ImportDeclaration');
13+
assert.equal(ast.source.value, 'react');
14+
assert.equal(ast.specifiers.length, 1);
15+
assert.equal(ast.specifiers[0].type, 'ImportDefaultSpecifier');
16+
});
17+
18+
it('should create named import', () => {
19+
const ast = createImportDeclaration('useState', 'react', false);
20+
21+
assert.equal(ast.type, 'ImportDeclaration');
22+
assert.equal(ast.specifiers[0].type, 'ImportSpecifier');
23+
});
24+
25+
it('should create side-effect import', () => {
26+
const ast = createImportDeclaration(null, './styles.css');
27+
28+
assert.equal(ast.specifiers.length, 0);
29+
assert.equal(ast.source.value, './styles.css');
30+
});
31+
});
32+
33+
describe('AST Builder', () => {
34+
const builder = createAstBuilder();
35+
const mockComponent = t.identifier('MyComponent');
36+
37+
it('should build client program with hydrate call', () => {
38+
const code = builder.buildClientProgram(mockComponent);
39+
40+
assert.ok(code.includes('hydrate'));
41+
assert.ok(code.includes('document.getElementById'));
42+
assert.ok(code.includes('"root"'));
43+
});
44+
45+
it('should build server program with renderToStringAsync', () => {
46+
const code = builder.buildServerProgram(mockComponent);
47+
48+
assert.ok(code.includes('renderToStringAsync'));
49+
assert.ok(code.includes('code ='));
50+
});
51+
52+
it('should include CSS import in client only', () => {
53+
const clientCode = builder.buildClientProgram(mockComponent);
54+
const serverCode = builder.buildServerProgram(mockComponent);
55+
56+
assert.ok(clientCode.includes('./index.css'));
57+
assert.ok(!serverCode.includes('./index.css'));
58+
});
59+
60+
it('should return object with both build functions', () => {
61+
assert.equal(typeof builder.buildClientProgram, 'function');
62+
assert.equal(typeof builder.buildServerProgram, 'function');
63+
});
64+
});
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import assert from 'node:assert/strict';
2+
import fs from 'node:fs';
3+
import { describe, it, mock } from 'node:test';
4+
5+
const existsSync = mock.fn();
6+
mock.module('node:fs', { namedExports: { ...fs, existsSync } });
7+
8+
const { uiComponentsResolverPlugin } = await import('../plugins.mjs');
9+
10+
describe('uiComponentsResolverPlugin', async () => {
11+
let onResolveCallback;
12+
13+
uiComponentsResolverPlugin.setup({
14+
onResolve: (_, callback) => {
15+
onResolveCallback = callback;
16+
},
17+
});
18+
19+
it('should skip paths with file extensions', () => {
20+
const result = onResolveCallback({
21+
path: '@node-core/ui-components/button.tsx',
22+
});
23+
assert.equal(result, undefined);
24+
});
25+
26+
it('should process paths without extensions', () => {
27+
existsSync.mock.mockImplementation(() => false);
28+
29+
const result = onResolveCallback({
30+
path: '@node-core/ui-components/button',
31+
});
32+
assert.equal(result, undefined); // Returns undefined when no files exist
33+
});
34+
35+
it('should resolve when index.tsx exists', () => {
36+
existsSync.mock.mockImplementation(path => path.includes('index.tsx'));
37+
38+
const result = onResolveCallback({
39+
path: '@node-core/ui-components/button',
40+
});
41+
42+
assert.ok(result.path.includes('button'));
43+
assert.ok(result.path.endsWith('index.tsx'));
44+
});
45+
46+
it('should fall back to .tsx when index.tsx does not exist', () => {
47+
existsSync.mock.mockImplementation(
48+
path => path.endsWith('.tsx') && !path.includes('index.tsx')
49+
);
50+
51+
const result = onResolveCallback({
52+
path: '@node-core/ui-components/button',
53+
});
54+
55+
assert.ok(result.path.includes('button'));
56+
assert.ok(result.path.endsWith('.tsx'));
57+
assert.ok(!result.path.includes('index.tsx'));
58+
});
59+
60+
it('should return undefined when no files exist', () => {
61+
existsSync.mock.mockImplementation(() => false);
62+
63+
const result = onResolveCallback({
64+
path: '@node-core/ui-components/button',
65+
});
66+
assert.equal(result, undefined);
67+
});
68+
69+
it('should handle #ui/ alias conversion', () => {
70+
existsSync.mock.mockImplementation(() => false);
71+
72+
// Should not throw and should attempt resolution
73+
const result = onResolveCallback({ path: '#ui/button' });
74+
assert.equal(result, undefined);
75+
});
76+
});

src/generators/web/build/generate.mjs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ import { JSX_IMPORTS } from '../constants.mjs';
99
* @param {string} source - The module path to import from
1010
* @param {boolean} [useDefault=true] - Whether to use a default import or a named import
1111
*/
12-
const createImportDeclaration = (importName, source, useDefault = true) => {
12+
export const createImportDeclaration = (
13+
importName,
14+
source,
15+
useDefault = true
16+
) => {
1317
const specifiers = importName
1418
? [
1519
useDefault

src/generators/web/build/plugins.mjs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import postcssCalc from 'postcss-calc';
1212
* and resolves them to the corresponding TypeScript files. It follows a resolution
1313
* strategy where it first tries to find an index.tsx file, then falls back to a
1414
* .tsx file with the same name.
15-
* ```
1615
*/
1716
export const uiComponentsResolverPlugin = {
1817
name: 'ui-components-resolver',

0 commit comments

Comments
 (0)