@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.
Files changed (40) hide show
  1. package/package.json +3 -2
  2. package/bin/validators.test.ts +0 -205
  3. package/build/build-manifest.test.ts +0 -369
  4. package/build/code.test.ts +0 -272
  5. package/build/colors.test.ts +0 -66
  6. package/build/globals.test.ts +0 -100
  7. package/build/json-schema.test.ts +0 -57
  8. package/build/log.test.ts +0 -117
  9. package/build/markdown-plugins.test.ts +0 -456
  10. package/build/pagefind.test.ts +0 -192
  11. package/build/pdf-text.test.ts +0 -52
  12. package/build/reachability.test.ts +0 -80
  13. package/build/serve.test.ts +0 -59
  14. package/build/toc-plugin.test.ts +0 -120
  15. package/build/utils/content-files.test.ts +0 -56
  16. package/build/utils/derive-theme.test.ts +0 -111
  17. package/build/utils/file-types.test.ts +0 -79
  18. package/build/utils/front-matter.test.ts +0 -77
  19. package/build/utils/include.test.ts +0 -117
  20. package/build/utils/paths.test.ts +0 -91
  21. package/build/utils/trace-layout.test.ts +0 -570
  22. package/build/utils/trace-svg.test.ts +0 -425
  23. package/build/utils/trace.test.ts +0 -169
  24. package/build/validate-config-links.test.ts +0 -239
  25. package/build/validate-internal-links-plugin.test.ts +0 -134
  26. package/build/watch-reachability-state.test.ts +0 -198
  27. package/src/anchor/index.test.ts +0 -64
  28. package/src/code/index.test.ts +0 -71
  29. package/src/header/index.test.ts +0 -89
  30. package/src/navigate/eligible.test.ts +0 -101
  31. package/src/navigate/index.test.ts +0 -82
  32. package/src/navigate/lifecycle.test.ts +0 -26
  33. package/src/print/index.test.ts +0 -71
  34. package/src/question/index.test.ts +0 -82
  35. package/src/search/index.test.ts +0 -203
  36. package/src/timezone/index.test.ts +0 -400
  37. package/src/toc/index.test.ts +0 -237
  38. package/src/top/index.test.ts +0 -46
  39. package/src/trace/index.test.ts +0 -554
  40. package/src/util.test.ts +0 -135
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abreen/tada",
3
- "version": "1.5.1",
3
+ "version": "1.5.2",
4
4
  "type": "module",
5
5
  "description": "A static site generator",
6
6
  "license": "MIT",
@@ -21,7 +21,8 @@
21
21
  "init/",
22
22
  "schema/",
23
23
  "types/",
24
- "tsconfig.json"
24
+ "tsconfig.json",
25
+ "!**/*.test.ts"
25
26
  ],
