@mintlify/previewing 4.0.1462 → 4.0.1463

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 (49) hide show
  1. package/dist/__tests__/listener-graphql-packages.test.js +68 -0
  2. package/dist/__tests__/listener-package-manifest.test.d.ts +1 -0
  3. package/dist/__tests__/listener-package-manifest.test.js +103 -0
  4. package/dist/__tests__/packages-builder.test.d.ts +1 -0
  5. package/dist/__tests__/packages-builder.test.js +88 -0
  6. package/dist/__tests__/packages-collect.test.d.ts +1 -0
  7. package/dist/__tests__/packages-collect.test.js +387 -0
  8. package/dist/__tests__/packages-dev-snippets.test.d.ts +1 -0
  9. package/dist/__tests__/packages-dev-snippets.test.js +457 -0
  10. package/dist/__tests__/packages-dev.test.d.ts +1 -0
  11. package/dist/__tests__/packages-dev.test.js +1304 -0
  12. package/dist/__tests__/packages-flag.test.d.ts +1 -0
  13. package/dist/__tests__/packages-flag.test.js +146 -0
  14. package/dist/__tests__/packages-imports.test.d.ts +1 -0
  15. package/dist/__tests__/packages-imports.test.js +68 -0
  16. package/dist/__tests__/packages-island-errors.test.d.ts +1 -0
  17. package/dist/__tests__/packages-island-errors.test.js +128 -0
  18. package/dist/__tests__/packages-serve.test.d.ts +1 -0
  19. package/dist/__tests__/packages-serve.test.js +73 -0
  20. package/dist/__tests__/packages-setup.test.js +65 -7
  21. package/dist/local-preview/index.js +6 -0
  22. package/dist/local-preview/listener/index.d.ts +1 -0
  23. package/dist/local-preview/listener/index.js +10 -1
  24. package/dist/local-preview/listener/regenerateAllSnippets.js +2 -0
  25. package/dist/local-preview/listener/resolve-imports-from-import-closure.js +3 -0
  26. package/dist/local-preview/listener/resolveAllImports.js +2 -0
  27. package/dist/local-preview/packages/dev.d.ts +88 -0
  28. package/dist/local-preview/packages/dev.js +478 -0
  29. package/dist/local-preview/packages/index.d.ts +8 -0
  30. package/dist/local-preview/packages/index.js +89 -0
  31. package/dist/local-preview/packages/serve.d.ts +8 -0
  32. package/dist/local-preview/packages/serve.js +41 -0
  33. package/dist/local-preview/packages/state.d.ts +6 -0
  34. package/dist/local-preview/packages/state.js +6 -0
  35. package/dist/local-preview/run.js +8 -0
  36. package/dist/logging-state.d.ts +1 -0
  37. package/dist/logging-state.js +5 -0
  38. package/dist/packages/builder.d.ts +2 -0
  39. package/dist/packages/builder.js +1 -0
  40. package/dist/packages/index.d.ts +0 -1
  41. package/dist/packages/index.js +0 -1
  42. package/dist/packages/setup.d.ts +2 -0
  43. package/dist/packages/setup.js +5 -1
  44. package/dist/tsconfig.tsbuildinfo +1 -1
  45. package/package.json +2 -2
  46. package/dist/__tests__/packages-check.test.js +0 -254
  47. package/dist/packages/check.d.ts +0 -25
  48. package/dist/packages/check.js +0 -132
  49. /package/dist/__tests__/{packages-check.test.d.ts → listener-graphql-packages.test.d.ts} +0 -0
