@abreen/tada 1.5.0 → 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/build/generate-content-assets.ts +2 -2
- package/build/types.d.ts +1 -0
- package/build/utils/render.ts +5 -0
- package/build/utils/trace.ts +70 -22
- package/package.json +4 -3
- package/src/trace/index.ts +9 -1
- 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 -528
- package/src/util.test.ts +0 -135
|
@@ -158,6 +158,7 @@ export class ContentRenderer {
|
|
|
158
158
|
assetFiles,
|
|
159
159
|
literateJavaOutputPaths,
|
|
160
160
|
traceCache: this.traceCache,
|
|
161
|
+
javacAvailable: this.javacAvailable,
|
|
161
162
|
}),
|
|
162
163
|
);
|
|
163
164
|
return assets;
|
|
@@ -316,8 +317,7 @@ export class ContentRenderer {
|
|
|
316
317
|
log.info`Processing ${dirtySourceFiles.size} content ${noun}`;
|
|
317
318
|
}
|
|
318
319
|
|
|
319
|
-
|
|
320
|
-
if (hasLiterateJava && this.javacAvailable === undefined) {
|
|
320
|
+
if (this.javacAvailable === undefined && dirtySourceFiles.size > 0) {
|
|
321
321
|
this.javacAvailable = checkJavac();
|
|
322
322
|
if (!this.javacAvailable) {
|
|
323
323
|
log.warn`javac was not found; literate Java pages will not include execution output`;
|
package/build/types.d.ts
CHANGED
package/build/utils/render.ts
CHANGED
|
@@ -179,6 +179,7 @@ export function renderPlainTextPageAsset({
|
|
|
179
179
|
assetFiles,
|
|
180
180
|
literateJavaOutputPaths,
|
|
181
181
|
traceCache,
|
|
182
|
+
javacAvailable,
|
|
182
183
|
}: RenderPlainTextOptions): Asset[] {
|
|
183
184
|
const { dir, name, ext } = path.parse(filePath);
|
|
184
185
|
const subPath = path.relative(contentDir, path.join(dir, name));
|
|
@@ -197,6 +198,7 @@ export function renderPlainTextPageAsset({
|
|
|
197
198
|
contentDir,
|
|
198
199
|
distDir,
|
|
199
200
|
literateJavaOutputPaths,
|
|
201
|
+
javacAvailable,
|
|
200
202
|
},
|
|
201
203
|
);
|
|
202
204
|
|
|
@@ -339,6 +341,7 @@ function renderPlainTextContent(
|
|
|
339
341
|
contentDir,
|
|
340
342
|
distDir,
|
|
341
343
|
literateJavaOutputPaths,
|
|
344
|
+
javacAvailable,
|
|
342
345
|
}: {
|
|
343
346
|
validateInternalLinks?: boolean;
|
|
344
347
|
traceCache?: Map<
|
|
@@ -353,6 +356,7 @@ function renderPlainTextContent(
|
|
|
353
356
|
contentDir?: string;
|
|
354
357
|
distDir?: string;
|
|
355
358
|
literateJavaOutputPaths?: Set<string>;
|
|
359
|
+
javacAvailable?: boolean;
|
|
356
360
|
} = {},
|
|
357
361
|
): {
|
|
358
362
|
content: string | null;
|
|
@@ -446,6 +450,7 @@ function renderPlainTextContent(
|
|
|
446
450
|
distDir,
|
|
447
451
|
applyBasePath,
|
|
448
452
|
cache: traceCache,
|
|
453
|
+
javacAvailable,
|
|
449
454
|
});
|
|
450
455
|
params.renderTrace = helpers.renderTrace;
|
|
451
456
|
}
|
package/build/utils/trace.ts
CHANGED
|
@@ -214,6 +214,7 @@ export interface TraceContext {
|
|
|
214
214
|
distDir: string;
|
|
215
215
|
applyBasePath: (subPath: string) => string;
|
|
216
216
|
cache: Map<string, TraceResult>;
|
|
217
|
+
javacAvailable?: boolean;
|
|
217
218
|
}
|
|
218
219
|
|
|
219
220
|
export interface TraceResult {
|
|
@@ -223,10 +224,65 @@ export interface TraceResult {
|
|
|
223
224
|
mtime: number;
|
|
224
225
|
}
|
|
225
226
|
|
|
227
|
+
function highlightSource(javaFile: string, source: string): string {
|
|
228
|
+
const sourceLines = source.split('\n');
|
|
229
|
+
if (sourceLines[sourceLines.length - 1] === '') {
|
|
230
|
+
sourceLines.pop();
|
|
231
|
+
}
|
|
232
|
+
const ext = path.extname(javaFile).slice(1).toLowerCase();
|
|
233
|
+
const lang = ext === 'java' ? 'java' : 'text';
|
|
234
|
+
return renderCodeSegment(sourceLines, 1, lang, { linkLineNumbers: false });
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function renderWidgetHtml({
|
|
238
|
+
highlightedSource,
|
|
239
|
+
manifestUrl,
|
|
240
|
+
totalSteps,
|
|
241
|
+
}: {
|
|
242
|
+
highlightedSource: string;
|
|
243
|
+
manifestUrl?: string;
|
|
244
|
+
totalSteps?: number;
|
|
245
|
+
}): string {
|
|
246
|
+
const svgAttrs = `aria-hidden='true' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'`;
|
|
247
|
+
const iconFirst = `<svg xmlns='http://www.w3.org/2000/svg' ${svgAttrs}><line x1='2' y1='6' x2='2' y2='18'/><polyline points='10 6 4 12 10 18'/><line x1='4' y1='12' x2='22' y2='12'/></svg>`;
|
|
248
|
+
const iconPrev = `<svg xmlns='http://www.w3.org/2000/svg' ${svgAttrs}><polyline points='10 6 4 12 10 18'/><line x1='4' y1='12' x2='22' y2='12'/></svg>`;
|
|
249
|
+
const iconNext = `<svg xmlns='http://www.w3.org/2000/svg' ${svgAttrs}><line x1='2' y1='12' x2='20' y2='12'/><polyline points='14 6 20 12 14 18'/></svg>`;
|
|
250
|
+
const iconLast = `<svg xmlns='http://www.w3.org/2000/svg' ${svgAttrs}><line x1='2' y1='12' x2='20' y2='12'/><polyline points='14 6 20 12 14 18'/><line x1='22' y1='6' x2='22' y2='18'/></svg>`;
|
|
251
|
+
const disabled = totalSteps === undefined;
|
|
252
|
+
const counterContent = disabled ? '--' : `1/${totalSteps}`;
|
|
253
|
+
const counterStyle = disabled
|
|
254
|
+
? ''
|
|
255
|
+
: ` style="min-width: ${String(totalSteps).length * 2 + 1}ch"`;
|
|
256
|
+
const controls =
|
|
257
|
+
`<button class="trace-btn trace-first" disabled tabindex="-1" aria-label="First step" title="First step">${iconFirst}</button>` +
|
|
258
|
+
`<button class="trace-btn trace-prev" disabled tabindex="-1" aria-label="Previous step" title="Previous step">${iconPrev}</button>` +
|
|
259
|
+
`<span class="trace-step-counter"${counterStyle}>${counterContent}</span>` +
|
|
260
|
+
(disabled
|
|
261
|
+
? `<button class="trace-btn trace-next" disabled tabindex="-1" aria-label="Next step" title="Next step">${iconNext}</button>`
|
|
262
|
+
: `<button class="trace-btn trace-next" aria-label="Next step" title="Next step">${iconNext}</button>`) +
|
|
263
|
+
(disabled
|
|
264
|
+
? `<button class="trace-btn trace-last" disabled tabindex="-1" aria-label="Last step" title="Last step">${iconLast}</button>`
|
|
265
|
+
: `<button class="trace-btn trace-last" tabindex="-1" aria-label="Last step" title="Last step">${iconLast}</button>`);
|
|
266
|
+
const wrapperAttrs = manifestUrl
|
|
267
|
+
? ` data-trace-manifest="${manifestUrl}"`
|
|
268
|
+
: '';
|
|
269
|
+
const wrapperClass = disabled
|
|
270
|
+
? 'trace-widget trace-disabled'
|
|
271
|
+
: 'trace-widget';
|
|
272
|
+
return `<div class="${wrapperClass}"${wrapperAttrs}><noscript><p>This interactive trace requires JavaScript.</p></noscript><div class="trace-body"><div class="trace-toolbar"><div class="trace-controls" role="toolbar" aria-label="Trace navigation">${controls}</div></div><div class="trace-content"><div class="trace-diagram"></div><div class="trace-source-wrapper"><div class="trace-source">${highlightedSource}</div></div><pre class="trace-output"></pre></div></div></div>`;
|
|
273
|
+
}
|
|
274
|
+
|
|
226
275
|
export function createTraceHelpers(context: TraceContext): {
|
|
227
276
|
renderTrace: (javaFile: string) => string;
|
|
228
277
|
} {
|
|
229
|
-
const {
|
|
278
|
+
const {
|
|
279
|
+
filePath,
|
|
280
|
+
contentDir,
|
|
281
|
+
distDir,
|
|
282
|
+
applyBasePath,
|
|
283
|
+
cache,
|
|
284
|
+
javacAvailable,
|
|
285
|
+
} = context;
|
|
230
286
|
const pageDir = path.dirname(filePath);
|
|
231
287
|
|
|
232
288
|
function getOrRunTrace(javaFile: string): TraceResult {
|
|
@@ -266,15 +322,7 @@ export function createTraceHelpers(context: TraceContext): {
|
|
|
266
322
|
{ timeout: 60000, encoding: 'utf-8', maxBuffer: 50 * 1024 * 1024 },
|
|
267
323
|
);
|
|
268
324
|
|
|
269
|
-
const
|
|
270
|
-
if (sourceLines[sourceLines.length - 1] === '') {
|
|
271
|
-
sourceLines.pop();
|
|
272
|
-
}
|
|
273
|
-
const ext = path.extname(javaFile).slice(1).toLowerCase();
|
|
274
|
-
const lang = ext === 'java' ? 'java' : 'text';
|
|
275
|
-
const highlightedSource = renderCodeSegment(sourceLines, 1, lang, {
|
|
276
|
-
linkLineNumbers: false,
|
|
277
|
-
});
|
|
325
|
+
const highlightedSource = highlightSource(javaFile, source);
|
|
278
326
|
|
|
279
327
|
const relDir = path
|
|
280
328
|
.relative(contentDir, pageDir)
|
|
@@ -315,20 +363,20 @@ export function createTraceHelpers(context: TraceContext): {
|
|
|
315
363
|
|
|
316
364
|
return {
|
|
317
365
|
renderTrace: (javaFile: string): string => {
|
|
366
|
+
if (javacAvailable === false) {
|
|
367
|
+
log.warn`javac was not found; trace for ${B`${javaFile}`} will be disabled`;
|
|
368
|
+
const javaFilePath = path.resolve(pageDir, javaFile);
|
|
369
|
+
if (!fs.existsSync(javaFilePath)) {
|
|
370
|
+
throw new Error(`Trace target not found: ${javaFilePath}`);
|
|
371
|
+
}
|
|
372
|
+
const source = fs.readFileSync(javaFilePath, 'utf-8');
|
|
373
|
+
return renderWidgetHtml({
|
|
374
|
+
highlightedSource: highlightSource(javaFile, source),
|
|
375
|
+
});
|
|
376
|
+
}
|
|
318
377
|
const { manifestUrl, highlightedSource, totalSteps } =
|
|
319
378
|
getOrRunTrace(javaFile);
|
|
320
|
-
|
|
321
|
-
const iconFirst = `<svg xmlns='http://www.w3.org/2000/svg' ${svgAttrs}><line x1='2' y1='6' x2='2' y2='18'/><polyline points='10 6 4 12 10 18'/><line x1='4' y1='12' x2='22' y2='12'/></svg>`;
|
|
322
|
-
const iconPrev = `<svg xmlns='http://www.w3.org/2000/svg' ${svgAttrs}><polyline points='10 6 4 12 10 18'/><line x1='4' y1='12' x2='22' y2='12'/></svg>`;
|
|
323
|
-
const iconNext = `<svg xmlns='http://www.w3.org/2000/svg' ${svgAttrs}><line x1='2' y1='12' x2='20' y2='12'/><polyline points='14 6 20 12 14 18'/></svg>`;
|
|
324
|
-
const iconLast = `<svg xmlns='http://www.w3.org/2000/svg' ${svgAttrs}><line x1='2' y1='12' x2='20' y2='12'/><polyline points='14 6 20 12 14 18'/><line x1='22' y1='6' x2='22' y2='18'/></svg>`;
|
|
325
|
-
const controls =
|
|
326
|
-
`<button class="trace-btn trace-first" disabled tabindex="-1" aria-label="First step" title="First step">${iconFirst}</button>` +
|
|
327
|
-
`<button class="trace-btn trace-prev" disabled tabindex="-1" aria-label="Previous step" title="Previous step">${iconPrev}</button>` +
|
|
328
|
-
`<span class="trace-step-counter" style="min-width: ${String(totalSteps).length * 2 + 1}ch">1/${totalSteps}</span>` +
|
|
329
|
-
`<button class="trace-btn trace-next" aria-label="Next step" title="Next step">${iconNext}</button>` +
|
|
330
|
-
`<button class="trace-btn trace-last" tabindex="-1" aria-label="Last step" title="Last step">${iconLast}</button>`;
|
|
331
|
-
return `<div class="trace-widget" data-trace-manifest="${manifestUrl}"><noscript><p>This interactive trace requires JavaScript.</p></noscript><div class="trace-body"><div class="trace-toolbar"><div class="trace-controls" role="toolbar" aria-label="Trace navigation">${controls}</div></div><div class="trace-content"><div class="trace-diagram"></div><div class="trace-source-wrapper"><div class="trace-source">${highlightedSource}</div></div><pre class="trace-output"></pre></div></div></div>`;
|
|
379
|
+
return renderWidgetHtml({ highlightedSource, manifestUrl, totalSteps });
|
|
332
380
|
},
|
|
333
381
|
};
|
|
334
382
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abreen/tada",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A static site generator",
|
|
6
6
|
"license": "MIT",
|
|
@@ -21,10 +21,11 @@
|
|
|
21
21
|
"init/",
|
|
22
22
|
"schema/",
|
|
23
23
|
"types/",
|
|
24
|
-
"tsconfig.json"
|
|
24
|
+
"tsconfig.json",
|
|
25
|
+
"!**/*.test.ts"
|
|
25
26
|
],
|
|
26
27
|
"scripts": {
|
|
27
|
-
"prepare": "husky && bun run scripts/check-deps.ts",
|
|
28
|
+
"prepare": "bunx husky && bun run scripts/check-deps.ts",
|
|
28
29
|
"prepack": "bun run scripts/prepack.ts",
|
|
29
30
|
"lint": "eslint src/ build/ bin/",
|
|
30
31
|
"typecheck": "tsc --noEmit",
|
package/src/trace/index.ts
CHANGED
|
@@ -175,7 +175,15 @@ async function initWidget(root: HTMLElement, doc: Document): Promise<void> {
|
|
|
175
175
|
return;
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
|
|
178
|
+
let res: Response;
|
|
179
|
+
try {
|
|
180
|
+
res = await fetch(manifestUrl);
|
|
181
|
+
} catch {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (!res.ok) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
179
187
|
const manifest: TraceManifest = await res.json();
|
|
180
188
|
|
|
181
189
|
const state: WidgetState = { manifest, chunks: new Map(), currentStep: 0 };
|
package/bin/validators.test.ts
DELETED
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test';
|
|
2
|
-
import type { SiteConfigInput } from '../build/types';
|
|
3
|
-
import {
|
|
4
|
-
validateSymbol,
|
|
5
|
-
validateColor,
|
|
6
|
-
validateHue,
|
|
7
|
-
validateUrl,
|
|
8
|
-
validateBasePath,
|
|
9
|
-
createSiteConfig,
|
|
10
|
-
} from './validators';
|
|
11
|
-
|
|
12
|
-
describe('validateSymbol', () => {
|
|
13
|
-
test('accepts a single uppercase letter', () => {
|
|
14
|
-
expect(validateSymbol('A')).toBeNull();
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
test('accepts digits and uppercase letters up to 5 chars', () => {
|
|
18
|
-
expect(validateSymbol('CS 0')).toBeNull();
|
|
19
|
-
expect(validateSymbol('CS101')).toBeNull();
|
|
20
|
-
expect(validateSymbol('AB-CD')).toBeNull();
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
test('rejects empty string', () => {
|
|
24
|
-
expect(validateSymbol('')).not.toBeNull();
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
test('rejects value longer than 5 chars', () => {
|
|
28
|
-
expect(validateSymbol('TOOLONG')).not.toBeNull();
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
test('rejects lowercase letters', () => {
|
|
32
|
-
expect(validateSymbol('abc')).not.toBeNull();
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
test('rejects special characters', () => {
|
|
36
|
-
expect(validateSymbol('A@B')).not.toBeNull();
|
|
37
|
-
expect(validateSymbol('A.B')).not.toBeNull();
|
|
38
|
-
});
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
describe('validateColor', () => {
|
|
42
|
-
test('accepts CSS color names', () => {
|
|
43
|
-
expect(validateColor('red')).toBeNull();
|
|
44
|
-
expect(validateColor('tomato')).toBeNull();
|
|
45
|
-
expect(validateColor('blue')).toBeNull();
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
test('accepts hex colors', () => {
|
|
49
|
-
expect(validateColor('#f00')).toBeNull();
|
|
50
|
-
expect(validateColor('#ff0000')).toBeNull();
|
|
51
|
-
expect(validateColor('#c04040')).toBeNull();
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test('accepts HSL colors', () => {
|
|
55
|
-
expect(validateColor('hsl(195 70% 40%)')).toBeNull();
|
|
56
|
-
expect(validateColor('hsl(195, 70%, 40%)')).toBeNull();
|
|
57
|
-
expect(validateColor('hsl(195deg 70% 40%)')).toBeNull();
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
test('accepts RGB colors', () => {
|
|
61
|
-
expect(validateColor('rgb(0 0 0)')).toBeNull();
|
|
62
|
-
expect(validateColor('rgb(255, 99, 71)')).toBeNull();
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
test('rejects empty string', () => {
|
|
66
|
-
expect(validateColor('')).not.toBeNull();
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
test('rejects invalid color strings', () => {
|
|
70
|
-
expect(validateColor('notacolor')).not.toBeNull();
|
|
71
|
-
expect(validateColor('hsl 195 70% 40%')).not.toBeNull();
|
|
72
|
-
});
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
describe('validateHue', () => {
|
|
76
|
-
test('accepts integer', () => {
|
|
77
|
-
expect(validateHue('0')).toBeNull();
|
|
78
|
-
expect(validateHue('180')).toBeNull();
|
|
79
|
-
expect(validateHue('360')).toBeNull();
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
test('accepts integer with deg suffix', () => {
|
|
83
|
-
expect(validateHue('90deg')).toBeNull();
|
|
84
|
-
expect(validateHue('360deg')).toBeNull();
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
test('rejects empty string', () => {
|
|
88
|
-
expect(validateHue('')).not.toBeNull();
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
test('rejects out of range', () => {
|
|
92
|
-
expect(validateHue('-1')).not.toBeNull();
|
|
93
|
-
expect(validateHue('361')).not.toBeNull();
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
test('rejects non-integer', () => {
|
|
97
|
-
expect(validateHue('1.5')).not.toBeNull();
|
|
98
|
-
expect(validateHue('abc')).not.toBeNull();
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
describe('validateUrl', () => {
|
|
103
|
-
test('accepts https URL', () => {
|
|
104
|
-
expect(validateUrl('https://example.edu')).toBeNull();
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
test('accepts http URL', () => {
|
|
108
|
-
expect(validateUrl('http://localhost')).toBeNull();
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
test('accepts URL with port', () => {
|
|
112
|
-
expect(validateUrl('http://localhost:8080')).toBeNull();
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
test('rejects empty string', () => {
|
|
116
|
-
expect(validateUrl('')).not.toBeNull();
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
test('rejects trailing slash', () => {
|
|
120
|
-
expect(validateUrl('https://example.edu/')).not.toBeNull();
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
test('rejects URL with path', () => {
|
|
124
|
-
expect(validateUrl('https://example.edu/foo')).not.toBeNull();
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
test('rejects non-http scheme', () => {
|
|
128
|
-
expect(validateUrl('ftp://example.edu')).not.toBeNull();
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
test('rejects bare domain', () => {
|
|
132
|
-
expect(validateUrl('example.edu')).not.toBeNull();
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
describe('validateBasePath', () => {
|
|
137
|
-
test('accepts root path', () => {
|
|
138
|
-
expect(validateBasePath('/')).toBeNull();
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
test('accepts simple path segment', () => {
|
|
142
|
-
expect(validateBasePath('/foo')).toBeNull();
|
|
143
|
-
expect(validateBasePath('/foo-bar')).toBeNull();
|
|
144
|
-
expect(validateBasePath('/cs101')).toBeNull();
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
test('rejects path without leading slash', () => {
|
|
148
|
-
expect(validateBasePath('foo')).not.toBeNull();
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
test('rejects trailing slash', () => {
|
|
152
|
-
expect(validateBasePath('/foo/')).not.toBeNull();
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
test('rejects spaces', () => {
|
|
156
|
-
expect(validateBasePath('/foo bar')).not.toBeNull();
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
test('rejects nested paths', () => {
|
|
160
|
-
expect(validateBasePath('/foo/bar')).not.toBeNull();
|
|
161
|
-
});
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
describe('createSiteConfig', () => {
|
|
165
|
-
const base: SiteConfigInput = {
|
|
166
|
-
title: 'Test Site',
|
|
167
|
-
symbol: 'TS',
|
|
168
|
-
themeColor: 'hsl(200 50% 40%)',
|
|
169
|
-
tintHue: '20',
|
|
170
|
-
tintAmount: '100',
|
|
171
|
-
defaultTimeZone: 'America/New_York',
|
|
172
|
-
base: 'https://example.edu',
|
|
173
|
-
basePath: '/',
|
|
174
|
-
internalDomains: ['example.edu'],
|
|
175
|
-
features: { search: true, code: true, favicon: false },
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
test('returns an object with all expected keys', () => {
|
|
179
|
-
const config = createSiteConfig(base);
|
|
180
|
-
expect(config).toMatchObject({
|
|
181
|
-
title: 'Test Site',
|
|
182
|
-
symbol: 'TS',
|
|
183
|
-
themeColor: 'hsl(200 50% 40%)',
|
|
184
|
-
base: 'https://example.edu',
|
|
185
|
-
basePath: '/',
|
|
186
|
-
internalDomains: ['example.edu'],
|
|
187
|
-
defaultTimeZone: 'America/New_York',
|
|
188
|
-
features: { search: true, code: true, favicon: false },
|
|
189
|
-
codeLanguages: { java: 'java', py: 'python' },
|
|
190
|
-
vars: {},
|
|
191
|
-
});
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
test('coerces tintHue and tintAmount to numbers', () => {
|
|
195
|
-
const config = createSiteConfig(base);
|
|
196
|
-
expect(config.tintHue).toBe(20);
|
|
197
|
-
expect(config.tintAmount).toBe(100);
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
test('coerces numeric string tintHue correctly', () => {
|
|
201
|
-
const config = createSiteConfig({ ...base, tintHue: '0', tintAmount: '0' });
|
|
202
|
-
expect(config.tintHue).toBe(0);
|
|
203
|
-
expect(config.tintAmount).toBe(0);
|
|
204
|
-
});
|
|
205
|
-
});
|