mirror of
https://github.com/backnotprop/plannotator.git
synced 2026-09-14 14:17:26 +08:00
0b167cc478
Bundle-weight optimization of @plannotator/ui for multi-chunk hosts, requested by Workspaces: the Mermaid runtime and Graphviz engine load inside the render effect, the username dictionary sits behind a synchronous identity generator slot, and KaTeX sits behind a math renderer slot with a loader seam on configurePlannotatorUI. Plannotator's own apps import eager entries (math, identity, and Mermaid for the plan editor) so their behavior is unchanged: single-file builds within noise of main, math typeset on first paint, identities from the full dictionary, and the share portal keeps Mermaid in its entry chunk so its failure surface matches main. Built-HTML registration markers guard the eager imports. Hosts that omit the eager entries get the lazy paths, a one-shot automatic re-attempt, and a Retry affordance on the diagram error panel; the module-map limitation of in-page retries is documented. AI-assisted (Claude) under maintainer direction.
86 lines
2.9 KiB
TypeScript
86 lines
2.9 KiB
TypeScript
/**
|
|
* Mermaid runtime slot (utils/mermaid.ts), sibling of math.test.ts.
|
|
*
|
|
* What regresses if these fail:
|
|
* - a filled slot no longer short-circuits the loader, so Plannotator's eager
|
|
* entry (which initialized the runtime at module evaluation) would be
|
|
* followed by a second import and a second initialize;
|
|
* - the empty-slot path stops memoizing, so every diagram on a page imports
|
|
* the runtime again;
|
|
* - a rejected load stays memoized, so the block's re-attempt and the Retry
|
|
* button replay the cached rejection instead of issuing a fresh import.
|
|
*
|
|
* No DOM required. The runtime is a stand-in; the real mermaid never loads.
|
|
* Bun shares one process across files, so the slot is emptied before and
|
|
* after every test through the test hook.
|
|
*/
|
|
import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
|
|
import type { Mermaid } from 'mermaid';
|
|
import {
|
|
__setMermaidRuntimeLoaderForTests,
|
|
getMermaidRuntime,
|
|
getMermaidRuntimeSource,
|
|
loadMermaidRuntime,
|
|
setMermaidRuntime,
|
|
} from './mermaid';
|
|
|
|
const fakeRuntime = { initialize() {}, render: async () => ({ svg: '<svg/>' }) } as unknown as Mermaid;
|
|
|
|
beforeEach(() => {
|
|
__setMermaidRuntimeLoaderForTests(undefined);
|
|
});
|
|
|
|
afterEach(() => {
|
|
__setMermaidRuntimeLoaderForTests(undefined);
|
|
});
|
|
|
|
describe('filled slot', () => {
|
|
test('resolves at once without calling the loader (single-initialize guarantee)', async () => {
|
|
let loads = 0;
|
|
__setMermaidRuntimeLoaderForTests(async () => {
|
|
loads += 1;
|
|
return fakeRuntime;
|
|
});
|
|
setMermaidRuntime(fakeRuntime, 'plannotator-mermaid-eager');
|
|
|
|
expect(getMermaidRuntime()).toBe(fakeRuntime);
|
|
expect(getMermaidRuntimeSource()).toBe('plannotator-mermaid-eager');
|
|
expect(await loadMermaidRuntime()).toBe(fakeRuntime);
|
|
expect(await loadMermaidRuntime()).toBe(fakeRuntime);
|
|
expect(loads).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('empty slot', () => {
|
|
test('calls the loader once, memoizes, and fills the slot with source loader', async () => {
|
|
let loads = 0;
|
|
__setMermaidRuntimeLoaderForTests(async () => {
|
|
loads += 1;
|
|
return fakeRuntime;
|
|
});
|
|
expect(getMermaidRuntime()).toBeNull();
|
|
|
|
const [a, b] = await Promise.all([loadMermaidRuntime(), loadMermaidRuntime()]);
|
|
expect(a).toBe(fakeRuntime);
|
|
expect(b).toBe(fakeRuntime);
|
|
expect(await loadMermaidRuntime()).toBe(fakeRuntime);
|
|
expect(loads).toBe(1);
|
|
expect(getMermaidRuntime()).toBe(fakeRuntime);
|
|
expect(getMermaidRuntimeSource()).toBe('loader');
|
|
});
|
|
|
|
test('a rejected load is dropped so the next call re-attempts', async () => {
|
|
let attempts = 0;
|
|
__setMermaidRuntimeLoaderForTests(async () => {
|
|
attempts += 1;
|
|
if (attempts === 1) throw new Error('chunk failed');
|
|
return fakeRuntime;
|
|
});
|
|
|
|
await expect(loadMermaidRuntime()).rejects.toThrow('chunk failed');
|
|
expect(getMermaidRuntime()).toBeNull();
|
|
expect(await loadMermaidRuntime()).toBe(fakeRuntime);
|
|
expect(attempts).toBe(2);
|
|
});
|
|
});
|