toiljs 0.0.11 → 0.0.14
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/README.md +3 -1
- package/build/cli/.tsbuildinfo +1 -1
- package/build/cli/configure.js +10 -4
- package/build/cli/create.js +58 -30
- package/build/cli/diagnostics.d.ts +55 -0
- package/build/cli/diagnostics.js +333 -0
- package/build/cli/doctor.d.ts +6 -0
- package/build/cli/doctor.js +249 -0
- package/build/cli/index.js +26 -0
- package/build/cli/proc.d.ts +5 -0
- package/build/cli/proc.js +20 -0
- package/build/cli/ui.d.ts +1 -0
- package/build/cli/ui.js +1 -0
- package/build/cli/update.d.ts +7 -0
- package/build/cli/update.js +117 -0
- package/build/cli/updates.d.ts +10 -0
- package/build/cli/updates.js +45 -0
- package/build/client/.tsbuildinfo +1 -1
- package/build/client/dev/error-overlay.js +1 -1
- package/build/client/head/metadata.js +3 -1
- package/build/client/index.d.ts +5 -1
- package/build/client/index.js +2 -0
- package/build/client/navigation/navigation.js +1 -1
- package/build/client/routing/Router.js +2 -2
- package/build/client/search/search.d.ts +26 -0
- package/build/client/search/search.js +101 -0
- package/build/client/search/use-page-search.d.ts +8 -0
- package/build/client/search/use-page-search.js +21 -0
- package/build/compiler/.tsbuildinfo +1 -1
- package/build/compiler/generate.js +33 -24
- package/build/compiler/index.d.ts +2 -0
- package/build/compiler/index.js +1 -0
- package/build/compiler/pages.d.ts +8 -0
- package/build/compiler/pages.js +37 -0
- package/build/compiler/plugin.js +3 -1
- package/build/compiler/prerender.d.ts +1 -0
- package/build/compiler/prerender.js +11 -5
- package/build/compiler/seo.js +10 -3
- package/build/io/.tsbuildinfo +1 -1
- package/examples/basic/client/components/Header.tsx +43 -41
- package/examples/basic/client/components/HoneycombBackground.tsx +223 -230
- package/examples/basic/client/public/index.html +18 -16
- package/examples/basic/client/routes/(legal)/privacy.tsx +18 -19
- package/examples/basic/client/routes/(legal)/terms.tsx +15 -16
- package/examples/basic/client/routes/about.tsx +21 -22
- package/examples/basic/client/routes/blog/[id].tsx +26 -18
- package/examples/basic/client/routes/features/actions.tsx +67 -67
- package/examples/basic/client/routes/features/error/index.tsx +27 -27
- package/examples/basic/client/routes/features/head.tsx +38 -38
- package/examples/basic/client/routes/features/index.tsx +83 -75
- package/examples/basic/client/routes/features/realtime.tsx +34 -32
- package/examples/basic/client/routes/features/script.tsx +31 -31
- package/examples/basic/client/routes/features/seo.tsx +39 -39
- package/examples/basic/client/routes/features/template/index.tsx +20 -20
- package/examples/basic/client/routes/features/template/template.tsx +16 -18
- package/examples/basic/client/routes/gallery/@modal/(.)photo/[id].tsx +23 -23
- package/examples/basic/client/routes/gallery/index.tsx +42 -42
- package/examples/basic/client/routes/gallery/photo/[id].tsx +18 -18
- package/examples/basic/client/routes/get-started.tsx +157 -84
- package/examples/basic/client/routes/index.tsx +137 -96
- package/examples/basic/client/routes/loader-demo/index.tsx +59 -52
- package/examples/basic/client/routes/search.tsx +61 -0
- package/examples/basic/client/routes/test.tsx +7 -8
- package/examples/basic/client/styles/main.css +624 -552
- package/package.json +2 -2
- package/presets/eslint.js +10 -3
- package/src/cli/configure.ts +363 -353
- package/src/cli/create.ts +563 -530
- package/src/cli/diagnostics.ts +421 -0
- package/src/cli/doctor.ts +318 -0
- package/src/cli/features.ts +166 -160
- package/src/cli/index.ts +242 -211
- package/src/cli/proc.ts +30 -0
- package/src/cli/ui.ts +111 -103
- package/src/cli/update.ts +150 -0
- package/src/cli/updates.ts +69 -0
- package/src/client/components/Image.tsx +91 -89
- package/src/client/dev/error-overlay.tsx +193 -197
- package/src/client/head/metadata.ts +94 -92
- package/src/client/index.ts +79 -64
- package/src/client/navigation/Link.tsx +94 -100
- package/src/client/navigation/navigation.ts +215 -218
- package/src/client/routing/Router.tsx +210 -193
- package/src/client/routing/hooks.ts +110 -114
- package/src/client/routing/lazy.ts +77 -81
- package/src/client/search/search.ts +189 -0
- package/src/client/search/use-page-search.ts +73 -0
- package/src/compiler/config.ts +173 -171
- package/src/compiler/fonts.ts +89 -87
- package/src/compiler/generate.ts +45 -27
- package/src/compiler/image-report.ts +88 -85
- package/src/compiler/index.ts +2 -0
- package/src/compiler/pages.ts +70 -0
- package/src/compiler/plugin.ts +51 -47
- package/src/compiler/prerender.ts +152 -130
- package/src/compiler/routes.ts +132 -131
- package/src/compiler/seo.ts +381 -356
- package/src/compiler/vite.ts +155 -145
- package/src/io/FastSet.ts +99 -96
- package/test/configure.test.ts +94 -90
- package/test/doctor.test.ts +140 -0
- package/test/dom/Image.test.tsx +73 -46
- package/test/dom/Script.test.tsx +48 -45
- package/test/dom/action.test.tsx +146 -129
- package/test/dom/error-overlay.test.tsx +1 -1
- package/test/dom/loader.test.tsx +2 -2
- package/test/dom/revalidate.test.tsx +1 -1
- package/test/dom/route-head.test.tsx +1 -2
- package/test/dom/router-loading.test.tsx +1 -1
- package/test/dom/slot.test.tsx +131 -109
- package/test/dom/view-transitions.test.tsx +53 -51
- package/test/features.test.ts +149 -142
- package/test/fonts.test.ts +28 -26
- package/test/head.test.ts +45 -35
- package/test/metadata.test.ts +42 -41
- package/test/pages.test.ts +105 -0
- package/test/prerender.test.ts +54 -46
- package/test/search.test.ts +114 -0
- package/test/seo.test.ts +30 -8
- package/test/update.test.ts +44 -0
|
@@ -1,51 +1,53 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
|
-
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
-
|
|
4
|
-
import { navigate, setViewTransitions } from '../../src/client/navigation/navigation';
|
|
5
|
-
|
|
6
|
-
interface VTDoc {
|
|
7
|
-
startViewTransition?: (cb: () => void) => unknown;
|
|
8
|
-
}
|
|
9
|
-
const doc = document as Document & VTDoc;
|
|
10
|
-
|
|
11
|
-
afterEach(() => {
|
|
12
|
-
setViewTransitions(false);
|
|
13
|
-
delete doc.startViewTransition;
|
|
14
|
-
vi.restoreAllMocks();
|
|
15
|
-
window.history.replaceState({}, '', '/');
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
describe('view transitions', () => {
|
|
19
|
-
function stubReducedMotion(matches: boolean): void {
|
|
20
|
-
window.matchMedia = vi
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import { navigate, setViewTransitions } from '../../src/client/navigation/navigation';
|
|
5
|
+
|
|
6
|
+
interface VTDoc {
|
|
7
|
+
startViewTransition?: (cb: () => void) => unknown;
|
|
8
|
+
}
|
|
9
|
+
const doc = document as Document & VTDoc;
|
|
10
|
+
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
setViewTransitions(false);
|
|
13
|
+
delete doc.startViewTransition;
|
|
14
|
+
vi.restoreAllMocks();
|
|
15
|
+
window.history.replaceState({}, '', '/');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe('view transitions', () => {
|
|
19
|
+
function stubReducedMotion(matches: boolean): void {
|
|
20
|
+
window.matchMedia = vi
|
|
21
|
+
.fn()
|
|
22
|
+
.mockReturnValue({ matches }) as unknown as typeof window.matchMedia;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
it('wraps navigation in startViewTransition when enabled and supported', () => {
|
|
26
|
+
const vt = vi.fn((cb: () => void) => {
|
|
27
|
+
cb();
|
|
28
|
+
});
|
|
29
|
+
doc.startViewTransition = vt;
|
|
30
|
+
stubReducedMotion(false);
|
|
31
|
+
setViewTransitions(true);
|
|
32
|
+
navigate('/a');
|
|
33
|
+
expect(vt).toHaveBeenCalledOnce();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('skips the view transition under prefers-reduced-motion', () => {
|
|
37
|
+
const vt = vi.fn();
|
|
38
|
+
doc.startViewTransition = vt;
|
|
39
|
+
stubReducedMotion(true);
|
|
40
|
+
setViewTransitions(true);
|
|
41
|
+
navigate('/b');
|
|
42
|
+
expect(vt).not.toHaveBeenCalled();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('does not use view transitions when disabled', () => {
|
|
46
|
+
const vt = vi.fn();
|
|
47
|
+
doc.startViewTransition = vt;
|
|
48
|
+
stubReducedMotion(false);
|
|
49
|
+
setViewTransitions(false);
|
|
50
|
+
navigate('/c');
|
|
51
|
+
expect(vt).not.toHaveBeenCalled();
|
|
52
|
+
});
|
|
53
|
+
});
|
package/test/features.test.ts
CHANGED
|
@@ -1,142 +1,149 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
defaultConfigSource,
|
|
5
|
-
detectPreprocessor,
|
|
6
|
-
detectTailwind,
|
|
7
|
-
packageDiff,
|
|
8
|
-
preprocessorForExt,
|
|
9
|
-
requiredPackages,
|
|
10
|
-
setConfigImages,
|
|
11
|
-
setStyleImports,
|
|
12
|
-
styleEntry,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
} from '../src/cli/features';
|
|
16
|
-
|
|
17
|
-
const CSS: StyleFeatures = { preprocessor: 'css', tailwind: false };
|
|
18
|
-
const SASS_TW: StyleFeatures = { preprocessor: 'sass', tailwind: true };
|
|
19
|
-
|
|
20
|
-
describe('styleEntry / preprocessorForExt', () => {
|
|
21
|
-
it('maps preprocessors to stylesheet paths', () => {
|
|
22
|
-
expect(styleEntry('css')).toBe('styles/main.css');
|
|
23
|
-
expect(styleEntry('sass')).toBe('styles/main.scss');
|
|
24
|
-
expect(styleEntry('less')).toBe('styles/main.less');
|
|
25
|
-
expect(styleEntry('stylus')).toBe('styles/main.styl');
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('reverses extensions back to preprocessors', () => {
|
|
29
|
-
expect(preprocessorForExt('scss')).toBe('sass');
|
|
30
|
-
expect(preprocessorForExt('.sass')).toBe('sass');
|
|
31
|
-
expect(preprocessorForExt('less')).toBe('less');
|
|
32
|
-
expect(preprocessorForExt('styl')).toBe('stylus');
|
|
33
|
-
expect(preprocessorForExt('css')).toBe('css');
|
|
34
|
-
expect(preprocessorForExt('txt')).toBeNull();
|
|
35
|
-
});
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
describe('requiredPackages / packageDiff', () => {
|
|
39
|
-
it('lists packages for a feature set', () => {
|
|
40
|
-
expect(requiredPackages(CSS)).toEqual([]);
|
|
41
|
-
expect(requiredPackages({ preprocessor: 'sass', tailwind: false })).toEqual(['sass']);
|
|
42
|
-
expect(requiredPackages({ preprocessor: 'css', tailwind: true })).toEqual([
|
|
43
|
-
'tailwindcss',
|
|
44
|
-
'@tailwindcss/vite',
|
|
45
|
-
]);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('diffs add/remove between two setups', () => {
|
|
49
|
-
expect(packageDiff(CSS, SASS_TW)).toEqual({
|
|
50
|
-
add: ['sass', 'tailwindcss', '@tailwindcss/vite'],
|
|
51
|
-
remove: [],
|
|
52
|
-
});
|
|
53
|
-
expect(packageDiff(SASS_TW, CSS)).toEqual({
|
|
54
|
-
add: [],
|
|
55
|
-
remove: ['sass', 'tailwindcss', '@tailwindcss/vite'],
|
|
56
|
-
});
|
|
57
|
-
expect(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
'',
|
|
78
|
-
'
|
|
79
|
-
'',
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
expect(out).toContain("
|
|
87
|
-
expect(out).toContain('
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
expect(
|
|
111
|
-
expect(
|
|
112
|
-
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
});
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
defaultConfigSource,
|
|
5
|
+
detectPreprocessor,
|
|
6
|
+
detectTailwind,
|
|
7
|
+
packageDiff,
|
|
8
|
+
preprocessorForExt,
|
|
9
|
+
requiredPackages,
|
|
10
|
+
setConfigImages,
|
|
11
|
+
setStyleImports,
|
|
12
|
+
styleEntry,
|
|
13
|
+
type StyleFeatures,
|
|
14
|
+
styleImportLines,
|
|
15
|
+
} from '../src/cli/features';
|
|
16
|
+
|
|
17
|
+
const CSS: StyleFeatures = { preprocessor: 'css', tailwind: false };
|
|
18
|
+
const SASS_TW: StyleFeatures = { preprocessor: 'sass', tailwind: true };
|
|
19
|
+
|
|
20
|
+
describe('styleEntry / preprocessorForExt', () => {
|
|
21
|
+
it('maps preprocessors to stylesheet paths', () => {
|
|
22
|
+
expect(styleEntry('css')).toBe('styles/main.css');
|
|
23
|
+
expect(styleEntry('sass')).toBe('styles/main.scss');
|
|
24
|
+
expect(styleEntry('less')).toBe('styles/main.less');
|
|
25
|
+
expect(styleEntry('stylus')).toBe('styles/main.styl');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('reverses extensions back to preprocessors', () => {
|
|
29
|
+
expect(preprocessorForExt('scss')).toBe('sass');
|
|
30
|
+
expect(preprocessorForExt('.sass')).toBe('sass');
|
|
31
|
+
expect(preprocessorForExt('less')).toBe('less');
|
|
32
|
+
expect(preprocessorForExt('styl')).toBe('stylus');
|
|
33
|
+
expect(preprocessorForExt('css')).toBe('css');
|
|
34
|
+
expect(preprocessorForExt('txt')).toBeNull();
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('requiredPackages / packageDiff', () => {
|
|
39
|
+
it('lists packages for a feature set', () => {
|
|
40
|
+
expect(requiredPackages(CSS)).toEqual([]);
|
|
41
|
+
expect(requiredPackages({ preprocessor: 'sass', tailwind: false })).toEqual(['sass']);
|
|
42
|
+
expect(requiredPackages({ preprocessor: 'css', tailwind: true })).toEqual([
|
|
43
|
+
'tailwindcss',
|
|
44
|
+
'@tailwindcss/vite',
|
|
45
|
+
]);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('diffs add/remove between two setups', () => {
|
|
49
|
+
expect(packageDiff(CSS, SASS_TW)).toEqual({
|
|
50
|
+
add: ['sass', 'tailwindcss', '@tailwindcss/vite'],
|
|
51
|
+
remove: [],
|
|
52
|
+
});
|
|
53
|
+
expect(packageDiff(SASS_TW, CSS)).toEqual({
|
|
54
|
+
add: [],
|
|
55
|
+
remove: ['sass', 'tailwindcss', '@tailwindcss/vite'],
|
|
56
|
+
});
|
|
57
|
+
expect(
|
|
58
|
+
packageDiff(
|
|
59
|
+
{ preprocessor: 'sass', tailwind: false },
|
|
60
|
+
{ preprocessor: 'less', tailwind: false },
|
|
61
|
+
),
|
|
62
|
+
).toEqual({ add: ['less'], remove: ['sass'] });
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('styleImportLines / setStyleImports', () => {
|
|
67
|
+
it('orders Tailwind before the main stylesheet', () => {
|
|
68
|
+
expect(styleImportLines(CSS)).toEqual(["import './styles/main.css';"]);
|
|
69
|
+
expect(styleImportLines(SASS_TW)).toEqual([
|
|
70
|
+
"import './styles/tailwind.css';",
|
|
71
|
+
"import './styles/main.scss';",
|
|
72
|
+
]);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('rewrites the app entry imports, preserving the rest', () => {
|
|
76
|
+
const src = [
|
|
77
|
+
"import { routes, layout, notFound } from 'toiljs/routes';",
|
|
78
|
+
'',
|
|
79
|
+
"import './styles/main.css';",
|
|
80
|
+
'',
|
|
81
|
+
'Toil.mount(routes, layout, notFound);',
|
|
82
|
+
'',
|
|
83
|
+
].join('\n');
|
|
84
|
+
|
|
85
|
+
const out = setStyleImports(src, SASS_TW);
|
|
86
|
+
expect(out).toContain("import './styles/tailwind.css';");
|
|
87
|
+
expect(out).toContain("import './styles/main.scss';");
|
|
88
|
+
expect(out).not.toContain("import './styles/main.css';");
|
|
89
|
+
expect(out).toContain("from 'toiljs/routes'");
|
|
90
|
+
expect(out).toContain('Toil.mount(routes, layout, notFound);');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('round-trips back to plain CSS (drops Tailwind import)', () => {
|
|
94
|
+
const src = [
|
|
95
|
+
"import { routes, layout, notFound } from 'toiljs/routes';",
|
|
96
|
+
"import './styles/tailwind.css';",
|
|
97
|
+
"import './styles/main.scss';",
|
|
98
|
+
'Toil.mount(routes, layout, notFound);',
|
|
99
|
+
].join('\n');
|
|
100
|
+
|
|
101
|
+
const out = setStyleImports(src, CSS);
|
|
102
|
+
expect(out).toContain("import './styles/main.css';");
|
|
103
|
+
expect(out).not.toContain('tailwind.css');
|
|
104
|
+
expect(out).not.toContain('main.scss');
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe('detect from dependencies', () => {
|
|
109
|
+
it('finds the active preprocessor and Tailwind', () => {
|
|
110
|
+
expect(detectPreprocessor({ sass: '^1' })).toBe('sass');
|
|
111
|
+
expect(detectPreprocessor({ less: '^4' })).toBe('less');
|
|
112
|
+
expect(detectPreprocessor({})).toBe('css');
|
|
113
|
+
expect(detectTailwind({ '@tailwindcss/vite': '^4' })).toBe(true);
|
|
114
|
+
expect(detectTailwind({ react: '^19' })).toBe(false);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe('setConfigImages / defaultConfigSource', () => {
|
|
119
|
+
it('flips an existing images flag', () => {
|
|
120
|
+
const src =
|
|
121
|
+
'export default defineConfig({\n client: {\n images: true,\n },\n});\n';
|
|
122
|
+
expect(setConfigImages(src, false)).toContain('images: false');
|
|
123
|
+
expect(setConfigImages(src, false)).not.toContain('images: true');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('adds images to an existing client block', () => {
|
|
127
|
+
const out = setConfigImages(
|
|
128
|
+
'export default defineConfig({ client: { base: "/" } });',
|
|
129
|
+
false,
|
|
130
|
+
);
|
|
131
|
+
expect(out).toContain('images: false');
|
|
132
|
+
expect(out).toContain('base: "/"');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('adds a client block to a bare config', () => {
|
|
136
|
+
const out = setConfigImages('export default defineConfig({});', false);
|
|
137
|
+
expect(out).toContain('client: { images: false }');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('returns null when the shape is unrecognized', () => {
|
|
141
|
+
expect(setConfigImages('const x = 1;', false)).toBeNull();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('round-trips through defaultConfigSource', () => {
|
|
145
|
+
const src = defaultConfigSource(false);
|
|
146
|
+
expect(src).toContain('images: false');
|
|
147
|
+
expect(setConfigImages(src, true)).toContain('images: true');
|
|
148
|
+
});
|
|
149
|
+
});
|
package/test/fonts.test.ts
CHANGED
|
@@ -1,26 +1,28 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import { fontPreloadTags } from '../src/compiler/fonts';
|
|
4
|
-
|
|
5
|
-
describe('fontPreloadTags', () => {
|
|
6
|
-
it('builds a crossorigin preload link per font, skipping non-fonts', () => {
|
|
7
|
-
const tags = fontPreloadTags(['fonts/a-abc.woff2', 'assets/x-1.js', 'fonts/b.ttf'], '/');
|
|
8
|
-
expect(tags).toHaveLength(2);
|
|
9
|
-
expect(tags[0]).toEqual({
|
|
10
|
-
tag: 'link',
|
|
11
|
-
attrs: {
|
|
12
|
-
rel: 'preload',
|
|
13
|
-
as: 'font',
|
|
14
|
-
type: 'font/woff2',
|
|
15
|
-
href: '/fonts/a-abc.woff2',
|
|
16
|
-
crossorigin: '',
|
|
17
|
-
},
|
|
18
|
-
injectTo: 'head',
|
|
19
|
-
});
|
|
20
|
-
expect(tags[1].attrs?.type).toBe('font/ttf');
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('respects a non-root base path', () => {
|
|
24
|
-
expect(fontPreloadTags(['fonts/a.woff2'], '/app/')[0].attrs?.href).toBe(
|
|
25
|
-
|
|
26
|
-
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { fontPreloadTags } from '../src/compiler/fonts';
|
|
4
|
+
|
|
5
|
+
describe('fontPreloadTags', () => {
|
|
6
|
+
it('builds a crossorigin preload link per font, skipping non-fonts', () => {
|
|
7
|
+
const tags = fontPreloadTags(['fonts/a-abc.woff2', 'assets/x-1.js', 'fonts/b.ttf'], '/');
|
|
8
|
+
expect(tags).toHaveLength(2);
|
|
9
|
+
expect(tags[0]).toEqual({
|
|
10
|
+
tag: 'link',
|
|
11
|
+
attrs: {
|
|
12
|
+
rel: 'preload',
|
|
13
|
+
as: 'font',
|
|
14
|
+
type: 'font/woff2',
|
|
15
|
+
href: '/fonts/a-abc.woff2',
|
|
16
|
+
crossorigin: '',
|
|
17
|
+
},
|
|
18
|
+
injectTo: 'head',
|
|
19
|
+
});
|
|
20
|
+
expect(tags[1].attrs?.type).toBe('font/ttf');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('respects a non-root base path', () => {
|
|
24
|
+
expect(fontPreloadTags(['fonts/a.woff2'], '/app/')[0].attrs?.href).toBe(
|
|
25
|
+
'/app/fonts/a.woff2',
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
});
|
package/test/head.test.ts
CHANGED
|
@@ -1,35 +1,45 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import { mergeHead } from '../src/client/head/head';
|
|
4
|
-
|
|
5
|
-
describe('mergeHead', () => {
|
|
6
|
-
it('takes the last title and applies a titleTemplate', () => {
|
|
7
|
-
expect(mergeHead([{ title: 'Home' }]).title).toBe('Home');
|
|
8
|
-
expect(mergeHead([{ title: 'A' }, { title: 'B' }]).title).toBe('B');
|
|
9
|
-
expect(mergeHead([{ titleTemplate: '%s · toiljs' }, { title: 'About' }]).title).toBe(
|
|
10
|
-
'About · toiljs',
|
|
11
|
-
);
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it('leaves title undefined when nothing sets it', () => {
|
|
15
|
-
expect(mergeHead([{ meta: [{ name: 'x', content: 'y' }] }]).title).toBeUndefined();
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it('dedupes meta by name/property, last wins', () => {
|
|
19
|
-
const resolved = mergeHead([
|
|
20
|
-
{ meta: [{ name: 'description', content: 'old' }] },
|
|
21
|
-
{
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { mergeHead } from '../src/client/head/head';
|
|
4
|
+
|
|
5
|
+
describe('mergeHead', () => {
|
|
6
|
+
it('takes the last title and applies a titleTemplate', () => {
|
|
7
|
+
expect(mergeHead([{ title: 'Home' }]).title).toBe('Home');
|
|
8
|
+
expect(mergeHead([{ title: 'A' }, { title: 'B' }]).title).toBe('B');
|
|
9
|
+
expect(mergeHead([{ titleTemplate: '%s · toiljs' }, { title: 'About' }]).title).toBe(
|
|
10
|
+
'About · toiljs',
|
|
11
|
+
);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('leaves title undefined when nothing sets it', () => {
|
|
15
|
+
expect(mergeHead([{ meta: [{ name: 'x', content: 'y' }] }]).title).toBeUndefined();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('dedupes meta by name/property, last wins', () => {
|
|
19
|
+
const resolved = mergeHead([
|
|
20
|
+
{ meta: [{ name: 'description', content: 'old' }] },
|
|
21
|
+
{
|
|
22
|
+
meta: [
|
|
23
|
+
{ name: 'description', content: 'new' },
|
|
24
|
+
{ property: 'og:title', content: 'T' },
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
]);
|
|
28
|
+
expect(resolved.meta).toHaveLength(2);
|
|
29
|
+
expect(resolved.meta.find((m) => m.name === 'description')?.content).toBe('new');
|
|
30
|
+
expect(resolved.meta.find((m) => m.property === 'og:title')?.content).toBe('T');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('dedupes links by rel+href', () => {
|
|
34
|
+
const resolved = mergeHead([
|
|
35
|
+
{ link: [{ rel: 'icon', href: '/a.svg' }] },
|
|
36
|
+
{
|
|
37
|
+
link: [
|
|
38
|
+
{ rel: 'icon', href: '/a.svg' },
|
|
39
|
+
{ rel: 'canonical', href: '/x' },
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
]);
|
|
43
|
+
expect(resolved.link).toHaveLength(2);
|
|
44
|
+
});
|
|
45
|
+
});
|
package/test/metadata.test.ts
CHANGED
|
@@ -1,41 +1,42 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import { resolveMetadata } from '../src/client/head/metadata';
|
|
4
|
-
|
|
5
|
-
describe('resolveMetadata', () => {
|
|
6
|
-
it('expands convenience fields into meta/link tags', () => {
|
|
7
|
-
const head = resolveMetadata({
|
|
8
|
-
title: 'About',
|
|
9
|
-
titleTemplate: '%s · toiljs',
|
|
10
|
-
description: 'desc',
|
|
11
|
-
keywords: ['a', 'b'],
|
|
12
|
-
robots: 'noindex',
|
|
13
|
-
themeColor: '#000',
|
|
14
|
-
canonical: 'https://x.test/about',
|
|
15
|
-
openGraph: { title: 'OG', type: 'website', image: 'https://x.test/og.png' },
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
expect(head.title).toBe('About');
|
|
19
|
-
expect(head.titleTemplate).toBe('%s · toiljs');
|
|
20
|
-
const byName = (name: string) => head.meta?.find((m) => m.name === name)?.content;
|
|
21
|
-
const byProp = (property: string) =>
|
|
22
|
-
|
|
23
|
-
expect(byName('
|
|
24
|
-
expect(byName('
|
|
25
|
-
expect(byName('
|
|
26
|
-
expect(
|
|
27
|
-
expect(byProp('og:
|
|
28
|
-
expect(byProp('og:
|
|
29
|
-
expect(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
expect(head.
|
|
40
|
-
|
|
41
|
-
});
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { resolveMetadata } from '../src/client/head/metadata';
|
|
4
|
+
|
|
5
|
+
describe('resolveMetadata', () => {
|
|
6
|
+
it('expands convenience fields into meta/link tags', () => {
|
|
7
|
+
const head = resolveMetadata({
|
|
8
|
+
title: 'About',
|
|
9
|
+
titleTemplate: '%s · toiljs',
|
|
10
|
+
description: 'desc',
|
|
11
|
+
keywords: ['a', 'b'],
|
|
12
|
+
robots: 'noindex',
|
|
13
|
+
themeColor: '#000',
|
|
14
|
+
canonical: 'https://x.test/about',
|
|
15
|
+
openGraph: { title: 'OG', type: 'website', image: 'https://x.test/og.png' },
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
expect(head.title).toBe('About');
|
|
19
|
+
expect(head.titleTemplate).toBe('%s · toiljs');
|
|
20
|
+
const byName = (name: string) => head.meta?.find((m) => m.name === name)?.content;
|
|
21
|
+
const byProp = (property: string) =>
|
|
22
|
+
head.meta?.find((m) => m.property === property)?.content;
|
|
23
|
+
expect(byName('description')).toBe('desc');
|
|
24
|
+
expect(byName('keywords')).toBe('a, b');
|
|
25
|
+
expect(byName('robots')).toBe('noindex');
|
|
26
|
+
expect(byName('theme-color')).toBe('#000');
|
|
27
|
+
expect(byProp('og:title')).toBe('OG');
|
|
28
|
+
expect(byProp('og:type')).toBe('website');
|
|
29
|
+
expect(byProp('og:image')).toBe('https://x.test/og.png');
|
|
30
|
+
expect(head.link?.find((l) => l.rel === 'canonical')?.href).toBe('https://x.test/about');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('passes through raw meta/link and omits unset fields', () => {
|
|
34
|
+
const head = resolveMetadata({
|
|
35
|
+
title: 'X',
|
|
36
|
+
meta: [{ name: 'author', content: 'me' }],
|
|
37
|
+
link: [{ rel: 'alternate', href: '/rss' }],
|
|
38
|
+
});
|
|
39
|
+
expect(head.meta).toEqual([{ name: 'author', content: 'me' }]);
|
|
40
|
+
expect(head.link).toEqual([{ rel: 'alternate', href: '/rss' }]);
|
|
41
|
+
});
|
|
42
|
+
});
|