@@ -0,0 +1,457 @@
1
+ import { findAndRemoveImports, getFileCategory, parseFrontmatter } from '@mintlify/common';
2
+ import { getMdx } from '@mintlify/common/server-mdx';
3
+ import { createPage, preparseMdxTree } from '@mintlify/prebuild';
4
+ import fse from 'fs-extra';
5
+ import { mkdtemp, readdir, rm } from 'fs/promises';
6
+ import { tmpdir } from 'os';
7
+ import { join } from 'path';
8
+ const testPaths = vi.hoisted(() => ({ cmd: '', public: '', props: '' }));
9
+ vi.mock('../constants.js', () => ({
10
+ get CMD_EXEC_PATH() {
11
+ return testPaths.cmd;
12
+ },
13
+ get NEXT_PUBLIC_PATH() {
14
+ return testPaths.public;
15
+ },
16
+ get NEXT_PROPS_PATH() {
17
+ return testPaths.props;
18
+ },
19
+ }));
20
+ vi.mock('../local-preview/listener/utils.js', () => ({
21
+ getCurrentVariables: vi.fn(async () => ({})),
22
+ getMintIgnoreGlobs: vi.fn(() => []),
23
+ handleParseError: vi.fn(),
24
+ normalizePathForComparison: (path) => (path.startsWith('/') ? path : `/${path}`).toLowerCase(),
25
+ suppressParseError: vi.fn(),
26
+ }));
27
+ import { generateDependentSnippets } from '../local-preview/listener/generateDependentSnippets.js';
28
+ import { generatePagesWithImports } from '../local-preview/listener/generatePagesWithImports.js';
29
+ import { initializeImportCache, updateImportCacheForFile, } from '../local-preview/listener/importCache.js';
30
+ import { regenerateAllSnippets } from '../local-preview/listener/regenerateAllSnippets.js';
31
+ import { ensurePageCompiled, markOnDemandPagesReady } from '../local-preview/on-demand-page.js';
32
+ import { PACKAGE_BUILD_PROPS_FILE, PackagesDev } from '../local-preview/packages/dev.js';
33
+ import { getPackageImportOptions, setPackageImportOptions, } from '../local-preview/packages/state.js';
34
+ import { PackageIslandCollector } from '../packages/collect.js';
35
+ const PLAIN_TOGGLE = 'export const Toggle = () => <span>plain toggle</span>;\n';
36
+ const PACKAGE_TOGGLE = 'import { Switch } from "@acme/ui";\n\nexport const Toggle = () => <Switch />;\n';
37
+ const BROKEN_TOGGLE = 'import { Switch } from "@acme/ui";\n\nexport const Toggle = () => <Switch;\n';
38
+ const PLAIN_WRAPPER = 'export const Wrapper = () => <span>plain wrapper</span>;\n';
39
+ const TOGGLE_WRAPPER = 'import { Toggle } from "/snippets/toggle.jsx";\n\nexport const Wrapper = () => <Toggle />;\n';
40
+ const TOGGLE_OUTPUTS = [
41
+ 'snippets/wrapper.jsx',
42
+ 'snippets/intro.mdx',
43
+ 'toggle-page.mdx',
44
+ 'wrapper-page.mdx',
45
+ ];
46
+ const WRAPPER_OUTPUTS = ['snippets/wrapper.jsx', 'wrapper-page.mdx'];
47
+ const TOGGLE_IMPORT = 'import {Toggle} from "/snippets/toggle.jsx";';
48
+ const WRAPPER_IMPORT = 'import {Wrapper} from "/snippets/wrapper.jsx";';
49
+ let heldBuild;
50
+ let releaseBuild = () => { };
51
+ const fakeBuild = async ({ islands, outDir, }) => {
52
+ await heldBuild;
53
+ await fse.ensureDir(outDir);
54
+ const built = {};
55
+ for (const { id } of islands) {
56
+ await fse.outputFile(join(outDir, 'client/islands', `${id}.js`), 'export {}');
57
+ built[id] = {
58
+ entry: `client/islands/${id}.js`,
59
+ prefix: `mi-${id}-`,
60
+ html: `<svg data-id="${id}"></svg>`,
61
+ };
62
+ }
63
+ return {
64
+ ok: true,
65
+ manifest: {
66
+ version: 1,
67
+ builderVersion: 'test',
68
+ buildId: `b${islands.length}`,
69
+ depsHash: 'd',
70
+ islandsHash: 'i',
71
+ react: { version: '18.3.1', domVersion: '18.3.1' },
72
+ packages: {},
73
+ css: [],
74
+ clientAccess: {
75
+ version: 1,
76
+ files: Object.fromEntries(islands.map(({ id }) => [`client/islands/${id}.js`, [id]])),
77
+ globalFiles: [],
78
+ },
79
+ islands: built,
80
+ files: islands.map(({ id }) => ({
81
+ path: `client/islands/${id}.js`,
82
+ bytes: 9,
83
+ gzipBytes: 9,
84
+ integrity: 'sha384-fixture',
85
+ })),
86
+ diagnostics: [],
87
+ },
88
+ stats: {
89
+ installMs: 0,
90
+ bundleMs: 0,
91
+ renderMs: 0,
92
+ totalMs: 0,
93
+ clientBytes: 0,
94
+ clientGzipBytes: 0,
95
+ serverBytes: 0,
96
+ islandClientGzipBytes: {},
97
+ reactDirs: { react: [], 'react-dom': [] },
98
+ },
99
+ localFiles: [],
100
+ missingLocalFiles: [],
101
+ };
102
+ };
103
+ const until = async (condition) => {
104
+ for (let i = 0; i < 300; i++) {
105
+ if (condition())
106
+ return;
107
+ await new Promise((resolve) => setTimeout(resolve, 10));
108
+ }
109
+ throw new Error('condition not met');
110
+ };
111
+ describe('package snippet membership in the mint dev watcher', () => {
112
+ let root;
113
+ let dev;
114
+ let builds;
115
+ const docs = (file) => join(testPaths.cmd, file);
116
+ const output = (file) => fse.readFile(join(file.startsWith('snippets/') ? testPaths.public : testPaths.props, file), 'utf8');
117
+ const outputs = (files) => Promise.all(files.map(output));
118
+ const packageSnippets = async () => {
119
+ const published = [...(getPackageImportOptions()?.packageSnippets ?? [])].sort();
120
+ const props = await fse.readJson(join(testPaths.props, PACKAGE_BUILD_PROPS_FILE));
121
+ expect(props.packageSnippets).toEqual(published);
122
+ return published;
123
+ };
124
+ const expectKept = (content, importLine, inlined) => {
125
+ expect(content).toContain(importLine);
126
+ expect(content).not.toContain(inlined);
127
+ };
128
+ const expectInlined = (content, marker) => {
129
+ expect(content).not.toContain('import');
130
+ expect(content).toContain(marker);
131
+ };
132
+ const changeSnippet = async (file, content) => {
133
+ await fse.outputFile(docs(file), content);
134
+ await updateImportCacheForFile(testPaths.cmd, file);
135
+ await dev.onFileChange(file, 'snippet-v2');
136
+ };
137
+ const regenerate = async (file) => {
138
+ const content = await fse.readFile(docs(file), 'utf8');
139
+ const tree = await preparseMdxTree(content, testPaths.cmd, docs(file), vi.fn());
140
+ const updated = await generateDependentSnippets(file, await findAndRemoveImports(tree));
141
+ await generatePagesWithImports(new Set(updated));
142
+ };
143
+ const saveSnippet = async (file, content) => {
144
+ await changeSnippet(file, content);
145
+ await regenerate(file);
146
+ };
147
+ const editSnippet = async (file, content) => {
148
+ const settled = builds + 1;
149
+ await saveSnippet(file, content);
150
+ await until(() => builds >= settled);
151
+ };
152
+ const compilePage = async (file) => {
153
+ const { pageContent } = await createPage(file, await fse.readFile(docs(file), 'utf8'), testPaths.cmd, [], []);
154
+ await fse.outputFile(join(testPaths.props, file), pageContent);
155
+ };
156
+ const legacySnippets = async () => {
157
+ const dir = join(testPaths.public, '_snippets');
158
+ const names = (await fse.pathExists(dir)) ? await readdir(dir) : [];
159
+ return Promise.all(names.map(async (name) => ({
160
+ snippetFileLocation: name,
161
+ content: await fse.readFile(join(dir, name), 'utf8'),
162
+ })));
163
+ };
164
+ const render = async (page) => {
165
+ const { baseUrl, islands, packageNames, packageSnippets } = await fse.readJson(join(testPaths.props, PACKAGE_BUILD_PROPS_FILE));
166
+ const compiled = await getMdx({
167
+ path: page.replace(/\.mdx$/, ''),
168
+ content: parseFrontmatter(await output(page)).body,
169
+ metadata: {},
170
+ snippets: await legacySnippets(),
171
+ subdomain: 'local',
172
+ codeStyling: undefined,
173
+ tailwindSelectors: [],
174
+ packageBuild: { baseUrl, islands, packageNames, packageSnippets },
175
+ });
176
+ expect(compiled.parseFailed).toBeUndefined();
177
+ return compiled.mdxExtracts.islands ?? {};
178
+ };
179
+ const holdBuilds = () => {
180
+ heldBuild = new Promise((resolve) => {
181
+ releaseBuild = () => {
182
+ heldBuild = undefined;
183
+ resolve();
184
+ };
185
+ });
186
+ };
187
+ const start = async ({ toggle, wrapper }) => {
188
+ await fse.outputFile(docs('snippets/toggle.jsx'), toggle);
189
+ await fse.outputFile(docs('snippets/wrapper.jsx'), wrapper);
190
+ await dev.init();
191
+ await initializeImportCache(testPaths.cmd);
192
+ const snippets = await regenerateAllSnippets();
193
+ await generatePagesWithImports(new Set(snippets));
194
+ builds = 0;
195
+ };
196
+ beforeEach(async () => {
197
+ root = await mkdtemp(join(tmpdir(), 'packages-dev-snippets-'));
198
+ testPaths.cmd = join(root, 'docs');
199
+ testPaths.public = join(root, 'public');
200
+ testPaths.props = join(root, 'props');
201
+ builds = 0;
202
+ await fse.outputJson(docs('docs.json'), { name: 'Docs', packages: {} });
203
+ await fse.outputJson(docs('package.json'), { dependencies: { '@acme/ui': '1.0.0' } });
204
+ await fse.outputFile(docs('snippets/intro.mdx'), 'import { Toggle } from "/snippets/toggle.jsx";\n\nIntro text\n\n<Toggle />\n');
205
+ await fse.outputFile(docs('toggle-page.mdx'), 'import { Toggle } from "/snippets/toggle.jsx";\n\n<Toggle />\n');
206
+ await fse.outputFile(docs('wrapper-page.mdx'), 'import { Wrapper } from "/snippets/wrapper.jsx";\n\n<Wrapper />\n');
207
+ vi.spyOn(console, 'log').mockImplementation(() => { });
208
+ dev = new PackagesDev({
209
+ docsRoot: testPaths.cmd,
210
+ buildsDir: join(root, 'builds'),
211
+ propsPath: testPaths.props,
212
+ onRebuilt: () => {
213
+ builds++;
214
+ },
215
+ regenerateSnippets: regenerateAllSnippets,
216
+ reporter: {
217
+ building: () => { },
218
+ built: () => { },
219
+ failed: () => { },
220
+ missingNodeModules: () => { },
221
+ islandErrors: () => { },
222
+ configError: () => { },
223
+ },
224
+ debounceMs: 200,
225
+ build: fakeBuild,
226
+ });
227
+ });
228
+ afterEach(async () => {
229
+ releaseBuild();
230
+ await dev.close();
231
+ setPackageImportOptions(undefined);
232
+ vi.mocked(console.log).mockRestore();
233
+ await rm(root, { recursive: true, force: true });
234
+ });
235
+ it('turns a snippet into an island when it starts importing a package and back when it stops', async () => {
236
+ await start({ toggle: PLAIN_TOGGLE, wrapper: TOGGLE_WRAPPER });
237
+ const initial = await outputs(TOGGLE_OUTPUTS);
238
+ expect(await packageSnippets()).toEqual([]);
239
+ expectInlined(await output('snippets/wrapper.jsx'), 'plain toggle');
240
+ expectInlined(await output('toggle-page.mdx'), 'plain toggle');
241
+ await editSnippet('snippets/toggle.jsx', PACKAGE_TOGGLE);
242
+ expect(await packageSnippets()).toEqual(['/snippets/toggle.jsx', '/snippets/wrapper.jsx']);
243
+ expectKept(await output('snippets/wrapper.jsx'), TOGGLE_IMPORT, 'export const Toggle');
244
+ expectKept(await output('snippets/intro.mdx'), TOGGLE_IMPORT, 'export const Toggle');
245
+ expectKept(await output('toggle-page.mdx'), TOGGLE_IMPORT, 'export const Toggle');
246
+ expectKept(await output('wrapper-page.mdx'), WRAPPER_IMPORT, 'export const Wrapper');
247
+ expect(builds).toBe(1);
248
+ await editSnippet('snippets/toggle.jsx', PLAIN_TOGGLE);
249
+ expect(await packageSnippets()).toEqual([]);
250
+ expect(await outputs(TOGGLE_OUTPUTS)).toEqual(initial);
251
+ expect(builds).toBe(2);
252
+ });
253
+ it('makes a wrapper an island when it starts importing a package snippet and back when it stops', async () => {
254
+ await start({ toggle: PACKAGE_TOGGLE, wrapper: PLAIN_WRAPPER });
255
+ const initial = await outputs(WRAPPER_OUTPUTS);
256
+ expect(await packageSnippets()).toEqual(['/snippets/toggle.jsx']);
257
+ expectInlined(await output('wrapper-page.mdx'), 'plain wrapper');
258
+ await editSnippet('snippets/wrapper.jsx', TOGGLE_WRAPPER);
259
+ expect(await packageSnippets()).toEqual(['/snippets/toggle.jsx', '/snippets/wrapper.jsx']);
260
+ expectKept(await output('snippets/wrapper.jsx'), TOGGLE_IMPORT, 'export const Toggle');
261
+ expectKept(await output('wrapper-page.mdx'), WRAPPER_IMPORT, 'export const Wrapper');
262
+ await editSnippet('snippets/wrapper.jsx', PLAIN_WRAPPER);
263
+ expect(await packageSnippets()).toEqual(['/snippets/toggle.jsx']);
264
+ expect(await outputs(WRAPPER_OUTPUTS)).toEqual(initial);
265
+ expect(builds).toBe(2);
266
+ });
267
+ it('keeps a package snippet and its importers on islands while it fails to parse, until it is deleted', async () => {
268
+ const islands = ['/snippets/toggle.jsx', '/snippets/wrapper.jsx'];
269
+ await start({ toggle: PACKAGE_TOGGLE, wrapper: TOGGLE_WRAPPER });
270
+ const initial = await outputs(TOGGLE_OUTPUTS);
271
+ expect(await packageSnippets()).toEqual(islands);
272
+ await editSnippet('snippets/toggle.jsx', BROKEN_TOGGLE);
273
+ expect(await packageSnippets()).toEqual(islands);
274
+ const parseError = 'A parsing error occurred';
275
+ expectKept(await output('snippets/wrapper.jsx'), TOGGLE_IMPORT, parseError);
276
+ expectKept(await output('snippets/intro.mdx'), TOGGLE_IMPORT, parseError);
277
+ expectKept(await output('toggle-page.mdx'), TOGGLE_IMPORT, parseError);
278
+ expectKept(await output('wrapper-page.mdx'), WRAPPER_IMPORT, parseError);
279
+ await editSnippet('snippets/toggle.jsx', PACKAGE_TOGGLE);
280
+ expect(await packageSnippets()).toEqual(islands);
281
+ expect(await outputs(TOGGLE_OUTPUTS)).toEqual(initial);
282
+ const settled = builds + 1;
283
+ await fse.remove(docs('snippets/toggle.jsx'));
284
+ await dev.onFileChange('snippets/toggle.jsx', 'snippet-v2');
285
+ await until(() => builds >= settled);
286
+ expect(await packageSnippets()).toEqual([]);
287
+ });
288
+ it('keeps a package snippet that fails to parse on islands when package.json and docs.json are saved', async () => {
289
+ const islands = ['/snippets/toggle.jsx', '/snippets/wrapper.jsx'];
290
+ await start({ toggle: PACKAGE_TOGGLE, wrapper: TOGGLE_WRAPPER });
291
+ const initial = await outputs(TOGGLE_OUTPUTS);
292
+ await editSnippet('snippets/toggle.jsx', BROKEN_TOGGLE);
293
+ const broken = await outputs(TOGGLE_OUTPUTS);
294
+ await fse.outputJson(docs('package.json'), { dependencies: { '@acme/ui': '1.0.0' } });
295
+ await dev.onFileChange('package.json', getFileCategory('package.json'));
296
+ await fse.outputJson(docs('docs.json'), { name: 'Docs', packages: {} });
297
+ await dev.onFileChange('docs.json', 'docsConfig');
298
+ expect(await packageSnippets()).toEqual(islands);
299
+ expect(await outputs(TOGGLE_OUTPUTS)).toEqual(broken);
300
+ expectKept(await output('snippets/wrapper.jsx'), TOGGLE_IMPORT, 'A parsing error occurred');
301
+ expectKept(await output('toggle-page.mdx'), TOGGLE_IMPORT, 'A parsing error occurred');
302
+ await editSnippet('snippets/toggle.jsx', PACKAGE_TOGGLE);
303
+ expect(await packageSnippets()).toEqual(islands);
304
+ expect(await outputs(TOGGLE_OUTPUTS)).toEqual(initial);
305
+ });
306
+ it('drops a package snippet that failed to parse once it is deleted before docs.json is saved', async () => {
307
+ await start({ toggle: PACKAGE_TOGGLE, wrapper: TOGGLE_WRAPPER });
308
+ await editSnippet('snippets/toggle.jsx', BROKEN_TOGGLE);
309
+ await fse.outputJson(docs('docs.json'), { name: 'Docs', packages: {} });
310
+ await dev.onFileChange('docs.json', 'docsConfig');
311
+ expect(await packageSnippets()).toEqual(['/snippets/toggle.jsx', '/snippets/wrapper.jsx']);
312
+ await fse.remove(docs('snippets/toggle.jsx'));
313
+ await fse.outputJson(docs('docs.json'), { name: 'Docs', packages: {} });
314
+ await dev.onFileChange('docs.json', 'docsConfig');
315
+ expect(await packageSnippets()).toEqual([]);
316
+ });
317
+ it('keeps a package snippet that broke while docs.json was invalid once docs.json is fixed', async () => {
318
+ const islands = ['/snippets/toggle.jsx', '/snippets/wrapper.jsx'];
319
+ await start({ toggle: PACKAGE_TOGGLE, wrapper: TOGGLE_WRAPPER });
320
+ await fse.outputFile(docs('docs.json'), '{ "name": ');
321
+ await dev.onFileChange('docs.json', 'docsConfig');
322
+ await editSnippet('snippets/toggle.jsx', BROKEN_TOGGLE);
323
+ expect(await packageSnippets()).toEqual(islands);
324
+ const broken = await outputs(TOGGLE_OUTPUTS);
325
+ await fse.outputJson(docs('docs.json'), { name: 'Docs', packages: {} });
326
+ await dev.onFileChange('docs.json', 'docsConfig');
327
+ expect(await packageSnippets()).toEqual(islands);
328
+ expect(await outputs(TOGGLE_OUTPUTS)).toEqual(broken);
329
+ });
330
+ it('regenerates snippet copies when packages are enabled, gain a dependency, or are disabled', async () => {
331
+ const BADGE_IMPORT = 'import {Badge} from "@acme/ui";';
332
+ await fse.outputJson(docs('docs.json'), { name: 'Docs' });
333
+ await fse.outputJson(docs('package.json'), { dependencies: {} });
334
+ await fse.outputFile(docs('snippets/badge.mdx'), `${BADGE_IMPORT}\n\n<Badge />\n`);
335
+ await start({ toggle: PACKAGE_TOGGLE, wrapper: PLAIN_WRAPPER });
336
+ const initial = await outputs(['snippets/intro.mdx', 'snippets/badge.mdx']);
337
+ expect(getPackageImportOptions()).toBeUndefined();
338
+ expectInlined(await output('snippets/intro.mdx'), 'export const Toggle');
339
+ expect(await output('snippets/badge.mdx')).not.toContain(BADGE_IMPORT);
340
+ await fse.outputJson(docs('docs.json'), { name: 'Docs', packages: {} });
341
+ await dev.onFileChange('docs.json', 'docsConfig');
342
+ expect(await packageSnippets()).toEqual(['/snippets/toggle.jsx']);
343
+ expectKept(await output('snippets/intro.mdx'), TOGGLE_IMPORT, 'export const Toggle');
344
+ await fse.outputJson(docs('package.json'), { dependencies: { '@acme/ui': '1.0.0' } });
345
+ await dev.onFileChange('package.json', getFileCategory('package.json'));
346
+ expect(await output('snippets/badge.mdx')).toContain(BADGE_IMPORT);
347
+ await fse.outputJson(docs('docs.json'), { name: 'Docs' });
348
+ await dev.onFileChange('docs.json', 'docsConfig');
349
+ expect(getPackageImportOptions()).toBeUndefined();
350
+ expect(await outputs(['snippets/intro.mdx', 'snippets/badge.mdx'])).toEqual(initial);
351
+ });
352
+ it('renders placeholders for the islands a snippet edit adds until the rebuild finishes', async () => {
353
+ await start({ toggle: PLAIN_TOGGLE, wrapper: TOGGLE_WRAPPER });
354
+ expect(await render('toggle-page.mdx')).toEqual({});
355
+ holdBuilds();
356
+ const settled = builds + 1;
357
+ await saveSnippet('snippets/toggle.jsx', PACKAGE_TOGGLE);
358
+ const pending = { ...(await render('toggle-page.mdx')), ...(await render('wrapper-page.mdx')) };
359
+ expect(Object.keys(pending)).toHaveLength(2);
360
+ for (const [id, island] of Object.entries(pending)) {
361
+ expect(island).toEqual({ prefix: `mi-${id}-` });
362
+ }
363
+ releaseBuild();
364
+ await until(() => builds >= settled);
365
+ const built = { ...(await render('toggle-page.mdx')), ...(await render('wrapper-page.mdx')) };
366
+ expect(Object.keys(built)).toEqual(Object.keys(pending));
367
+ for (const [id, island] of Object.entries(built)) {
368
+ expect(island.html).toBe(`<svg data-id="${id}"></svg>`);
369
+ }
370
+ });
371
+ it('renders placeholders for the islands a <Snippet file> edit adds until the rebuild finishes', async () => {
372
+ await fse.outputFile(docs('_snippets/badge.mdx'), 'Plain badge\n');
373
+ await fse.outputFile(docs('badge-page.mdx'), '# Badge\n\n<Snippet file="badge.mdx" />\n');
374
+ await start({ toggle: PLAIN_TOGGLE, wrapper: PLAIN_WRAPPER });
375
+ await compilePage('badge-page.mdx');
376
+ await fse.copy(docs('_snippets/badge.mdx'), join(testPaths.public, '_snippets/badge.mdx'));
377
+ expect(await render('badge-page.mdx')).toEqual({});
378
+ holdBuilds();
379
+ const settled = builds + 1;
380
+ await fse.outputFile(docs('_snippets/badge.mdx'), 'import { Switch } from "@acme/ui";\n\n<Switch />\n');
381
+ await dev.onFileChange('_snippets/badge.mdx', 'snippet');
382
+ await fse.copy(docs('_snippets/badge.mdx'), join(testPaths.public, '_snippets/badge.mdx'));
383
+ const pending = await render('badge-page.mdx');
384
+ const [id] = Object.keys(pending);
385
+ expect(pending).toEqual({ [id ?? '']: { prefix: `mi-${id}-` } });
386
+ releaseBuild();
387
+ await until(() => builds >= settled);
388
+ expect(await render('badge-page.mdx')).toEqual({
389
+ [id ?? '']: { prefix: `mi-${id}-`, html: `<svg data-id="${id}"></svg>` },
390
+ });
391
+ });
392
+ it('keeps rendering the islands a snippet edit removes until the watcher recompiles the pages', async () => {
393
+ await fse.outputFile(docs('snippets/switches.mdx'), 'import { Switch } from "@acme/ui";\n\n<Switch />\n');
394
+ await fse.outputFile(docs('switch-page.mdx'), 'import Switches from "/snippets/switches.mdx";\n\n<Switches />\n');
395
+ await fse.outputFile(docs('_snippets/badge.mdx'), 'import { Switch } from "@acme/ui";\n\n<Switch size="sm" />\n');
396
+ await fse.outputFile(docs('badge-page.mdx'), '<Snippet file="badge.mdx" />\n');
397
+ await start({ toggle: PLAIN_TOGGLE, wrapper: PLAIN_WRAPPER });
398
+ await compilePage('badge-page.mdx');
399
+ await fse.copy(docs('_snippets/badge.mdx'), join(testPaths.public, '_snippets/badge.mdx'));
400
+ const switches = await render('switch-page.mdx');
401
+ const badge = await render('badge-page.mdx');
402
+ expect(Object.keys({ ...switches, ...badge })).toHaveLength(2);
403
+ holdBuilds();
404
+ await changeSnippet('snippets/switches.mdx', 'No switch\n');
405
+ expect(await render('switch-page.mdx')).toEqual(switches);
406
+ await regenerate('snippets/switches.mdx');
407
+ expect(await render('switch-page.mdx')).toEqual({});
408
+ await fse.outputFile(docs('_snippets/badge.mdx'), 'Plain badge\n');
409
+ await dev.onFileChange('_snippets/badge.mdx', 'snippet');
410
+ expect(await render('badge-page.mdx')).toEqual(badge);
411
+ await fse.copy(docs('_snippets/badge.mdx'), join(testPaths.public, '_snippets/badge.mdx'));
412
+ expect(await render('badge-page.mdx')).toEqual({});
413
+ });
414
+ it('recompiles pages on request after a package snippet change, and keeps serving ones that failed to compile', async () => {
415
+ markOnDemandPagesReady();
416
+ await fse.outputFile(docs('broken.mdx'), '# Broken\n\n<div>\n\nunclosed {\n');
417
+ await fse.outputFile(docs('plain.mdx'), '# Plain\n');
418
+ await start({ toggle: PLAIN_TOGGLE, wrapper: TOGGLE_WRAPPER });
419
+ await ensurePageCompiled('/broken', { silent: true });
420
+ await ensurePageCompiled('/plain', { silent: true });
421
+ const broken = await output('broken.mdx');
422
+ const compiledAt = (await fse.stat(join(testPaths.props, 'plain.mdx'))).mtimeMs;
423
+ await saveSnippet('snippets/toggle.jsx', PACKAGE_TOGGLE);
424
+ await ensurePageCompiled('/broken', { silent: true });
425
+ await ensurePageCompiled('/plain', { silent: true });
426
+ expect(await output('broken.mdx')).toBe(broken);
427
+ expect((await fse.stat(join(testPaths.props, 'plain.mdx'))).mtimeMs).toBeGreaterThanOrEqual(compiledAt);
428
+ });
429
+ it('drops a snippet update that finishes after packages are disabled', async () => {
430
+ await start({ toggle: PLAIN_TOGGLE, wrapper: TOGGLE_WRAPPER });
431
+ const collectAll = PackageIslandCollector.prototype.collectAll;
432
+ let release = () => { };
433
+ const held = new Promise((resolve) => {
434
+ release = resolve;
435
+ });
436
+ const spy = vi
437
+ .spyOn(PackageIslandCollector.prototype, 'collectAll')
438
+ .mockImplementation(async function () {
439
+ await held;
440
+ return collectAll.call(this);
441
+ });
442
+ try {
443
+ await fse.outputFile(docs('snippets/toggle.jsx'), PACKAGE_TOGGLE);
444
+ const change = dev.onFileChange('snippets/toggle.jsx', 'snippet-v2');
445
+ await fse.outputJson(docs('docs.json'), { name: 'Docs' });
446
+ await dev.onFileChange('docs.json', 'docsConfig');
447
+ release();
448
+ await change;
449
+ }
450
+ finally {
451
+ spy.mockRestore();
452
+ }
453
+ expect(dev.enabled).toBe(false);
454
+ expect(getPackageImportOptions()).toBeUndefined();
455
+ expect(await fse.pathExists(join(testPaths.props, PACKAGE_BUILD_PROPS_FILE))).toBe(false);
456
+ });
457
+ });
@@ -0,0 +1 @@
1
+ export {};