26
27
  "scripts": {
27
28
  "prepare": "bunx husky && bun run scripts/check-deps.ts",
@@ -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
- });
@@ -1,369 +0,0 @@
1
- import { describe, expect, test, beforeEach, afterEach } from 'bun:test';
2
- import fs from 'fs';
3
- import os from 'os';
4
- import path from 'path';
5
- import {
6
- diffManifests,
7
- hashFile,
8
- walkAndHash,
9
- getVersions,
10
- getNextVersion,
11
- generateBuildManifest,
12
- loadManifest,
13
- pruneOldVersions,
14
- copyChangedFiles,
15
- } from './build-manifest';
16
- import type { BuildManifest, ManifestDiff } from './build-manifest';
17
-
18
- function makeManifest(
19
- files: Record<string, string>,
20
- build: number = 1,
21
- ): BuildManifest {
22
- return { schema: 1, build, buildTime: '2025-01-01T00:00:00.000Z', files };
23
- }
24
-
25
- let tmpDir: string;
26
-
27
- beforeEach(() => {
28
- tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tada-test-'));
29
- });
30
-
31
- afterEach(() => {
32
- fs.rmSync(tmpDir, { recursive: true });
33
- });
34
-
35
- describe('diffManifests', () => {
36
- test('detects added files', () => {
37
- const prev = makeManifest({ 'a.html': 'hash1' });
38
- const current = makeManifest({ 'a.html': 'hash1', 'b.html': 'hash2' });
39
- const diff = diffManifests(prev, current);
40
- expect(diff.added).toEqual(['b.html']);
41
- expect(diff.changed).toEqual([]);
42
- expect(diff.removed).toEqual([]);
43
- });
44
-
45
- test('detects changed files', () => {
46
- const prev = makeManifest({ 'a.html': 'hash1' });
47
- const current = makeManifest({ 'a.html': 'hash2' });
48
- const diff = diffManifests(prev, current);
49
- expect(diff.added).toEqual([]);
50
- expect(diff.changed).toEqual(['a.html']);
51
- expect(diff.removed).toEqual([]);
52
- });
53
-
54
- test('detects removed files', () => {
55
- const prev = makeManifest({ 'a.html': 'hash1', 'b.html': 'hash2' });
56
- const current = makeManifest({ 'a.html': 'hash1' });
57
- const diff = diffManifests(prev, current);
58
- expect(diff.added).toEqual([]);
59
- expect(diff.changed).toEqual([]);
60
- expect(diff.removed).toEqual(['b.html']);
61
- });
62
-
63
- test('detects added, changed, and removed all at once', () => {
64
- const prev = makeManifest({
65
- 'unchanged.html': 'same',
66
- 'changed.html': 'old',
67
- 'removed.html': 'gone',
68
- });
69
- const current = makeManifest({
70
- 'unchanged.html': 'same',
71
- 'changed.html': 'new',
72
- 'added.html': 'fresh',
73
- });
74
- const diff = diffManifests(prev, current);
75
- expect(diff.added).toEqual(['added.html']);
76
- expect(diff.changed).toEqual(['changed.html']);
77
- expect(diff.removed).toEqual(['removed.html']);
78
- });
79
-
80
- test('returns empty arrays for identical manifests', () => {
81
- const manifest = makeManifest({ 'a.html': 'hash1', 'b.css': 'hash2' });
82
- const diff = diffManifests(manifest, manifest);
83
- expect(diff.added).toEqual([]);
84
- expect(diff.changed).toEqual([]);
85
- expect(diff.removed).toEqual([]);
86
- });
87
-
88
- test('returns results in alphabetical order', () => {
89
- const prev = makeManifest({
90
- 'z.html': 'old',
91
- 'removed-b.html': 'r',
92
- 'removed-a.html': 'r',
93
- });
94
- const current = makeManifest({
95
- 'z.html': 'new',
96
- 'c-added.html': 'c',
97
- 'a-added.html': 'a',
98
- });
99
- const diff = diffManifests(prev, current);
100
- expect(diff.added).toEqual(['a-added.html', 'c-added.html']);
101
- expect(diff.changed).toEqual(['z.html']);
102
- expect(diff.removed).toEqual(['removed-a.html', 'removed-b.html']);
103
- });
104
- });
105
-
106
- describe('hashFile', () => {
107
- test('produces consistent hashes for the same content', async () => {
108
- const file = path.join(tmpDir, 'test.txt');
109
- fs.writeFileSync(file, 'hello world');
110
- const hash1 = await hashFile(file);
111
- const hash2 = await hashFile(file);
112
- expect(hash1).toBe(hash2);
113
- });
114
-
115
- test('returns a 64-character hex string', async () => {
116
- const file = path.join(tmpDir, 'test.txt');
117
- fs.writeFileSync(file, 'content');
118
- const hash = await hashFile(file);
119
- expect(hash).toHaveLength(64);
120
- expect(hash).toMatch(/^[0-9a-f]+$/);
121
- });
122
-
123
- test('different content produces different hashes', async () => {
124
- const file1 = path.join(tmpDir, 'a.txt');
125
- const file2 = path.join(tmpDir, 'b.txt');
126
- fs.writeFileSync(file1, 'content one');
127
- fs.writeFileSync(file2, 'content two');
128
- const hash1 = await hashFile(file1);
129
- const hash2 = await hashFile(file2);
130
- expect(hash1).not.toBe(hash2);
131
- });
132
- });
133
-
134
- describe('walkAndHash', () => {
135
- test('returns a file map with relative paths and hashes', async () => {
136
- fs.writeFileSync(path.join(tmpDir, 'index.html'), '<html></html>');
137
- fs.writeFileSync(path.join(tmpDir, 'style.css'), 'body {}');
138
- const result = await walkAndHash(tmpDir);
139
- expect(Object.keys(result).sort()).toEqual(['index.html', 'style.css']);
140
- for (const hash of Object.values(result)) {
141
- expect(hash).toHaveLength(64);
142
- }
143
- });
144
-
145
- test('excludes pagefind/ top-level directory', async () => {
146
- fs.writeFileSync(path.join(tmpDir, 'index.html'), '<html></html>');
147
- const pagefindDir = path.join(tmpDir, 'pagefind');
148
- fs.mkdirSync(pagefindDir);
149
- fs.writeFileSync(path.join(pagefindDir, 'pagefind.js'), 'code');
150
- const result = await walkAndHash(tmpDir);
151
- expect(Object.keys(result)).toEqual(['index.html']);
152
- expect('pagefind/pagefind.js' in result).toBe(false);
153
- });
154
-
155
- test('excludes tada.manifest.json', async () => {
156
- fs.writeFileSync(path.join(tmpDir, 'index.html'), '<html></html>');
157
- fs.writeFileSync(path.join(tmpDir, 'tada.manifest.json'), '{"schema":1}');
158
- const result = await walkAndHash(tmpDir);
159
- expect(Object.keys(result)).toEqual(['index.html']);
160
- expect('tada.manifest.json' in result).toBe(false);
161
- });
162
-
163
- test('returns empty object for empty directory', async () => {
164
- const result = await walkAndHash(tmpDir);
165
- expect(result).toEqual({});
166
- });
167
-
168
- test('uses forward slashes in keys', async () => {
169
- const subDir = path.join(tmpDir, 'sub', 'nested');
170
- fs.mkdirSync(subDir, { recursive: true });
171
- fs.writeFileSync(path.join(subDir, 'file.html'), 'content');
172
- const result = await walkAndHash(tmpDir);
173
- const keys = Object.keys(result);
174
- expect(keys).toEqual(['sub/nested/file.html']);
175
- for (const key of keys) {
176
- expect(key).not.toContain('\\');
177
- }
178
- });
179
- });
180
-
181
- describe('getVersions', () => {
182
- test('returns sorted version numbers', () => {
183
- fs.mkdirSync(path.join(tmpDir, 'v3'));
184
- fs.mkdirSync(path.join(tmpDir, 'v1'));
185
- fs.mkdirSync(path.join(tmpDir, 'v2'));
186
- expect(getVersions(tmpDir)).toEqual([1, 2, 3]);
187
- });
188
-
189
- test('ignores non-version entries', () => {
190
- fs.mkdirSync(path.join(tmpDir, 'v1'));
191
- fs.mkdirSync(path.join(tmpDir, 'other'));
192
- fs.mkdirSync(path.join(tmpDir, 'v2x'));
193
- fs.writeFileSync(path.join(tmpDir, 'some-file.json'), '{}');
194
- expect(getVersions(tmpDir)).toEqual([1]);
195
- });
196
-
197
- test('returns empty array for nonexistent directory', () => {
198
- expect(getVersions(path.join(tmpDir, 'nonexistent'))).toEqual([]);
199
- });
200
-
201
- test('returns empty array for empty directory', () => {
202
- expect(getVersions(tmpDir)).toEqual([]);
203
- });
204
- });
205
-
206
- describe('getNextVersion', () => {
207
- test('returns 1 for nonexistent directory', () => {
208
- expect(getNextVersion(path.join(tmpDir, 'nonexistent'))).toBe(1);
209
- });
210
-
211
- test('returns 1 for empty directory', () => {
212
- expect(getNextVersion(tmpDir)).toBe(1);
213
- });
214
-
215
- test('returns highest version + 1', () => {
216
- fs.mkdirSync(path.join(tmpDir, 'v1'));
217
- fs.mkdirSync(path.join(tmpDir, 'v3'));
218
- expect(getNextVersion(tmpDir)).toBe(4);
219
- });
220
- });
221
-
222
- describe('generateBuildManifest', () => {
223
- test('writes valid JSON with schema, build, buildTime, and files', async () => {
224
- const distDir = path.join(tmpDir, 'dist');
225
- fs.mkdirSync(distDir);
226
- fs.writeFileSync(path.join(distDir, 'index.html'), '<html></html>');
227
-
228
- const manifestPath = path.join(distDir, 'tada.manifest.json');
229
- await generateBuildManifest(distDir, manifestPath, 3);
230
-
231
- const raw = fs.readFileSync(manifestPath, 'utf8');
232
- const manifest = JSON.parse(raw) as BuildManifest;
233
-
234
- expect(manifest.schema).toBe(1);
235
- expect(manifest.build).toBe(3);
236
- expect(typeof manifest.buildTime).toBe('string');
237
- expect(manifest.buildTime).toMatch(/^\d{4}-\d{2}-\d{2}T/);
238
- expect(typeof manifest.files).toBe('object');
239
- expect('index.html' in manifest.files).toBe(true);
240
- expect('tada.manifest.json' in manifest.files).toBe(false);
241
- });
242
- });
243
-
244
- describe('loadManifest', () => {
245
- test('loads and parses a manifest from a file', () => {
246
- const manifest = makeManifest({ 'index.html': 'abc123' });
247
- const filePath = path.join(tmpDir, 'tada.manifest.json');
248
- fs.writeFileSync(filePath, JSON.stringify(manifest, null, 2) + '\n');
249
-
250
- const loaded = loadManifest(filePath);
251
- expect(loaded).toEqual(manifest);
252
- });
253
-
254
- test('returns null for a nonexistent file', () => {
255
- const result = loadManifest(path.join(tmpDir, 'missing.json'));
256
- expect(result).toBeNull();
257
- });
258
- });
259
-
260
- describe('pruneOldVersions', () => {
261
- function setupVersions(versions: number[]) {
262
- for (const v of versions) {
263
- const vDir = path.join(tmpDir, `v${v}`);
264
- fs.mkdirSync(vDir);
265
- fs.writeFileSync(
266
- path.join(vDir, 'tada.manifest.json'),
267
- JSON.stringify(makeManifest({}, v)),
268
- );
269
- }
270
- }
271
-
272
- test('removes all but the latest 2 version directories', () => {
273
- setupVersions([1, 2, 3]);
274
- pruneOldVersions(tmpDir);
275
- expect(fs.existsSync(path.join(tmpDir, 'v1'))).toBe(false);
276
- expect(fs.existsSync(path.join(tmpDir, 'v2'))).toBe(true);
277
- expect(fs.existsSync(path.join(tmpDir, 'v3'))).toBe(true);
278
- });
279
-
280
- test('removes manifests inside pruned version directories', () => {
281
- setupVersions([1, 2, 3]);
282
- pruneOldVersions(tmpDir);
283
- expect(fs.existsSync(path.join(tmpDir, 'v1', 'tada.manifest.json'))).toBe(
284
- false,
285
- );
286
- expect(fs.existsSync(path.join(tmpDir, 'v2', 'tada.manifest.json'))).toBe(
287
- true,
288
- );
289
- expect(fs.existsSync(path.join(tmpDir, 'v3', 'tada.manifest.json'))).toBe(
290
- true,
291
- );
292
- });
293
-
294
- test('keeps all versions when there are 2 or fewer', () => {
295
- setupVersions([1, 2]);
296
- pruneOldVersions(tmpDir);
297
- expect(fs.existsSync(path.join(tmpDir, 'v1'))).toBe(true);
298
- expect(fs.existsSync(path.join(tmpDir, 'v2'))).toBe(true);
299
- });
300
-
301
- test('is a no-op for a nonexistent directory', () => {
302
- expect(() =>
303
- pruneOldVersions(path.join(tmpDir, 'nonexistent')),
304
- ).not.toThrow();
305
- });
306
- });
307
-
308
- describe('copyChangedFiles', () => {
309
- test('copies added and changed files', () => {
310
- const distDir = path.join(tmpDir, 'dist');
311
- const outDir = path.join(tmpDir, 'out');
312
- fs.mkdirSync(distDir);
313
- fs.mkdirSync(outDir);
314
-
315
- fs.writeFileSync(path.join(distDir, 'added.html'), 'new file');
316
- fs.writeFileSync(path.join(distDir, 'changed.css'), 'updated styles');
317
-
318
- const diff: ManifestDiff = {
319
- added: ['added.html'],
320
- changed: ['changed.css'],
321
- removed: ['old.html'],
322
- };
323
-
324
- copyChangedFiles(diff, distDir, outDir);
325
-
326
- expect(fs.existsSync(path.join(outDir, 'added.html'))).toBe(true);
327
- expect(fs.existsSync(path.join(outDir, 'changed.css'))).toBe(true);
328
- });
329
-
330
- test('does not copy removed files', () => {
331
- const distDir = path.join(tmpDir, 'dist');
332
- const outDir = path.join(tmpDir, 'out');
333
- fs.mkdirSync(distDir);
334
- fs.mkdirSync(outDir);
335
-
336
- const diff: ManifestDiff = {
337
- added: [],
338
- changed: [],
339
- removed: ['removed.html'],
340
- };
341
-
342
- copyChangedFiles(diff, distDir, outDir);
343
-
344
- expect(fs.existsSync(path.join(outDir, 'removed.html'))).toBe(false);
345
- });
346
-
347
- test('creates nested directories as needed', () => {
348
- const distDir = path.join(tmpDir, 'dist');
349
- const outDir = path.join(tmpDir, 'out');
350
- fs.mkdirSync(distDir);
351
- fs.mkdirSync(outDir);
352
-
353
- const nested = path.join(distDir, 'deep', 'nested');
354
- fs.mkdirSync(nested, { recursive: true });
355
- fs.writeFileSync(path.join(nested, 'page.html'), 'content');
356
-
357
- const diff: ManifestDiff = {
358
- added: ['deep/nested/page.html'],
359
- changed: [],
360
- removed: [],
361
- };
362
-
363
- copyChangedFiles(diff, distDir, outDir);
364
-
365
- expect(
366
- fs.existsSync(path.join(outDir, 'deep', 'nested', 'page.html')),
367
- ).toBe(true);
368
- });
369
- });