@embedpdf/core 3.0.0-next.13 → 3.0.0-next.15

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,401 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_settings = require("./settings-maKtf237.cjs");
3
+ let _embedpdf_engine_core_runtime = require("@embedpdf/engine-core/runtime");
4
+ //#region src/testing.ts
5
+ const layoutOf = (page, index) => {
6
+ const size = page.size ?? (page.crop ? {
7
+ width: page.crop.right - page.crop.left,
8
+ height: page.crop.top - page.crop.bottom
9
+ } : {
10
+ width: 612,
11
+ height: 792
12
+ });
13
+ const crop = page.crop ?? {
14
+ left: 0,
15
+ bottom: 0,
16
+ right: size.width,
17
+ top: size.height
18
+ };
19
+ const visible = {
20
+ x: 0,
21
+ y: 0,
22
+ width: crop.right - crop.left,
23
+ height: crop.top - crop.bottom
24
+ };
25
+ return {
26
+ index,
27
+ ref: page.ref,
28
+ label: page.label ?? null,
29
+ size,
30
+ rotation: page.rotation ?? 0,
31
+ userUnit: page.userUnit ?? 1,
32
+ tabOrder: null,
33
+ boxes: {
34
+ media: { ...visible },
35
+ crop: { ...visible },
36
+ bleed: { ...visible },
37
+ trim: { ...visible },
38
+ art: { ...visible }
39
+ },
40
+ pdfCropBox: crop
41
+ };
42
+ };
43
+ /** A read-only document registry holding the test's one document. */
44
+ function testDocuments(meta) {
45
+ const info = {
46
+ id: meta.id,
47
+ status: "ready",
48
+ pageCount: meta.pageCount,
49
+ hasUnsavedChanges: false
50
+ };
51
+ const known = (id) => id === void 0 || id === meta.id;
52
+ const pagesOf = (id) => known(id) ? meta.pages : [];
53
+ const unsupported = (verb) => () => {
54
+ throw new require_settings.PluginError("unsupported", "documents", `${verb} is not available in a test context`);
55
+ };
56
+ const never = () => () => {};
57
+ return {
58
+ getActiveId: () => meta.id,
59
+ getActive: () => info,
60
+ list: () => [info],
61
+ get: (id) => known(id) ? info : null,
62
+ has: (id) => known(id),
63
+ getCount: () => 1,
64
+ getOrder: () => [meta.id],
65
+ listPages: pagesOf,
66
+ getPage: (page, id) => require_settings.findPage(pagesOf(id), page),
67
+ getPageIndex: (ref, id) => pagesOf(id).findIndex((page) => (0, _embedpdf_engine_core_runtime.pageRefsEqual)(page.ref, ref)),
68
+ getRevision: (id) => known(id) ? meta.revision : -1,
69
+ canDownload: () => true,
70
+ canPrint: () => true,
71
+ canOpenFiles: () => false,
72
+ open: unsupported("open"),
73
+ retry: unsupported("retry"),
74
+ rename: unsupported("rename"),
75
+ openAll: unsupported("openAll"),
76
+ unlock: unsupported("unlock"),
77
+ close: unsupported("close"),
78
+ closeAll: unsupported("closeAll"),
79
+ setActive: unsupported("setActive"),
80
+ setOrder: unsupported("setOrder"),
81
+ move: unsupported("move"),
82
+ swap: unsupported("swap"),
83
+ download: unsupported("download"),
84
+ downloadLayer: unsupported("downloadLayer"),
85
+ onOpened: never,
86
+ onOpenFailed: never,
87
+ onLocked: never,
88
+ onClosed: never,
89
+ onActiveChanged: never,
90
+ onPagesChanged: never,
91
+ onUnsavedChangesChanged: never
92
+ };
93
+ }
94
+ function createTestContext(options = {}) {
95
+ const id = options.id ?? "test";
96
+ const documentId = options.documentId ?? "doc";
97
+ const report = (error) => console.error(`[${id}]`, error);
98
+ const store = require_settings.createStore(report);
99
+ const instanceId = `${documentId}:${id}`;
100
+ const lease = store.lease(id, options.state, instanceId);
101
+ const cleanups = [];
102
+ const lifetime = new AbortController();
103
+ const settleFlushes = /* @__PURE__ */ new Set();
104
+ const downloadWraps = [];
105
+ const pages = (options.pages ?? []).map(layoutOf);
106
+ const capabilities = new Map(options.capabilities ?? []);
107
+ const documentEvents = require_settings.createEventHook(report);
108
+ const isRealHandle = options.doc != null && Object.getPrototypeOf(options.doc) !== Object.prototype;
109
+ const doc = options.doc === null ? null : isRealHandle ? options.doc : fakeDocument({
110
+ id: documentId,
111
+ events: {
112
+ subscribe: documentEvents.on,
113
+ on: (type, listener) => (0, _embedpdf_engine_core_runtime.subscribeToType)(documentEvents.on, type, listener),
114
+ lastServerId: () => null
115
+ },
116
+ security: {
117
+ allows: () => true,
118
+ allowsAnnotation: () => true,
119
+ allowsField: () => true
120
+ },
121
+ ...options.doc
122
+ });
123
+ const requireDoc = () => {
124
+ if (!doc) throw new require_settings.PluginError("not-ready", id, "the test context has no document");
125
+ return doc;
126
+ };
127
+ const changes = require_settings.createDocumentChanges({
128
+ handle: () => doc,
129
+ notify: store.notify,
130
+ report
131
+ });
132
+ settleFlushes.add(() => changes.settle());
133
+ cleanups.push(() => changes.close());
134
+ const bursts = require_settings.createBurstGate(store);
135
+ const mirrors = [];
136
+ const mirrorEnvironment = () => ({
137
+ doc: requireDoc(),
138
+ changes,
139
+ notify: store.notify,
140
+ lifetime: lifetime.signal,
141
+ cell(name, initial) {
142
+ const cell = store.lease(`${id}/${name}`, initial, instanceId);
143
+ lifetime.signal.addEventListener("abort", () => cell.revoke(), { once: true });
144
+ return cell;
145
+ },
146
+ onDocumentEvent(listener) {
147
+ cleanups.push(requireDoc().events.subscribe(listener));
148
+ },
149
+ report
150
+ });
151
+ const meta = {
152
+ id: documentId,
153
+ instanceId,
154
+ pageCount: pages.length,
155
+ pages,
156
+ revision: 1,
157
+ renderPolicy: { kind: "continuous" },
158
+ hasUnsavedChanges: false
159
+ };
160
+ if (!capabilities.has(require_settings.DocumentsToken)) capabilities.set(require_settings.DocumentsToken, testDocuments(meta));
161
+ const notFound = (ref) => new require_settings.PluginError("not-found", id, `page ${ref.objectNumber} is not in this document`);
162
+ const resolve = (token) => capabilities.has(token) ? capabilities.get(token) : null;
163
+ const require = (token) => {
164
+ const capability = resolve(token);
165
+ if (capability === null) throw new require_settings.PluginError("not-found", id, `no capability for ${token.name} in the test context`);
166
+ return capability;
167
+ };
168
+ const queues = /* @__PURE__ */ new Map();
169
+ const lanes = /* @__PURE__ */ new Map();
170
+ const settingsStore = options.settings ? require_settings.createSettingsStore(options.settings, store.notify, report) : null;
171
+ if (settingsStore) cleanups.push(() => settingsStore.dispose());
172
+ const settings = settingsStore?.forInstance((teardown) => cleanups.push(teardown)) ?? null;
173
+ const context = {
174
+ id,
175
+ instanceId: meta.instanceId,
176
+ engine: options.engine ?? {},
177
+ documentId,
178
+ get doc() {
179
+ return doc;
180
+ },
181
+ documentHandle: () => doc,
182
+ subscribe: store.subscribe,
183
+ state: {
184
+ get: () => lease.read(),
185
+ update: (transition, ...args) => {
186
+ lease.write(transition(lease.read(), ...args));
187
+ },
188
+ onChange: lease.onChange
189
+ },
190
+ settings: () => {
191
+ if (!settings) throw new Error(`[${id}] the test context declares no settings`);
192
+ return settings;
193
+ },
194
+ notify: store.notify,
195
+ watch: (select, handler, isEqual = Object.is) => {
196
+ let previous = select();
197
+ const off = store.subscribe(() => {
198
+ const next = select();
199
+ if (isEqual(previous, next)) return;
200
+ const prior = previous;
201
+ previous = next;
202
+ handler(next, prior);
203
+ });
204
+ cleanups.push(off);
205
+ return off;
206
+ },
207
+ mirror: (spec) => {
208
+ const controller = require_settings.createMirror(spec, mirrorEnvironment());
209
+ mirrors.push(controller);
210
+ return controller.mirror;
211
+ },
212
+ pageMirror: (spec) => require_settings.createPageMirror(spec, mirrorEnvironment()),
213
+ changes: changes.forPlugin(id),
214
+ document: () => meta,
215
+ get: require,
216
+ tryGet: resolve,
217
+ forDocument: (token) => require(token),
218
+ tryForDocument: (token) => resolve(token),
219
+ cleanup: (fn) => {
220
+ cleanups.push(fn);
221
+ },
222
+ clock: require_settings.instanceClock(options.clock ?? require_settings.timerClock, lifetime.signal),
223
+ onSettle: (flush) => {
224
+ settleFlushes.add(flush);
225
+ cleanups.push(() => {
226
+ settleFlushes.delete(flush);
227
+ });
228
+ },
229
+ aroundDownload: (wrap) => {
230
+ downloadWraps.push(wrap);
231
+ cleanups.push(() => {
232
+ const index = downloadWraps.indexOf(wrap);
233
+ if (index >= 0) downloadWraps.splice(index, 1);
234
+ });
235
+ },
236
+ events: { source: () => {
237
+ const hook = require_settings.createEventHook((error) => console.error(`[${id}] listener failed:`, error));
238
+ cleanups.push(() => hook.dispose());
239
+ return hook;
240
+ } },
241
+ listen: (source, listener) => {
242
+ const off = typeof source === "function" ? source(listener) : source.subscribe(listener);
243
+ cleanups.push(off);
244
+ },
245
+ waitFor: (predicate, waitOptions) => new Promise((resolveWait, reject) => {
246
+ if (predicate()) return resolveWait();
247
+ const off = context.subscribe(() => {
248
+ if (!predicate()) return;
249
+ off();
250
+ resolveWait();
251
+ });
252
+ waitOptions?.signal?.addEventListener("abort", () => {
253
+ off();
254
+ reject(new require_settings.PluginError("operation-cancelled", id, "waitFor cancelled"));
255
+ });
256
+ }),
257
+ serialQueue: (key = "default") => {
258
+ let queue = queues.get(key);
259
+ if (!queue) {
260
+ queue = require_settings.createSerialQueue(id);
261
+ queues.set(key, queue);
262
+ }
263
+ return queue;
264
+ },
265
+ latest: (key) => {
266
+ let lane = lanes.get(key);
267
+ if (!lane) {
268
+ lane = require_settings.createLatestLane(lifetime.signal, id, key);
269
+ lanes.set(key, lane);
270
+ }
271
+ return lane;
272
+ },
273
+ acquire: async (get, dispose) => {
274
+ const resource = await get(lifetime.signal);
275
+ if (lifetime.signal.aborted) {
276
+ await dispose(resource);
277
+ throw new require_settings.PluginError("instance-closed", id, "acquired after close");
278
+ }
279
+ cleanups.push(() => dispose(resource));
280
+ return resource;
281
+ },
282
+ assertPageRef: (ref) => {
283
+ if (!pages.some((pageInfo) => (0, _embedpdf_engine_core_runtime.pageRefsEqual)(pageInfo.ref, ref))) throw notFound(ref);
284
+ },
285
+ getPage: (page) => require_settings.findPage(pages, page),
286
+ pageOf: (page) => require_settings.pageOf(pages, page, id),
287
+ allows: (permission) => require_settings.sessionAllows(requireDoc().security, permission),
288
+ assertAllowed: (permission, operation) => {
289
+ if (!require_settings.sessionAllows(requireDoc().security, permission)) throw require_settings.permissionDenied(id, permission, operation);
290
+ },
291
+ allowsField: (action, field) => requireDoc().security.allowsField(action, { groupId: field.groupId }),
292
+ assertAllowedField: (action, field, operation) => {
293
+ if (!requireDoc().security.allowsField(action, { groupId: field.groupId })) throw require_settings.fieldPermissionDenied(id, action, field, operation);
294
+ },
295
+ cancellable: (signal, task) => require_settings.cancellable(id, signal, task),
296
+ capabilities,
297
+ connect: (instance) => {
298
+ instance.connect?.();
299
+ for (const mirror of mirrors.splice(0)) mirror.start();
300
+ return instance.api;
301
+ },
302
+ emitDocumentEvent: (event) => {
303
+ bursts.first(event);
304
+ documentEvents.emit(event);
305
+ bursts.last(event);
306
+ },
307
+ settle: (signal) => require_settings.settle(settleFlushes, [signal, lifetime.signal], report),
308
+ download: (read) => require_settings.readWrapped(downloadWraps, read),
309
+ dispose: async () => {
310
+ lifetime.abort();
311
+ for (const fn of cleanups.splice(0).reverse()) await fn();
312
+ }
313
+ };
314
+ return context;
315
+ }
316
+ /**
317
+ * Time that moves only when the test moves it. `frames: false` gives a host
318
+ * that never paints, as Node is.
319
+ */
320
+ function manualClock(options = {}) {
321
+ let now = 0;
322
+ let order = 0;
323
+ const timers = /* @__PURE__ */ new Map();
324
+ const frames = /* @__PURE__ */ new Map();
325
+ const nextDue = (until) => {
326
+ let found;
327
+ for (const [id, timer] of timers) {
328
+ if (timer.due > until) continue;
329
+ if (found === void 0 || timer.due < timers.get(found).due) found = id;
330
+ }
331
+ return found;
332
+ };
333
+ const advanceTo = (target) => {
334
+ if (target < now) throw new Error(`manualClock: time does not go back (${target} < ${now})`);
335
+ for (let id = nextDue(target); id !== void 0; id = nextDue(target)) {
336
+ const timer = timers.get(id);
337
+ timers.delete(id);
338
+ now = timer.due;
339
+ timer.run();
340
+ }
341
+ now = target;
342
+ };
343
+ return {
344
+ clock: {
345
+ now: () => now,
346
+ after(ms, run) {
347
+ const id = ++order;
348
+ timers.set(id, {
349
+ due: now + Math.max(0, ms),
350
+ run
351
+ });
352
+ return () => void timers.delete(id);
353
+ },
354
+ ...options.frames === false ? {} : { nextFrame(run) {
355
+ const id = ++order;
356
+ frames.set(id, run);
357
+ return () => void frames.delete(id);
358
+ } }
359
+ },
360
+ get now() {
361
+ return now;
362
+ },
363
+ get pending() {
364
+ return {
365
+ timers: timers.size,
366
+ frames: frames.size
367
+ };
368
+ },
369
+ advance: (ms) => advanceTo(now + ms),
370
+ frame(timeMs = now) {
371
+ advanceTo(timeMs);
372
+ const due = [...frames.values()];
373
+ frames.clear();
374
+ for (const run of due) run(now);
375
+ }
376
+ };
377
+ }
378
+ /**
379
+ * A faked document's members over the defaults every document has: calls'
380
+ * facts and working sets change no result, so `with` gives the same fake and
381
+ * a working set goes nowhere, unless the test fakes them.
382
+ */
383
+ function fakeDocument(members) {
384
+ const fake = {
385
+ setWorkingSet: () => {},
386
+ ...members
387
+ };
388
+ fake.with ?? (fake.with = () => fake);
389
+ return fake;
390
+ }
391
+ //#endregion
392
+ Object.defineProperty(exports, "LOCAL_ENGINE_BRAND", {
393
+ enumerable: true,
394
+ get: function() {
395
+ return _embedpdf_engine_core_runtime.LOCAL_ENGINE_BRAND;
396
+ }
397
+ });
398
+ exports.createTestContext = createTestContext;
399
+ exports.manualClock = manualClock;
400
+
401
+ //# sourceMappingURL=testing.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing.cjs","names":["PluginError","findPage","createStore","createEventHook","createDocumentChanges","createBurstGate","DocumentsToken","createSettingsStore","createMirror","createPageMirror","instanceClock","timerClock","createSerialQueue","createLatestLane","pageOf","sessionAllows","permissionDenied","fieldPermissionDenied","cancellable","settle","readWrapped"],"sources":["../src/testing.ts"],"sourcesContent":["/**\n * @embedpdf/core/testing — a real plugin context for plugin unit tests, so a\n * suite never re-invents the kernel: the kernel's own store, state cell,\n * mirrors and event hooks, page geometry from a page list, capabilities from\n * a token map, cleanups you can run, and a document handle you shape per\n * test. Everything a controller reaches for behaves as in the kernel, minus\n * the engine.\n *\n * const ctx = createTestContext({\n * id: 'measurement',\n * state: initialMeasurementState(),\n * pages: [{ ref: toPageRef(1), size: { width: 600, height: 800 } }],\n * capabilities: [[AnnotationToken, fakeAnnotation]],\n * doc: { security: { allows: () => true } },\n * });\n * const api = ctx.connect(createMeasurementController(ctx));\n * ctx.emitDocumentEvent(event); // folds into the plugin's mirrors\n *\n * Time is the host's timers unless a test passes a clock: `manualClock()`\n * steps timers and frames by hand.\n *\n * const time = manualClock();\n * const ctx = createTestContext({ clock: time.clock });\n * time.advance(300); // timers due by then run\n * time.frame(16); // and the callbacks waiting for a frame\n */\nimport type {\n DocumentEvent,\n DocumentHandle,\n Engine,\n PageLayout,\n PageRef,\n PdfRect,\n} from '@embedpdf/engine-core/runtime';\nimport { pageRefsEqual, subscribeToType } from '@embedpdf/engine-core/runtime';\n\n/** Brand a fake engine or handle as local (`[LOCAL_ENGINE_BRAND]: true`) so\n * `isLocalEngine` and `isLocalDocument` accept it. */\nexport { LOCAL_ENGINE_BRAND } from '@embedpdf/engine-core/runtime';\n\nimport { createBurstGate } from './bursts';\nimport { createDocumentChanges } from './changes';\nimport { instanceClock, timerClock, type Cancel, type HostClock } from './clock';\nimport { PluginError } from './errors';\nimport { createEventHook } from './event-hook';\nimport { createLatestLane, type LatestLane } from './lanes';\nimport { createMirror, type MirrorController, type MirrorEnvironment } from './mirror';\nimport { createPageMirror } from './page-mirror';\nimport { createSerialQueue, type SerialQueue } from './serial-queue';\nimport { cancellable } from './cancellable';\nimport { findPage, pageOf } from './page-of';\nimport { readWrapped, type DownloadWrap } from './context';\nimport { fieldPermissionDenied, permissionDenied, sessionAllows } from './permissions';\nimport { createSettingsStore, type NoSettings, type SettingsDeclaration } from './settings';\nimport { settle, type SettleFlush } from './settle';\nimport { createStore } from './store';\nimport {\n DocumentsToken,\n type CapabilityToken,\n type DocumentInfo,\n type DocumentMeta,\n type DocumentsCapability,\n type OperationOptions,\n type PluginContext,\n type Unsubscribe,\n} from './types';\n\nexport interface TestPage {\n readonly ref: PageRef;\n /** Page size in points (default 612 × 792, or the crop's extent); the crop box is `[0, 0, w, h]` unless given. */\n readonly size?: { readonly width: number; readonly height: number };\n readonly crop?: PdfRect;\n readonly rotation?: PageLayout['rotation'];\n readonly userUnit?: number;\n readonly label?: string | null;\n}\n\nexport interface TestContextOptions<S, T extends object = NoSettings> {\n /** The plugin id (`ctx.id`, error prefixes). Default `'test'`. */\n readonly id?: string;\n /** The instance's initial state; omit for a stateless plugin. */\n readonly state?: S;\n /**\n * The plugin's settings, as its definition declares them; `ctx.settings()` reads them.\n * Without them the context has none, and `ctx.settings()` throws.\n */\n readonly settings?: SettingsDeclaration<T>;\n /** The document's pages; `document()` and `geometry` derive from them. */\n readonly pages?: readonly TestPage[];\n readonly documentId?: string;\n /** Capabilities `get`/`tryGet` resolve, by token. `DocumentsToken` defaults to a\n * registry holding this one document. */\n readonly capabilities?: ReadonlyArray<readonly [CapabilityToken<unknown>, unknown]>;\n /** The document handle members the test exercises (`security`, `page`, `events`, …),\n * or a real engine document; `null` for a workspace plugin with no document. */\n readonly doc?: Partial<DocumentHandle> | null;\n readonly engine?: Partial<Engine>;\n /** The host's time (`ctx.clock`). Default: `timerClock`, the environment's timers and no frames. */\n readonly clock?: HostClock;\n}\n\nexport interface TestContext<S, T extends object = NoSettings> extends PluginContext<S, T> {\n /** Every capability `get` can resolve — add or replace during a test. */\n readonly capabilities: Map<CapabilityToken<unknown>, unknown>;\n /** Observe the change stream: state updates and `notify` wake the listener. */\n subscribe(listener: () => void): Unsubscribe;\n /**\n * What the kernel does after `create`: run the instance's `connect`, then\n * start its mirrors' first loads. Returns the instance's api.\n */\n connect<C>(instance: { api: C; connect?: () => void }): C;\n /** Deliver a confirmed document event, as the engine would (the default `doc.events`). */\n emitDocumentEvent(event: DocumentEvent): void;\n /** What `documents.download()` does first: run every `onSettle` flush and wait for it (settle.ts). */\n settle(signal?: AbortSignal): Promise<void>;\n /**\n * What `documents.download()` does after settling: `read` the file inside every\n * `aroundDownload` wrap, the first registered outermost.\n */\n download(read: () => Promise<Uint8Array>): Promise<Uint8Array>;\n /** Run the registered cleanups and abort the lifetime (the plugin's unmount). */\n dispose(): Promise<void>;\n}\n\nconst layoutOf = (page: TestPage, index: number): PageLayout => {\n // Size follows the crop when only the crop is given, and vice versa.\n const size =\n page.size ??\n (page.crop\n ? { width: page.crop.right - page.crop.left, height: page.crop.top - page.crop.bottom }\n : { width: 612, height: 792 });\n const crop = page.crop ?? { left: 0, bottom: 0, right: size.width, top: size.height };\n // Every box is the crop box, measured from its own top-left corner.\n const visible = { x: 0, y: 0, width: crop.right - crop.left, height: crop.top - crop.bottom };\n return {\n index,\n ref: page.ref,\n label: page.label ?? null,\n size,\n rotation: page.rotation ?? 0,\n userUnit: page.userUnit ?? 1,\n tabOrder: null,\n boxes: {\n media: { ...visible },\n crop: { ...visible },\n bleed: { ...visible },\n trim: { ...visible },\n art: { ...visible },\n },\n pdfCropBox: crop,\n };\n};\n\n/** A read-only document registry holding the test's one document. */\nfunction testDocuments(meta: DocumentMeta): DocumentsCapability {\n const info: DocumentInfo = {\n id: meta.id,\n status: 'ready',\n pageCount: meta.pageCount,\n hasUnsavedChanges: false,\n };\n const known = (id?: string) => id === undefined || id === meta.id;\n const pagesOf = (id?: string) => (known(id) ? meta.pages : []);\n const unsupported = (verb: string) => () => {\n throw new PluginError('unsupported', 'documents', `${verb} is not available in a test context`);\n };\n const never = () => () => {};\n return {\n getActiveId: () => meta.id,\n getActive: () => info,\n list: () => [info],\n get: (id) => (known(id) ? info : null),\n has: (id) => known(id),\n getCount: () => 1,\n getOrder: () => [meta.id],\n listPages: pagesOf,\n getPage: (page, id) => findPage(pagesOf(id), page),\n getPageIndex: (ref, id) => pagesOf(id).findIndex((page) => pageRefsEqual(page.ref, ref)),\n getRevision: (id) => (known(id) ? meta.revision : -1),\n canDownload: () => true,\n canPrint: () => true,\n // A test context opens nothing.\n canOpenFiles: () => false,\n open: unsupported('open'),\n retry: unsupported('retry'),\n rename: unsupported('rename'),\n openAll: unsupported('openAll'),\n unlock: unsupported('unlock'),\n close: unsupported('close'),\n closeAll: unsupported('closeAll'),\n setActive: unsupported('setActive'),\n setOrder: unsupported('setOrder'),\n move: unsupported('move'),\n swap: unsupported('swap'),\n download: unsupported('download'),\n downloadLayer: unsupported('downloadLayer'),\n onOpened: never,\n onOpenFailed: never,\n onLocked: never,\n onClosed: never,\n onActiveChanged: never,\n onPagesChanged: never,\n onUnsavedChangesChanged: never,\n } satisfies DocumentsCapability;\n}\n\nexport function createTestContext<S = void, T extends object = NoSettings>(\n options: TestContextOptions<S, T> = {},\n): TestContext<S, T> {\n const id = options.id ?? 'test';\n const documentId = options.documentId ?? 'doc';\n const report = (error: unknown) => console.error(`[${id}]`, error);\n const store = createStore(report);\n const instanceId = `${documentId}:${id}`;\n const lease = store.lease<S>(id, options.state as S, instanceId);\n const cleanups: Array<() => void | Promise<void>> = [];\n const lifetime = new AbortController();\n const settleFlushes = new Set<SettleFlush>();\n const downloadWraps: DownloadWrap[] = [];\n const pages = (options.pages ?? []).map(layoutOf);\n const capabilities = new Map<CapabilityToken<unknown>, unknown>(options.capabilities ?? []);\n const documentEvents = createEventHook<DocumentEvent>(report);\n // A plain object lists the members a test fakes, over working defaults; a\n // real engine document (a class instance) is used as it is.\n const isRealHandle =\n options.doc != null && Object.getPrototypeOf(options.doc) !== Object.prototype;\n const doc =\n options.doc === null\n ? null\n : isRealHandle\n ? (options.doc as DocumentHandle)\n : fakeDocument({\n id: documentId,\n events: {\n subscribe: documentEvents.on,\n on: (type: DocumentEvent['type'], listener: (event: DocumentEvent) => void) =>\n subscribeToType(documentEvents.on, type, listener),\n lastServerId: () => null,\n },\n security: { allows: () => true, allowsAnnotation: () => true, allowsField: () => true },\n ...options.doc,\n });\n const requireDoc = (): DocumentHandle => {\n if (!doc) throw new PluginError('not-ready', id, 'the test context has no document');\n return doc;\n };\n // The document's change queue, as the kernel keeps one: `doc.apply` sends its changes.\n const changes = createDocumentChanges({ handle: () => doc, notify: store.notify, report });\n settleFlushes.add(() => changes.settle());\n cleanups.push(() => changes.close());\n const bursts = createBurstGate(store);\n const mirrors: MirrorController<unknown>[] = [];\n const mirrorEnvironment = (): MirrorEnvironment => ({\n doc: requireDoc(),\n changes,\n notify: store.notify,\n lifetime: lifetime.signal,\n cell(name, initial) {\n const cell = store.lease(`${id}/${name}`, initial, instanceId);\n lifetime.signal.addEventListener('abort', () => cell.revoke(), { once: true });\n return cell;\n },\n onDocumentEvent(listener) {\n cleanups.push(requireDoc().events.subscribe(listener));\n },\n report,\n });\n const meta: DocumentMeta = {\n id: documentId,\n instanceId,\n pageCount: pages.length,\n pages,\n revision: 1,\n renderPolicy: { kind: 'continuous' },\n hasUnsavedChanges: false,\n };\n if (!capabilities.has(DocumentsToken as CapabilityToken<unknown>)) {\n capabilities.set(DocumentsToken as CapabilityToken<unknown>, testDocuments(meta));\n }\n const notFound = (ref: PageRef) =>\n new PluginError('not-found', id, `page ${ref.objectNumber} is not in this document`);\n const resolve = <T>(token: CapabilityToken<T>): T | null =>\n capabilities.has(token as CapabilityToken<unknown>)\n ? (capabilities.get(token as CapabilityToken<unknown>) as T)\n : null;\n const require = <T>(token: CapabilityToken<T>): T => {\n const capability = resolve(token);\n if (capability === null) {\n throw new PluginError('not-found', id, `no capability for ${token.name} in the test context`);\n }\n return capability;\n };\n const queues = new Map<string, SerialQueue>();\n const lanes = new Map<string, LatestLane>();\n // The context is the plugin's only instance, so it holds the registration's settings itself.\n const settingsStore = options.settings\n ? createSettingsStore(options.settings, store.notify, report)\n : null;\n if (settingsStore) cleanups.push(() => settingsStore.dispose());\n const settings = settingsStore?.forInstance((teardown) => cleanups.push(teardown)) ?? null;\n\n const context: TestContext<S, T> = {\n id,\n instanceId: meta.instanceId,\n engine: (options.engine ?? {}) as Engine,\n documentId,\n get doc() {\n return doc as DocumentHandle;\n },\n documentHandle: () => doc,\n subscribe: store.subscribe,\n state: {\n get: () => lease.read(),\n update: (transition, ...args) => {\n lease.write(transition(lease.read(), ...args));\n },\n onChange: lease.onChange,\n },\n settings: () => {\n if (!settings) throw new Error(`[${id}] the test context declares no settings`);\n return settings;\n },\n notify: store.notify,\n watch: (select, handler, isEqual = Object.is) => {\n let previous = select();\n const off = store.subscribe(() => {\n const next = select();\n if (isEqual(previous, next)) return;\n const prior = previous;\n previous = next;\n handler(next, prior);\n });\n cleanups.push(off);\n return off;\n },\n mirror: (spec) => {\n const controller = createMirror(spec, mirrorEnvironment());\n mirrors.push(controller as MirrorController<unknown>);\n return controller.mirror;\n },\n pageMirror: (spec) => createPageMirror(spec, mirrorEnvironment()),\n changes: changes.forPlugin(id),\n document: () => meta,\n get: require,\n tryGet: resolve,\n forDocument: (token) => require(token),\n tryForDocument: (token) => resolve(token),\n cleanup: (fn) => {\n cleanups.push(fn);\n },\n clock: instanceClock(options.clock ?? timerClock, lifetime.signal),\n onSettle: (flush) => {\n settleFlushes.add(flush);\n cleanups.push(() => {\n settleFlushes.delete(flush);\n });\n },\n aroundDownload: (wrap) => {\n downloadWraps.push(wrap);\n cleanups.push(() => {\n const index = downloadWraps.indexOf(wrap);\n if (index >= 0) downloadWraps.splice(index, 1);\n });\n },\n events: {\n source: <T>() => {\n const hook = createEventHook<T>((error) =>\n console.error(`[${id}] listener failed:`, error),\n );\n cleanups.push(() => hook.dispose());\n return hook;\n },\n },\n listen: (source, listener) => {\n const off = typeof source === 'function' ? source(listener) : source.subscribe(listener);\n cleanups.push(off);\n },\n waitFor: (predicate, waitOptions?: OperationOptions) =>\n new Promise<void>((resolveWait, reject) => {\n if (predicate()) return resolveWait();\n const off = context.subscribe(() => {\n if (!predicate()) return;\n off();\n resolveWait();\n });\n waitOptions?.signal?.addEventListener('abort', () => {\n off();\n reject(new PluginError('operation-cancelled', id, 'waitFor cancelled'));\n });\n }),\n serialQueue: (key = 'default') => {\n let queue = queues.get(key);\n if (!queue) {\n queue = createSerialQueue(id);\n queues.set(key, queue);\n }\n return queue;\n },\n latest: (key) => {\n let lane = lanes.get(key);\n if (!lane) {\n lane = createLatestLane(lifetime.signal, id, key);\n lanes.set(key, lane);\n }\n return lane;\n },\n acquire: async (get, dispose) => {\n const resource = await get(lifetime.signal);\n if (lifetime.signal.aborted) {\n await dispose(resource);\n throw new PluginError('instance-closed', id, 'acquired after close');\n }\n cleanups.push(() => dispose(resource));\n return resource;\n },\n assertPageRef: (ref) => {\n if (!pages.some((pageInfo) => pageRefsEqual(pageInfo.ref, ref))) throw notFound(ref);\n },\n getPage: (page) => findPage(pages, page),\n pageOf: (page) => pageOf(pages, page, id),\n allows: (permission) => sessionAllows(requireDoc().security, permission),\n assertAllowed: (permission, operation) => {\n if (!sessionAllows(requireDoc().security, permission)) {\n throw permissionDenied(id, permission, operation);\n }\n },\n allowsField: (action, field) =>\n requireDoc().security.allowsField(action, { groupId: field.groupId }),\n assertAllowedField: (action, field, operation) => {\n if (!requireDoc().security.allowsField(action, { groupId: field.groupId })) {\n throw fieldPermissionDenied(id, action, field, operation);\n }\n },\n cancellable: (signal, task) => cancellable(id, signal, task),\n capabilities,\n connect: (instance) => {\n instance.connect?.();\n for (const mirror of mirrors.splice(0)) mirror.start();\n return instance.api;\n },\n emitDocumentEvent: (event) => {\n bursts.first(event);\n documentEvents.emit(event);\n bursts.last(event);\n },\n settle: (signal) => settle(settleFlushes, [signal, lifetime.signal], report),\n download: (read) => readWrapped(downloadWraps, read),\n dispose: async () => {\n lifetime.abort();\n for (const fn of cleanups.splice(0).reverse()) await fn();\n },\n };\n return context;\n}\n\n/** A clock a test steps by hand (see {@link manualClock}). */\nexport interface ManualClock {\n /** The clock to hand a test context or a kernel. */\n readonly clock: HostClock;\n /** The current time in milliseconds. It starts at 0. */\n readonly now: number;\n /** How many timers and frame callbacks are waiting. */\n readonly pending: { readonly timers: number; readonly frames: number };\n /** Move time forward by `ms`, running the timers that fall due, earliest first. */\n advance(ms: number): void;\n /**\n * Show a frame at `timeMs` (default: now): move time there, running the\n * timers due by then, and run the callbacks waiting for a frame. What they\n * schedule waits for the next frame.\n */\n frame(timeMs?: number): void;\n}\n\n/**\n * Time that moves only when the test moves it. `frames: false` gives a host\n * that never paints, as Node is.\n */\nexport function manualClock(options: { frames?: boolean } = {}): ManualClock {\n let now = 0;\n let order = 0;\n const timers = new Map<number, { due: number; run: () => void }>();\n const frames = new Map<number, (timeMs: number) => void>();\n\n const nextDue = (until: number): number | undefined => {\n let found: number | undefined;\n for (const [id, timer] of timers) {\n if (timer.due > until) continue;\n if (found === undefined || timer.due < timers.get(found)!.due) found = id;\n }\n return found;\n };\n const advanceTo = (target: number): void => {\n if (target < now) throw new Error(`manualClock: time does not go back (${target} < ${now})`);\n for (let id = nextDue(target); id !== undefined; id = nextDue(target)) {\n const timer = timers.get(id)!;\n timers.delete(id);\n now = timer.due;\n timer.run();\n }\n now = target;\n };\n\n const clock: HostClock = {\n now: () => now,\n after(ms, run): Cancel {\n const id = ++order;\n timers.set(id, { due: now + Math.max(0, ms), run });\n return () => void timers.delete(id);\n },\n ...(options.frames === false\n ? {}\n : {\n nextFrame(run: (timeMs: number) => void): Cancel {\n const id = ++order;\n frames.set(id, run);\n return () => void frames.delete(id);\n },\n }),\n };\n return {\n clock,\n get now() {\n return now;\n },\n get pending() {\n return { timers: timers.size, frames: frames.size };\n },\n advance: (ms) => advanceTo(now + ms),\n frame(timeMs = now) {\n advanceTo(timeMs);\n const due = [...frames.values()];\n frames.clear();\n for (const run of due) run(now);\n },\n };\n}\n\n/**\n * A faked document's members over the defaults every document has: calls'\n * facts and working sets change no result, so `with` gives the same fake and\n * a working set goes nowhere, unless the test fakes them.\n */\nfunction fakeDocument(members: Record<string, unknown>): DocumentHandle {\n const fake: Record<string, unknown> = { setWorkingSet: () => {}, ...members };\n fake.with ??= () => fake;\n return fake as unknown as DocumentHandle;\n}\n"],"mappings":"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"}
@@ -0,0 +1,94 @@
1
+ import { It as HostClock, bt as SettingsDeclaration, gt as NoSettings, pt as Unsubscribe, r as CapabilityToken, rt as PluginContext } from "./types-t-DaU8VK.cjs";
2
+ import { DocumentEvent, DocumentHandle, Engine, LOCAL_ENGINE_BRAND, PageLayout, PageRef, PdfRect } from "@embedpdf/engine-core/runtime";
3
+ //#region src/testing.d.ts
4
+ interface TestPage {
5
+ readonly ref: PageRef;
6
+ /** Page size in points (default 612 × 792, or the crop's extent); the crop box is `[0, 0, w, h]` unless given. */
7
+ readonly size?: {
8
+ readonly width: number;
9
+ readonly height: number;
10
+ };
11
+ readonly crop?: PdfRect;
12
+ readonly rotation?: PageLayout['rotation'];
13
+ readonly userUnit?: number;
14
+ readonly label?: string | null;
15
+ }
16
+ interface TestContextOptions<S, T extends object = NoSettings> {
17
+ /** The plugin id (`ctx.id`, error prefixes). Default `'test'`. */
18
+ readonly id?: string;
19
+ /** The instance's initial state; omit for a stateless plugin. */
20
+ readonly state?: S;
21
+ /**
22
+ * The plugin's settings, as its definition declares them; `ctx.settings()` reads them.
23
+ * Without them the context has none, and `ctx.settings()` throws.
24
+ */
25
+ readonly settings?: SettingsDeclaration<T>;
26
+ /** The document's pages; `document()` and `geometry` derive from them. */
27
+ readonly pages?: readonly TestPage[];
28
+ readonly documentId?: string;
29
+ /** Capabilities `get`/`tryGet` resolve, by token. `DocumentsToken` defaults to a
30
+ * registry holding this one document. */
31
+ readonly capabilities?: ReadonlyArray<readonly [CapabilityToken<unknown>, unknown]>;
32
+ /** The document handle members the test exercises (`security`, `page`, `events`, …),
33
+ * or a real engine document; `null` for a workspace plugin with no document. */
34
+ readonly doc?: Partial<DocumentHandle> | null;
35
+ readonly engine?: Partial<Engine>;
36
+ /** The host's time (`ctx.clock`). Default: `timerClock`, the environment's timers and no frames. */
37
+ readonly clock?: HostClock;
38
+ }
39
+ interface TestContext<S, T extends object = NoSettings> extends PluginContext<S, T> {
40
+ /** Every capability `get` can resolve — add or replace during a test. */
41
+ readonly capabilities: Map<CapabilityToken<unknown>, unknown>;
42
+ /** Observe the change stream: state updates and `notify` wake the listener. */
43
+ subscribe(listener: () => void): Unsubscribe;
44
+ /**
45
+ * What the kernel does after `create`: run the instance's `connect`, then
46
+ * start its mirrors' first loads. Returns the instance's api.
47
+ */
48
+ connect<C>(instance: {
49
+ api: C;
50
+ connect?: () => void;
51
+ }): C;
52
+ /** Deliver a confirmed document event, as the engine would (the default `doc.events`). */
53
+ emitDocumentEvent(event: DocumentEvent): void;
54
+ /** What `documents.download()` does first: run every `onSettle` flush and wait for it (settle.ts). */
55
+ settle(signal?: AbortSignal): Promise<void>;
56
+ /**
57
+ * What `documents.download()` does after settling: `read` the file inside every
58
+ * `aroundDownload` wrap, the first registered outermost.
59
+ */
60
+ download(read: () => Promise<Uint8Array>): Promise<Uint8Array>;
61
+ /** Run the registered cleanups and abort the lifetime (the plugin's unmount). */
62
+ dispose(): Promise<void>;
63
+ }
64
+ declare function createTestContext<S = void, T extends object = NoSettings>(options?: TestContextOptions<S, T>): TestContext<S, T>;
65
+ /** A clock a test steps by hand (see {@link manualClock}). */
66
+ interface ManualClock {
67
+ /** The clock to hand a test context or a kernel. */
68
+ readonly clock: HostClock;
69
+ /** The current time in milliseconds. It starts at 0. */
70
+ readonly now: number;
71
+ /** How many timers and frame callbacks are waiting. */
72
+ readonly pending: {
73
+ readonly timers: number;
74
+ readonly frames: number;
75
+ };
76
+ /** Move time forward by `ms`, running the timers that fall due, earliest first. */
77
+ advance(ms: number): void;
78
+ /**
79
+ * Show a frame at `timeMs` (default: now): move time there, running the
80
+ * timers due by then, and run the callbacks waiting for a frame. What they
81
+ * schedule waits for the next frame.
82
+ */
83
+ frame(timeMs?: number): void;
84
+ }
85
+ /**
86
+ * Time that moves only when the test moves it. `frames: false` gives a host
87
+ * that never paints, as Node is.
88
+ */
89
+ declare function manualClock(options?: {
90
+ frames?: boolean;
91
+ }): ManualClock;
92
+ //#endregion
93
+ export { LOCAL_ENGINE_BRAND, ManualClock, TestContext, TestContextOptions, TestPage, createTestContext, manualClock };
94
+ //# sourceMappingURL=testing.d.cts.map
@@ -0,0 +1,94 @@
1
+ import { It as HostClock, bt as SettingsDeclaration, gt as NoSettings, pt as Unsubscribe, r as CapabilityToken, rt as PluginContext } from "./types-t-DaU8VK.js";
2
+ import { DocumentEvent, DocumentHandle, Engine, LOCAL_ENGINE_BRAND, PageLayout, PageRef, PdfRect } from "@embedpdf/engine-core/runtime";
3
+ //#region src/testing.d.ts
4
+ interface TestPage {
5
+ readonly ref: PageRef;
6
+ /** Page size in points (default 612 × 792, or the crop's extent); the crop box is `[0, 0, w, h]` unless given. */
7
+ readonly size?: {
8
+ readonly width: number;
9
+ readonly height: number;
10
+ };
11
+ readonly crop?: PdfRect;
12
+ readonly rotation?: PageLayout['rotation'];
13
+ readonly userUnit?: number;
14
+ readonly label?: string | null;
15
+ }
16
+ interface TestContextOptions<S, T extends object = NoSettings> {
17
+ /** The plugin id (`ctx.id`, error prefixes). Default `'test'`. */
18
+ readonly id?: string;
19
+ /** The instance's initial state; omit for a stateless plugin. */
20
+ readonly state?: S;
21
+ /**
22
+ * The plugin's settings, as its definition declares them; `ctx.settings()` reads them.
23
+ * Without them the context has none, and `ctx.settings()` throws.
24
+ */
25
+ readonly settings?: SettingsDeclaration<T>;
26
+ /** The document's pages; `document()` and `geometry` derive from them. */
27
+ readonly pages?: readonly TestPage[];
28
+ readonly documentId?: string;
29
+ /** Capabilities `get`/`tryGet` resolve, by token. `DocumentsToken` defaults to a
30
+ * registry holding this one document. */
31
+ readonly capabilities?: ReadonlyArray<readonly [CapabilityToken<unknown>, unknown]>;
32
+ /** The document handle members the test exercises (`security`, `page`, `events`, …),
33
+ * or a real engine document; `null` for a workspace plugin with no document. */
34
+ readonly doc?: Partial<DocumentHandle> | null;
35
+ readonly engine?: Partial<Engine>;
36
+ /** The host's time (`ctx.clock`). Default: `timerClock`, the environment's timers and no frames. */
37
+ readonly clock?: HostClock;
38
+ }
39
+ interface TestContext<S, T extends object = NoSettings> extends PluginContext<S, T> {
40
+ /** Every capability `get` can resolve — add or replace during a test. */
41
+ readonly capabilities: Map<CapabilityToken<unknown>, unknown>;
42
+ /** Observe the change stream: state updates and `notify` wake the listener. */
43
+ subscribe(listener: () => void): Unsubscribe;
44
+ /**
45
+ * What the kernel does after `create`: run the instance's `connect`, then
46
+ * start its mirrors' first loads. Returns the instance's api.
47
+ */
48
+ connect<C>(instance: {
49
+ api: C;
50
+ connect?: () => void;
51
+ }): C;
52
+ /** Deliver a confirmed document event, as the engine would (the default `doc.events`). */
53
+ emitDocumentEvent(event: DocumentEvent): void;
54
+ /** What `documents.download()` does first: run every `onSettle` flush and wait for it (settle.ts). */
55
+ settle(signal?: AbortSignal): Promise<void>;
56
+ /**
57
+ * What `documents.download()` does after settling: `read` the file inside every
58
+ * `aroundDownload` wrap, the first registered outermost.
59
+ */
60
+ download(read: () => Promise<Uint8Array>): Promise<Uint8Array>;
61
+ /** Run the registered cleanups and abort the lifetime (the plugin's unmount). */
62
+ dispose(): Promise<void>;
63
+ }
64
+ declare function createTestContext<S = void, T extends object = NoSettings>(options?: TestContextOptions<S, T>): TestContext<S, T>;
65
+ /** A clock a test steps by hand (see {@link manualClock}). */
66
+ interface ManualClock {
67
+ /** The clock to hand a test context or a kernel. */
68
+ readonly clock: HostClock;
69
+ /** The current time in milliseconds. It starts at 0. */
70
+ readonly now: number;
71
+ /** How many timers and frame callbacks are waiting. */
72
+ readonly pending: {
73
+ readonly timers: number;
74
+ readonly frames: number;
75
+ };
76
+ /** Move time forward by `ms`, running the timers that fall due, earliest first. */
77
+ advance(ms: number): void;
78
+ /**
79
+ * Show a frame at `timeMs` (default: now): move time there, running the
80
+ * timers due by then, and run the callbacks waiting for a frame. What they
81
+ * schedule waits for the next frame.
82
+ */
83
+ frame(timeMs?: number): void;
84
+ }
85
+ /**
86
+ * Time that moves only when the test moves it. `frames: false` gives a host
87
+ * that never paints, as Node is.
88
+ */
89
+ declare function manualClock(options?: {
90
+ frames?: boolean;
91
+ }): ManualClock;
92
+ //#endregion
93
+ export { LOCAL_ENGINE_BRAND, ManualClock, TestContext, TestContextOptions, TestPage, createTestContext, manualClock };
94
+ //# sourceMappingURL=testing.d.ts.map