@abreen/tada 1.5.1 → 1.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -2
- package/bin/validators.test.ts +0 -205
- package/build/build-manifest.test.ts +0 -369
- package/build/code.test.ts +0 -272
- package/build/colors.test.ts +0 -66
- package/build/globals.test.ts +0 -100
- package/build/json-schema.test.ts +0 -57
- package/build/log.test.ts +0 -117
- package/build/markdown-plugins.test.ts +0 -456
- package/build/pagefind.test.ts +0 -192
- package/build/pdf-text.test.ts +0 -52
- package/build/reachability.test.ts +0 -80
- package/build/serve.test.ts +0 -59
- package/build/toc-plugin.test.ts +0 -120
- package/build/utils/content-files.test.ts +0 -56
- package/build/utils/derive-theme.test.ts +0 -111
- package/build/utils/file-types.test.ts +0 -79
- package/build/utils/front-matter.test.ts +0 -77
- package/build/utils/include.test.ts +0 -117
- package/build/utils/paths.test.ts +0 -91
- package/build/utils/trace-layout.test.ts +0 -570
- package/build/utils/trace-svg.test.ts +0 -425
- package/build/utils/trace.test.ts +0 -169
- package/build/validate-config-links.test.ts +0 -239
- package/build/validate-internal-links-plugin.test.ts +0 -134
- package/build/watch-reachability-state.test.ts +0 -198
- package/src/anchor/index.test.ts +0 -64
- package/src/code/index.test.ts +0 -71
- package/src/header/index.test.ts +0 -89
- package/src/navigate/eligible.test.ts +0 -101
- package/src/navigate/index.test.ts +0 -82
- package/src/navigate/lifecycle.test.ts +0 -26
- package/src/print/index.test.ts +0 -71
- package/src/question/index.test.ts +0 -82
- package/src/search/index.test.ts +0 -203
- package/src/timezone/index.test.ts +0 -400
- package/src/toc/index.test.ts +0 -237
- package/src/top/index.test.ts +0 -46
- package/src/trace/index.test.ts +0 -554
- package/src/util.test.ts +0 -135
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test';
|
|
2
|
-
import {
|
|
3
|
-
collectDirectSiteAssetLinks,
|
|
4
|
-
collectReachableSiteAssets,
|
|
5
|
-
} from './reachability';
|
|
6
|
-
|
|
7
|
-
describe('reachability', () => {
|
|
8
|
-
test('collectDirectSiteAssetLinks resolves relative links and ignores excluded links', () => {
|
|
9
|
-
const result = collectDirectSiteAssetLinks({
|
|
10
|
-
html: `
|
|
11
|
-
<a href="../guide.html?x=1#top">Guide</a>
|
|
12
|
-
<a class="disabled" href="/ignore/">Ignore</a>
|
|
13
|
-
<a href="https://example.com/">External</a>
|
|
14
|
-
<a href="#section">Anchor</a>
|
|
15
|
-
<a href="mailto:test@example.com">Mail</a>
|
|
16
|
-
<a href="/docs/guide.pdf#page=2">PDF</a>
|
|
17
|
-
`,
|
|
18
|
-
fromAssetPath: 'section/index.html',
|
|
19
|
-
knownAssets: new Set(['guide.html', 'ignore/index.html']),
|
|
20
|
-
knownPdfPaths: new Set(['/docs/guide.pdf']),
|
|
21
|
-
basePath: '/',
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
expect(result).toEqual({
|
|
25
|
-
htmlAssetPaths: ['guide.html'],
|
|
26
|
-
pdfPaths: ['/docs/guide.pdf'],
|
|
27
|
-
});
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
test('collectDirectSiteAssetLinks honors basePath and meta refresh', () => {
|
|
31
|
-
const result = collectDirectSiteAssetLinks({
|
|
32
|
-
html: `
|
|
33
|
-
<a href="/course/faq/">FAQ</a>
|
|
34
|
-
<meta http-equiv="refresh" content="0; url='/course/refresh/'" />
|
|
35
|
-
`,
|
|
36
|
-
fromAssetPath: 'index.html',
|
|
37
|
-
knownAssets: new Set(['faq/index.html', 'refresh/index.html']),
|
|
38
|
-
basePath: '/course',
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
expect(result).toEqual({
|
|
42
|
-
htmlAssetPaths: ['faq/index.html', 'refresh/index.html'],
|
|
43
|
-
pdfPaths: [],
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
test('collectReachableSiteAssets reuses direct link parsing for html and pdf reachability', () => {
|
|
48
|
-
const htmlAssetsByPath = new Map([
|
|
49
|
-
[
|
|
50
|
-
'index.html',
|
|
51
|
-
`
|
|
52
|
-
<a href="/about/">About</a>
|
|
53
|
-
<a href="/docs/guide.pdf">Guide PDF</a>
|
|
54
|
-
`,
|
|
55
|
-
],
|
|
56
|
-
[
|
|
57
|
-
'about/index.html',
|
|
58
|
-
`<meta http-equiv="refresh" content="0; url='/redirect/'" />`,
|
|
59
|
-
],
|
|
60
|
-
['redirect/index.html', '<p>Done</p>'],
|
|
61
|
-
['orphan/index.html', '<p>Orphan</p>'],
|
|
62
|
-
]);
|
|
63
|
-
|
|
64
|
-
const result = collectReachableSiteAssets({
|
|
65
|
-
htmlAssetsByPath,
|
|
66
|
-
knownPdfPaths: new Set(['/docs/guide.pdf']),
|
|
67
|
-
rootPath: 'index.html',
|
|
68
|
-
basePath: '/',
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
expect(result).toEqual({
|
|
72
|
-
reachableHtmlPaths: [
|
|
73
|
-
'about/index.html',
|
|
74
|
-
'index.html',
|
|
75
|
-
'redirect/index.html',
|
|
76
|
-
],
|
|
77
|
-
reachablePdfPaths: ['/docs/guide.pdf'],
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
});
|
package/build/serve.test.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import { resolvePathname } from './serve';
|
|
5
|
-
|
|
6
|
-
let tmpDir: string;
|
|
7
|
-
|
|
8
|
-
beforeAll(() => {
|
|
9
|
-
tmpDir = fs.mkdtempSync('/tmp/serve-test-');
|
|
10
|
-
fs.writeFileSync(path.join(tmpDir, 'index.html'), '<h1>hello</h1>');
|
|
11
|
-
fs.mkdirSync(path.join(tmpDir, 'sub'));
|
|
12
|
-
fs.writeFileSync(path.join(tmpDir, 'sub', 'page.html'), '<p>page</p>');
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
afterAll(() => {
|
|
16
|
-
fs.rmSync(tmpDir, { recursive: true });
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
describe('resolvePathname', () => {
|
|
20
|
-
test('resolves a file at the root', () => {
|
|
21
|
-
const result = resolvePathname(tmpDir, '/index.html');
|
|
22
|
-
expect(result).not.toBeNull();
|
|
23
|
-
expect(result!.filePath).toBe(path.join(tmpDir, 'index.html'));
|
|
24
|
-
expect(result!.mtime).toBeInstanceOf(Date);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
test('resolves a file in a subdirectory', () => {
|
|
28
|
-
const result = resolvePathname(tmpDir, '/sub/page.html');
|
|
29
|
-
expect(result).not.toBeNull();
|
|
30
|
-
expect(result!.filePath).toBe(path.join(tmpDir, 'sub', 'page.html'));
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test('returns null for nonexistent file', () => {
|
|
34
|
-
expect(resolvePathname(tmpDir, '/missing.html')).toBeNull();
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
test('returns null for directory', () => {
|
|
38
|
-
expect(resolvePathname(tmpDir, '/sub')).toBeNull();
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
test('returns null for path traversal', () => {
|
|
42
|
-
expect(resolvePathname(tmpDir, '/../etc/passwd')).toBeNull();
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
test('returns null for encoded path traversal', () => {
|
|
46
|
-
expect(resolvePathname(tmpDir, '/%2e%2e/etc/passwd')).toBeNull();
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test('returns null for invalid URL encoding', () => {
|
|
50
|
-
expect(resolvePathname(tmpDir, '/%ZZ')).toBeNull();
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
test('decodes percent-encoded path', () => {
|
|
54
|
-
fs.writeFileSync(path.join(tmpDir, 'spaced file.html'), 'ok');
|
|
55
|
-
const result = resolvePathname(tmpDir, '/spaced%20file.html');
|
|
56
|
-
expect(result).not.toBeNull();
|
|
57
|
-
expect(result!.filePath).toBe(path.join(tmpDir, 'spaced file.html'));
|
|
58
|
-
});
|
|
59
|
-
});
|
package/build/toc-plugin.test.ts
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test';
|
|
2
|
-
import { generateTocHtml, generateCodeTocHtml } from './toc-plugin';
|
|
3
|
-
import type { JavaTocEntry } from './types';
|
|
4
|
-
|
|
5
|
-
describe('generateTocHtml', () => {
|
|
6
|
-
test('returns empty string for empty array', () => {
|
|
7
|
-
expect(generateTocHtml([], [])).toBe('');
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
test('generates heading items with correct level and link', () => {
|
|
11
|
-
const html = generateTocHtml(
|
|
12
|
-
[{ kind: 'heading', level: '2', id: 'intro', innerHtml: 'Introduction' }],
|
|
13
|
-
[],
|
|
14
|
-
);
|
|
15
|
-
expect(html).toContain('<ol>');
|
|
16
|
-
expect(html).toContain('class="heading-item level2"');
|
|
17
|
-
expect(html).toContain('href="#intro"');
|
|
18
|
-
expect(html).toContain('Introduction');
|
|
19
|
-
expect(html).toContain('</ol>');
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
test('generates dinkus items', () => {
|
|
23
|
-
const html = generateTocHtml([{ kind: 'dinkus' }], []);
|
|
24
|
-
expect(html).toContain('class="dinkus-item"');
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
test('generates alert items at one level deeper than last heading', () => {
|
|
28
|
-
const html = generateTocHtml(
|
|
29
|
-
[
|
|
30
|
-
{ kind: 'heading', level: '2', id: 'sec', innerHtml: 'Section' },
|
|
31
|
-
{ kind: 'alert', type: 'warning', title: 'Caution' },
|
|
32
|
-
],
|
|
33
|
-
['caution'],
|
|
34
|
-
);
|
|
35
|
-
expect(html).toContain('class="alert-item level3 warning"');
|
|
36
|
-
expect(html).toContain('href="#caution"');
|
|
37
|
-
expect(html).toContain('Caution');
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
test('alert before any heading uses level 2 (lastHeadingLevel defaults to 1)', () => {
|
|
41
|
-
const html = generateTocHtml(
|
|
42
|
-
[{ kind: 'alert', type: 'note', title: 'Note' }],
|
|
43
|
-
['note'],
|
|
44
|
-
);
|
|
45
|
-
expect(html).toContain('class="alert-item level2 note"');
|
|
46
|
-
expect(html).toContain('href="#note"');
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test('alert links use deduplicated IDs', () => {
|
|
50
|
-
const html = generateTocHtml(
|
|
51
|
-
[
|
|
52
|
-
{ kind: 'alert', type: 'note', title: 'Note' },
|
|
53
|
-
{ kind: 'alert', type: 'note', title: 'Note' },
|
|
54
|
-
],
|
|
55
|
-
['note', 'note-2'],
|
|
56
|
-
);
|
|
57
|
-
expect(html).toContain('href="#note"');
|
|
58
|
-
expect(html).toContain('href="#note-2"');
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test('handles mixed items in order', () => {
|
|
62
|
-
const html = generateTocHtml(
|
|
63
|
-
[
|
|
64
|
-
{ kind: 'heading', level: '2', id: 'a', innerHtml: 'A' },
|
|
65
|
-
{ kind: 'dinkus' },
|
|
66
|
-
{ kind: 'heading', level: '3', id: 'b', innerHtml: 'B' },
|
|
67
|
-
{ kind: 'alert', type: 'note', title: 'FYI' },
|
|
68
|
-
],
|
|
69
|
-
['fyi'],
|
|
70
|
-
);
|
|
71
|
-
expect(html).toContain('level2');
|
|
72
|
-
expect(html).toContain('dinkus-item');
|
|
73
|
-
expect(html).toContain('level3');
|
|
74
|
-
expect(html).toContain('level4');
|
|
75
|
-
});
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
describe('generateCodeTocHtml', () => {
|
|
79
|
-
test('returns empty string for empty array', () => {
|
|
80
|
-
expect(generateCodeTocHtml([])).toBe('');
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
test('returns empty string for null/undefined', () => {
|
|
84
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
85
|
-
expect(generateCodeTocHtml(null as any)).toBe('');
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
test('generates grouped entries with labels', () => {
|
|
89
|
-
const items: JavaTocEntry[] = [
|
|
90
|
-
{ kind: 'field', label: 'x', name: 'x', line: 5 },
|
|
91
|
-
{ kind: 'method', label: 'getX()', name: 'getX()', line: 10 },
|
|
92
|
-
];
|
|
93
|
-
const html = generateCodeTocHtml(items);
|
|
94
|
-
expect(html).toContain('Fields');
|
|
95
|
-
expect(html).toContain('Methods');
|
|
96
|
-
expect(html).toContain('href="#L5"');
|
|
97
|
-
expect(html).toContain('href="#L10"');
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
test('groups items by kind in order of appearance', () => {
|
|
101
|
-
const items: JavaTocEntry[] = [
|
|
102
|
-
{ kind: 'method', label: 'foo()', name: 'foo()', line: 1 },
|
|
103
|
-
{ kind: 'constructor', label: 'Bar()', name: 'Bar()', line: 5 },
|
|
104
|
-
{ kind: 'method', label: 'baz()', name: 'baz()', line: 10 },
|
|
105
|
-
];
|
|
106
|
-
const html = generateCodeTocHtml(items);
|
|
107
|
-
const methodsPos = html.indexOf('Methods');
|
|
108
|
-
const constructorsPos = html.indexOf('Constructors');
|
|
109
|
-
expect(methodsPos).toBeLessThan(constructorsPos);
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
test('escapes HTML in names', () => {
|
|
113
|
-
const items: JavaTocEntry[] = [
|
|
114
|
-
{ kind: 'method', label: 'compare<T>()', name: 'compare<T>()', line: 1 },
|
|
115
|
-
];
|
|
116
|
-
const html = generateCodeTocHtml(items);
|
|
117
|
-
expect(html).toContain('compare<T>()');
|
|
118
|
-
expect(html).not.toContain('compare<T>()');
|
|
119
|
-
});
|
|
120
|
-
});
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
import os from 'os';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
import { getValidInternalTargets } from './content-files';
|
|
6
|
-
|
|
7
|
-
describe('getValidInternalTargets', () => {
|
|
8
|
-
let tempDir: string;
|
|
9
|
-
let originalCwd: string;
|
|
10
|
-
|
|
11
|
-
beforeEach(() => {
|
|
12
|
-
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tada-targets-test-'));
|
|
13
|
-
fs.mkdirSync(path.join(tempDir, 'content'));
|
|
14
|
-
fs.mkdirSync(path.join(tempDir, 'public'), { recursive: true });
|
|
15
|
-
originalCwd = process.cwd();
|
|
16
|
-
process.chdir(tempDir);
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
afterEach(() => {
|
|
20
|
-
process.chdir(originalCwd);
|
|
21
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
test('includes public files as valid targets', () => {
|
|
25
|
-
fs.writeFileSync(path.join(tempDir, 'public', 'test.txt'), '');
|
|
26
|
-
|
|
27
|
-
const contentDir = path.join(tempDir, 'content');
|
|
28
|
-
const targets = getValidInternalTargets(contentDir, [], [], false);
|
|
29
|
-
|
|
30
|
-
expect(targets.has('/test.txt')).toBe(true);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test('includes public index.html by exact path only', () => {
|
|
34
|
-
const coverageDir = path.join(tempDir, 'public', 'coverage');
|
|
35
|
-
fs.mkdirSync(coverageDir, { recursive: true });
|
|
36
|
-
fs.writeFileSync(path.join(coverageDir, 'index.html'), '');
|
|
37
|
-
|
|
38
|
-
const contentDir = path.join(tempDir, 'content');
|
|
39
|
-
const targets = getValidInternalTargets(contentDir, [], [], false);
|
|
40
|
-
|
|
41
|
-
expect(targets.has('/coverage/index.html')).toBe(true);
|
|
42
|
-
expect(targets.has('/coverage/')).toBe(false);
|
|
43
|
-
expect(targets.has('/coverage')).toBe(false);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
test('includes nested public files', () => {
|
|
47
|
-
const subDir = path.join(tempDir, 'public', 'assets');
|
|
48
|
-
fs.mkdirSync(subDir, { recursive: true });
|
|
49
|
-
fs.writeFileSync(path.join(subDir, 'style.css'), '');
|
|
50
|
-
|
|
51
|
-
const contentDir = path.join(tempDir, 'content');
|
|
52
|
-
const targets = getValidInternalTargets(contentDir, [], [], false);
|
|
53
|
-
|
|
54
|
-
expect(targets.has('/assets/style.css')).toBe(true);
|
|
55
|
-
});
|
|
56
|
-
});
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test';
|
|
2
|
-
import { parse, oklch } from 'culori';
|
|
3
|
-
import { deriveTheme, getTextOnColor } from './derive-theme';
|
|
4
|
-
|
|
5
|
-
function getL(hex: string): number {
|
|
6
|
-
return oklch(parse(hex))!.l;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
describe('deriveTheme', () => {
|
|
10
|
-
test('returns valid hex strings for all outputs', () => {
|
|
11
|
-
const result = deriveTheme('steelblue');
|
|
12
|
-
for (const value of Object.values(result)) {
|
|
13
|
-
expect(value).toMatch(/^#[0-9a-f]{3,6}$/);
|
|
14
|
-
}
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
test('light theme color lightness is in range', () => {
|
|
18
|
-
for (const color of [
|
|
19
|
-
'cornsilk',
|
|
20
|
-
'navy',
|
|
21
|
-
'steelblue',
|
|
22
|
-
'tomato',
|
|
23
|
-
'#000',
|
|
24
|
-
'#fff',
|
|
25
|
-
]) {
|
|
26
|
-
const { themeColorLight } = deriveTheme(color);
|
|
27
|
-
const l = getL(themeColorLight);
|
|
28
|
-
expect(l).toBeGreaterThanOrEqual(0.34);
|
|
29
|
-
expect(l).toBeLessThanOrEqual(0.63);
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test('dark theme color lightness is in range', () => {
|
|
34
|
-
for (const color of [
|
|
35
|
-
'cornsilk',
|
|
36
|
-
'navy',
|
|
37
|
-
'steelblue',
|
|
38
|
-
'tomato',
|
|
39
|
-
'#000',
|
|
40
|
-
'#fff',
|
|
41
|
-
]) {
|
|
42
|
-
const { themeColorDark } = deriveTheme(color);
|
|
43
|
-
const l = getL(themeColorDark);
|
|
44
|
-
expect(l).toBeGreaterThanOrEqual(0.54);
|
|
45
|
-
expect(l).toBeLessThanOrEqual(0.81);
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test('light text color lightness is in range', () => {
|
|
50
|
-
for (const color of ['cornsilk', 'navy', 'steelblue', 'tomato', 'lime']) {
|
|
51
|
-
const { themeColorTextLight } = deriveTheme(color);
|
|
52
|
-
const l = getL(themeColorTextLight);
|
|
53
|
-
expect(l).toBeGreaterThanOrEqual(0.34);
|
|
54
|
-
expect(l).toBeLessThanOrEqual(0.51);
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test('dark text color lightness is in range', () => {
|
|
59
|
-
for (const color of ['cornsilk', 'navy', 'steelblue', 'tomato', 'lime']) {
|
|
60
|
-
const { themeColorTextDark } = deriveTheme(color);
|
|
61
|
-
const l = getL(themeColorTextDark);
|
|
62
|
-
expect(l).toBeGreaterThanOrEqual(0.69);
|
|
63
|
-
expect(l).toBeLessThanOrEqual(0.83);
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
test('white text on dark colors', () => {
|
|
68
|
-
expect(deriveTheme('navy').textOnThemeLight).toBe('#fff');
|
|
69
|
-
expect(deriveTheme('#000').textOnThemeLight).toBe('#fff');
|
|
70
|
-
expect(deriveTheme('hsl(351 70% 40%)').textOnThemeLight).toBe('#fff');
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
test('black text on bright colors', () => {
|
|
74
|
-
expect(deriveTheme('lime').textOnThemeLight).toBe('#000');
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test('white text on saturated and mid-tone colors', () => {
|
|
78
|
-
expect(deriveTheme('steelblue').textOnThemeLight).toBe('#fff');
|
|
79
|
-
expect(deriveTheme('teal').textOnThemeLight).toBe('#fff');
|
|
80
|
-
expect(deriveTheme('tomato').textOnThemeLight).toBe('#fff');
|
|
81
|
-
expect(deriveTheme('cornsilk').textOnThemeLight).toBe('#fff');
|
|
82
|
-
expect(deriveTheme('hsl(195 70% 40%)').textOnThemeLight).toBe('#fff');
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
test('steelblue produces values close to original', () => {
|
|
86
|
-
const result = deriveTheme('steelblue');
|
|
87
|
-
// steelblue OKLCH L is ~0.588 which is within light theme range,
|
|
88
|
-
// so it should be unchanged (or very close)
|
|
89
|
-
expect(result.themeColorLight).toBe('#4682b4');
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
test('throws on invalid color', () => {
|
|
93
|
-
expect(() => deriveTheme('notacolor')).toThrow('Invalid color');
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
describe('getTextOnColor', () => {
|
|
98
|
-
test('returns white for dark colors', () => {
|
|
99
|
-
expect(getTextOnColor('navy')).toBe('#fff');
|
|
100
|
-
expect(getTextOnColor('#000')).toBe('#fff');
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
test('returns black for bright colors', () => {
|
|
104
|
-
expect(getTextOnColor('cornsilk')).toBe('#000');
|
|
105
|
-
expect(getTextOnColor('#fff')).toBe('#000');
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
test('throws on invalid color', () => {
|
|
109
|
-
expect(() => getTextOnColor('notacolor')).toThrow('Invalid color');
|
|
110
|
-
});
|
|
111
|
-
});
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test';
|
|
2
|
-
import {
|
|
3
|
-
getProcessedExtensions,
|
|
4
|
-
extensionIsMarkdown,
|
|
5
|
-
isLiterateJava,
|
|
6
|
-
isPartial,
|
|
7
|
-
} from './file-types';
|
|
8
|
-
|
|
9
|
-
describe('getProcessedExtensions', () => {
|
|
10
|
-
test('includes md, markdown, html, and custom extensions', () => {
|
|
11
|
-
expect(getProcessedExtensions(['java'])).toEqual([
|
|
12
|
-
'md',
|
|
13
|
-
'markdown',
|
|
14
|
-
'html',
|
|
15
|
-
'java',
|
|
16
|
-
]);
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
test('works with empty code extensions', () => {
|
|
20
|
-
expect(getProcessedExtensions([])).toEqual(['md', 'markdown', 'html']);
|
|
21
|
-
});
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
describe('extensionIsMarkdown', () => {
|
|
25
|
-
test('returns true for .md', () => {
|
|
26
|
-
expect(extensionIsMarkdown('.md')).toBe(true);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
test('returns true for .markdown', () => {
|
|
30
|
-
expect(extensionIsMarkdown('.markdown')).toBe(true);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test('returns false for .html', () => {
|
|
34
|
-
expect(extensionIsMarkdown('.html')).toBe(false);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
test('returns false for .txt', () => {
|
|
38
|
-
expect(extensionIsMarkdown('.txt')).toBe(false);
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
describe('isPartial', () => {
|
|
43
|
-
test('returns true for files starting with _', () => {
|
|
44
|
-
expect(isPartial('_foo.md')).toBe(true);
|
|
45
|
-
expect(isPartial('/content/subdir/_bar.html')).toBe(true);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
test('returns false for regular files', () => {
|
|
49
|
-
expect(isPartial('index.md')).toBe(false);
|
|
50
|
-
expect(isPartial('/content/page.html')).toBe(false);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
test('handles paths with directories', () => {
|
|
54
|
-
expect(isPartial('/content/lectures/02/_pr1.md')).toBe(true);
|
|
55
|
-
expect(isPartial('/content/lectures/02/subdir/_foobar.html')).toBe(true);
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
describe('isLiterateJava', () => {
|
|
60
|
-
test('returns true for .java.md files', () => {
|
|
61
|
-
expect(isLiterateJava('Example.java.md')).toBe(true);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
test('case insensitive', () => {
|
|
65
|
-
expect(isLiterateJava('Example.JAVA.MD')).toBe(true);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test('returns false for plain .md files', () => {
|
|
69
|
-
expect(isLiterateJava('readme.md')).toBe(false);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
test('returns false for .java files', () => {
|
|
73
|
-
expect(isLiterateJava('Example.java')).toBe(false);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test('handles paths with directories', () => {
|
|
77
|
-
expect(isLiterateJava('/content/code/Example.java.md')).toBe(true);
|
|
78
|
-
});
|
|
79
|
-
});
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test';
|
|
2
|
-
import { parseFrontMatterAndContent, parseFrontMatter } from './front-matter';
|
|
3
|
-
|
|
4
|
-
describe('parseFrontMatter', () => {
|
|
5
|
-
test('parses markdown front matter separated by blank line', () => {
|
|
6
|
-
const raw = 'title: Hello\nauthor: Alice\n\nBody content here.';
|
|
7
|
-
const result = parseFrontMatter(raw, '.md');
|
|
8
|
-
expect(result.frontMatter).toBe('title: Hello\nauthor: Alice');
|
|
9
|
-
expect(result.content).toBe('\nBody content here.');
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
test('handles .markdown extension', () => {
|
|
13
|
-
const raw = 'title: Test\n\nContent.';
|
|
14
|
-
const result = parseFrontMatter(raw, '.markdown');
|
|
15
|
-
expect(result.frontMatter).toBe('title: Test');
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
test('handles .html extension', () => {
|
|
19
|
-
const raw = 'title: Page\nlayout: full\n\n<h1>Hi</h1>';
|
|
20
|
-
const result = parseFrontMatter(raw, '.html');
|
|
21
|
-
expect(result.frontMatter).toBe('title: Page\nlayout: full');
|
|
22
|
-
expect(result.content).toBe('\n<h1>Hi</h1>');
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
test('returns null frontMatter for unknown extensions', () => {
|
|
26
|
-
const raw = 'title: Nope\n\nContent';
|
|
27
|
-
const result = parseFrontMatter(raw, '.txt');
|
|
28
|
-
expect(result.frontMatter).toBe(null);
|
|
29
|
-
expect(result.content).toBe(raw);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
test('returns null frontMatter when content starts with blank line', () => {
|
|
33
|
-
const raw = '\nNo front matter here.';
|
|
34
|
-
const result = parseFrontMatter(raw, '.md');
|
|
35
|
-
expect(result.frontMatter).toBe(null);
|
|
36
|
-
expect(result.content).toBe(raw);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
test('handles YAML multiline pipe syntax', () => {
|
|
40
|
-
const raw =
|
|
41
|
-
'title: Hello\ndescription: |\n This is a\n multiline value\n\nBody.';
|
|
42
|
-
const result = parseFrontMatter(raw, '.md');
|
|
43
|
-
expect(result.frontMatter).toContain('description: |');
|
|
44
|
-
expect(result.frontMatter).toContain(' This is a');
|
|
45
|
-
expect(result.frontMatter).toContain(' multiline value');
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
describe('parseFrontMatterAndContent', () => {
|
|
50
|
-
test('returns parsed pageVariables and content', () => {
|
|
51
|
-
const raw = 'title: Hello World\nauthor: Bob\n\n# Main Content';
|
|
52
|
-
const result = parseFrontMatterAndContent(raw, '.md');
|
|
53
|
-
expect(result.pageVariables.title).toBe('Hello World');
|
|
54
|
-
expect(result.pageVariables.author).toBe('Bob');
|
|
55
|
-
expect(result.content).toContain('# Main Content');
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test('handles content with no front matter fields', () => {
|
|
59
|
-
const raw = '\nJust content, no front matter.';
|
|
60
|
-
const result = parseFrontMatterAndContent(raw, '.md');
|
|
61
|
-
expect(result.pageVariables).toEqual({});
|
|
62
|
-
expect(result.content).toBe(raw);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
test('handles boolean and numeric front matter values', () => {
|
|
66
|
-
const raw = 'toc: true\norder: 5\n\nContent.';
|
|
67
|
-
const result = parseFrontMatterAndContent(raw, '.md');
|
|
68
|
-
expect(result.pageVariables.toc).toBe(true);
|
|
69
|
-
expect(result.pageVariables.order).toBe(5);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
test('handles Windows-style line endings', () => {
|
|
73
|
-
const raw = 'title: Hello\r\n\r\nBody content.';
|
|
74
|
-
const result = parseFrontMatterAndContent(raw, '.md');
|
|
75
|
-
expect(result.pageVariables.title).toBe('Hello');
|
|
76
|
-
});
|
|
77
|
-
});
|