toiljs 0.0.80 → 0.0.82

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.
@@ -0,0 +1,132 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+
5
+ import ts from 'typescript';
6
+ import { afterEach, describe, expect, it } from 'vitest';
7
+
8
+ import { exportsTrue } from '../src/compiler/prerender';
9
+ import { patternToBracketFile, staticSectionPattern } from '../src/compiler/routes';
10
+ import { resolveDynamicFile } from '../src/devserver/http/runtime';
11
+
12
+ describe('patternToBracketFile', () => {
13
+ it('inverts URL patterns back to on-disk bracket filenames', () => {
14
+ expect(patternToBracketFile('/blog/:id')).toBe('blog/[id].html');
15
+ expect(patternToBracketFile('/docs/*slug')).toBe('docs/[...slug].html');
16
+ expect(patternToBracketFile('/docs/**slug')).toBe('docs/[[...slug]].html');
17
+ expect(patternToBracketFile('/:category/:id')).toBe('[category]/[id].html');
18
+ expect(patternToBracketFile('/gallery/photo/:id')).toBe('gallery/photo/[id].html');
19
+ });
20
+ });
21
+
22
+ describe('staticSectionPattern', () => {
23
+ it('strips dynamic segments to the static section (for the template canonical)', () => {
24
+ expect(staticSectionPattern('/blog/:id')).toBe('/blog');
25
+ expect(staticSectionPattern('/docs/*slug')).toBe('/docs');
26
+ expect(staticSectionPattern('/:category/:id')).toBe('/');
27
+ expect(staticSectionPattern('/gallery/photo/:id')).toBe('/gallery/photo');
28
+ });
29
+ });
30
+
31
+ describe('exportsTrue (edge-SSR opt-in detection)', () => {
32
+ const files: string[] = [];
33
+ function tmp(source: string): string {
34
+ const file = path.join(os.tmpdir(), `toil-ssrflag-${String(files.length)}-${process.pid}.tsx`);
35
+ fs.writeFileSync(file, source);
36
+ files.push(file);
37
+ return file;
38
+ }
39
+ afterEach(() => {
40
+ for (const f of files.splice(0)) fs.rmSync(f, { force: true });
41
+ });
42
+
43
+ it('detects `export const ssr = true`', () => {
44
+ expect(exportsTrue(ts, tmp(`export const ssr = true;\nexport default () => null;\n`), 'ssr')).toBe(
45
+ true,
46
+ );
47
+ });
48
+ it('is false for ssr=false, a non-literal, or an absent export', () => {
49
+ expect(exportsTrue(ts, tmp(`export const ssr = false;\n`), 'ssr')).toBe(false);
50
+ expect(exportsTrue(ts, tmp(`const ssr = true;\n`), 'ssr')).toBe(false); // not exported
51
+ expect(exportsTrue(ts, tmp(`export const other = true;\n`), 'ssr')).toBe(false);
52
+ });
53
+ });
54
+
55
+ describe('resolveDynamicFile (self-host bracket routing, mirrors the edge)', () => {
56
+ const roots: string[] = [];
57
+ function siteRoot(): string {
58
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'toil-dyn-'));
59
+ roots.push(root);
60
+ return root;
61
+ }
62
+ function write(root: string, rel: string, body = '<html></html>'): void {
63
+ const full = path.join(root, rel);
64
+ fs.mkdirSync(path.dirname(full), { recursive: true });
65
+ fs.writeFileSync(full, body);
66
+ }
67
+ afterEach(() => {
68
+ for (const d of roots.splice(0)) fs.rmSync(d, { recursive: true, force: true });
69
+ });
70
+
71
+ it('serves a single-segment [id].html for a matching URL, one segment only', () => {
72
+ const root = siteRoot();
73
+ write(root, 'blog/[id].html', 'blog-shell');
74
+ expect(resolveDynamicFile(root, '/blog/hello')).toBe(path.join(root, 'blog/[id].html'));
75
+ expect(resolveDynamicFile(root, '/blog/a/b')).toBeNull();
76
+ });
77
+
78
+ it('serves a required catch-all for 1+ segments but not zero', () => {
79
+ const root = siteRoot();
80
+ write(root, 'docs/[...slug].html', 'docs');
81
+ expect(resolveDynamicFile(root, '/docs/a')).toBe(path.join(root, 'docs/[...slug].html'));
82
+ expect(resolveDynamicFile(root, '/docs/a/b/c')).toBe(path.join(root, 'docs/[...slug].html'));
83
+ expect(resolveDynamicFile(root, '/docs')).toBeNull();
84
+ // Parity with the client router + edge: a literal `index.html` segment is a slug (matches
85
+ // the catch-all), while a bare `/docs/` is zero trailing segments (no required-catch-all match).
86
+ expect(resolveDynamicFile(root, '/docs/index.html')).toBe(
87
+ path.join(root, 'docs/[...slug].html'),
88
+ );
89
+ expect(resolveDynamicFile(root, '/docs/')).toBeNull();
90
+ });
91
+
92
+ it('serves an optional catch-all for 0+ segments', () => {
93
+ const root = siteRoot();
94
+ write(root, 'files/[[...slug]].html', 'files');
95
+ expect(resolveDynamicFile(root, '/files')).toBe(path.join(root, 'files/[[...slug]].html'));
96
+ expect(resolveDynamicFile(root, '/files/a/b')).toBe(path.join(root, 'files/[[...slug]].html'));
97
+ });
98
+
99
+ it('prefers single over catch-all for one segment, catch-all for deeper', () => {
100
+ const root = siteRoot();
101
+ write(root, 'shop/[id].html', 'one');
102
+ write(root, 'shop/[...rest].html', 'many');
103
+ expect(fs.readFileSync(resolveDynamicFile(root, '/shop/x') as string, 'utf8')).toBe('one');
104
+ expect(fs.readFileSync(resolveDynamicFile(root, '/shop/x/y') as string, 'utf8')).toBe('many');
105
+ });
106
+
107
+ it('prefers a real static subdirectory over a dynamic sibling', () => {
108
+ const root = siteRoot();
109
+ write(root, 'blog/[id].html', 'template');
110
+ write(root, 'blog/about/deep/[id].html', 'deep');
111
+ // /blog/about/deep/7 descends the real dirs to the deep template, not blog/[id].html.
112
+ expect(resolveDynamicFile(root, '/blog/about/deep/7')).toBe(
113
+ path.join(root, 'blog/about/deep/[id].html'),
114
+ );
115
+ });
116
+
117
+ it('resolves a nested bracket directory', () => {
118
+ const root = siteRoot();
119
+ write(root, '[category]/[id].html', 'cat');
120
+ expect(resolveDynamicFile(root, '/electronics/9')).toBe(
121
+ path.join(root, '[category]/[id].html'),
122
+ );
123
+ expect(resolveDynamicFile(root, '/electronics')).toBeNull();
124
+ });
125
+
126
+ it('returns null when no bracket template exists', () => {
127
+ const root = siteRoot();
128
+ write(root, 'about/index.html', 'about');
129
+ expect(resolveDynamicFile(root, '/blog/foo')).toBeNull();
130
+ expect(resolveDynamicFile(root, '/x/y/z')).toBeNull();
131
+ });
132
+ });
@@ -0,0 +1,50 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+
5
+ import sharp from 'sharp';
6
+ import { describe, expect, it } from 'vitest';
7
+
8
+ import { imageBlurPlugin } from '../src/compiler/image-blur';
9
+
10
+ /** Call the plugin's `load` hook directly (it uses no Rollup `this` context). */
11
+ function loadHook(): (id: string) => Promise<string | undefined> {
12
+ const plugin = imageBlurPlugin();
13
+ return plugin.load as unknown as (id: string) => Promise<string | undefined>;
14
+ }
15
+
16
+ describe('imageBlurPlugin', () => {
17
+ it('emits a { src, width, height, blurDataURL } module for a ?toil image import', async () => {
18
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'toil-blur-'));
19
+ const file = path.join(dir, 'pic.png');
20
+ await sharp({
21
+ create: { width: 40, height: 30, channels: 3, background: { r: 200, g: 100, b: 50 } },
22
+ })
23
+ .png()
24
+ .toFile(file);
25
+ try {
26
+ const load = loadHook();
27
+ const out = await load(`${file}?toil`);
28
+ expect(typeof out).toBe('string');
29
+ // re-imports the bare asset for src (so Vite/imagetools still optimize + hash it)
30
+ expect(out).toContain(`import src from ${JSON.stringify(file)}`);
31
+ // carries the intrinsic size for the aspect-ratio
32
+ expect(out).toContain('width: 40');
33
+ expect(out).toContain('height: 30');
34
+ // a real inlined LQIP, not a placeholder string
35
+ expect(out).toContain('blurDataURL: "data:image/webp;base64,');
36
+ const b64 = /base64,([^"]+)"/.exec(out as string)?.[1] ?? '';
37
+ expect(b64.length).toBeGreaterThan(20);
38
+ } finally {
39
+ fs.rmSync(dir, { recursive: true, force: true });
40
+ }
41
+ });
42
+
43
+ it('ignores imports without the ?toil flag', async () => {
44
+ const load = loadHook();
45
+ expect(await load('/some/pic.png')).toBeUndefined();
46
+ expect(await load('/some/pic.png?w=400')).toBeUndefined();
47
+ // non-raster ?toil (e.g. svg) is skipped too
48
+ expect(await load('/some/icon.svg?toil')).toBeUndefined();
49
+ });
50
+ });