@embedpdf/engine-core 3.0.0-next.11 → 3.0.0-next.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{AbortError-CRSooKsk.cjs → AbortError-Cq2igpZ6.cjs} +2 -2
- package/dist/{AbortError-CRSooKsk.cjs.map → AbortError-Cq2igpZ6.cjs.map} +1 -1
- package/dist/{AbortError-8-9JAX1Y.js → AbortError-DHqPBnYc.js} +2 -2
- package/dist/{AbortError-8-9JAX1Y.js.map → AbortError-DHqPBnYc.js.map} +1 -1
- package/dist/{AnnotationListSnapshot-C1U3XUxP.d.cts → AnnotationListSnapshot-D3mFut1e.d.cts} +1323 -214
- package/dist/{AnnotationListSnapshot-C1U3XUxP.d.ts → AnnotationListSnapshot-D3mFut1e.d.ts} +1323 -214
- package/dist/{DocumentSecurityService-6rhztZlz.d.cts → DocumentSecurityService-ClTZc0zO.d.cts} +2 -2
- package/dist/{DocumentSecurityService-7nL9aY75.d.ts → DocumentSecurityService-_JRjqvoI.d.ts} +2 -2
- package/dist/{Engine-D3868xIU.d.cts → Engine-Bx0NOfmd.d.cts} +219 -11
- package/dist/{Engine-CWnAKccD.d.ts → Engine-DxtpCYcP.d.ts} +219 -11
- package/dist/{PageInsertBlankInput-CzShuSJI.cjs → PageInsertBlankInput-CqXBmFhN.cjs} +37 -2
- package/dist/PageInsertBlankInput-CqXBmFhN.cjs.map +1 -0
- package/dist/{PageInsertBlankInput-WKY0tr6S.js → PageInsertBlankInput-cHq9-i-z.js} +37 -2
- package/dist/PageInsertBlankInput-cHq9-i-z.js.map +1 -0
- package/dist/{builders-Do6_4KnS.cjs → builders-Bqm8yTpi.cjs} +1483 -11
- package/dist/builders-Bqm8yTpi.cjs.map +1 -0
- package/dist/{builders-Cdz34nbF.js → builders-Dqo2X-dt.js} +1364 -12
- package/dist/builders-Dqo2X-dt.js.map +1 -0
- package/dist/conformance.cjs +1630 -107
- package/dist/conformance.cjs.map +1 -1
- package/dist/conformance.d.cts +117 -3
- package/dist/conformance.d.ts +117 -3
- package/dist/conformance.js +1627 -108
- package/dist/conformance.js.map +1 -1
- package/dist/{fold-C7I5GpBz.cjs → fold-2V29cxCV.cjs} +2 -2
- package/dist/{fold-C7I5GpBz.cjs.map → fold-2V29cxCV.cjs.map} +1 -1
- package/dist/{fold-BfyxxwS6.js → fold-CdEJTBTn.js} +2 -2
- package/dist/{fold-BfyxxwS6.js.map → fold-CdEJTBTn.js.map} +1 -1
- package/dist/index.cjs +33 -4
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +5 -5
- package/dist/{resolver-D-jumvgM.cjs → resolver-CYVNV4hn.cjs} +216 -10
- package/dist/resolver-CYVNV4hn.cjs.map +1 -0
- package/dist/{resolver-CHG4waXL.js → resolver-inAVzsWt.js} +157 -11
- package/dist/resolver-inAVzsWt.js.map +1 -0
- package/dist/runtime.cjs +34 -5
- package/dist/runtime.d.cts +253 -5
- package/dist/runtime.d.ts +253 -5
- package/dist/runtime.js +6 -6
- package/dist/{schemas-XGrNVMDd.js → schemas-BLARNCgx.js} +469 -7
- package/dist/schemas-BLARNCgx.js.map +1 -0
- package/dist/{schemas-CS5iPpX5.cjs → schemas-DIqFMmrC.cjs} +672 -6
- package/dist/schemas-DIqFMmrC.cjs.map +1 -0
- package/dist/wire.cjs +250 -12
- package/dist/wire.cjs.map +1 -1
- package/dist/wire.d.cts +444 -4
- package/dist/wire.d.ts +444 -4
- package/dist/wire.js +214 -13
- package/dist/wire.js.map +1 -1
- package/package.json +1 -1
- package/dist/PageInsertBlankInput-CzShuSJI.cjs.map +0 -1
- package/dist/PageInsertBlankInput-WKY0tr6S.js.map +0 -1
- package/dist/builders-Cdz34nbF.js.map +0 -1
- package/dist/builders-Do6_4KnS.cjs.map +0 -1
- package/dist/resolver-CHG4waXL.js.map +0 -1
- package/dist/resolver-D-jumvgM.cjs.map +0 -1
- package/dist/schemas-CS5iPpX5.cjs.map +0 -1
- package/dist/schemas-XGrNVMDd.js.map +0 -1
package/dist/conformance.cjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_fold = require("./fold-
|
|
3
|
-
const require_PageInsertBlankInput = require("./PageInsertBlankInput-
|
|
4
|
-
const require_AbortError = require("./AbortError-
|
|
5
|
-
const require_schemas = require("./schemas-
|
|
2
|
+
const require_fold = require("./fold-2V29cxCV.cjs");
|
|
3
|
+
const require_PageInsertBlankInput = require("./PageInsertBlankInput-CqXBmFhN.cjs");
|
|
4
|
+
const require_AbortError = require("./AbortError-Cq2igpZ6.cjs");
|
|
5
|
+
const require_schemas = require("./schemas-DIqFMmrC.cjs");
|
|
6
6
|
//#region src/conformance/runMetadataConformance.ts
|
|
7
7
|
/**
|
|
8
8
|
* The conformance harness is transport-agnostic. It takes the test runner as
|
|
@@ -19,7 +19,7 @@ function runMetadataConformance(runner, opts) {
|
|
|
19
19
|
if (engine) await engine.destroy();
|
|
20
20
|
});
|
|
21
21
|
test("reads metadata from sample fixture", async () => {
|
|
22
|
-
const doc = await openFixture$
|
|
22
|
+
const doc = await openFixture$20(engine, opts);
|
|
23
23
|
try {
|
|
24
24
|
const meta = await doc.metadata.read();
|
|
25
25
|
expect(meta).toMatchObject(opts.fixture.expected);
|
|
@@ -29,7 +29,7 @@ function runMetadataConformance(runner, opts) {
|
|
|
29
29
|
}
|
|
30
30
|
});
|
|
31
31
|
test("abort() before completion rejects with AbortError", async () => {
|
|
32
|
-
const doc = await openFixture$
|
|
32
|
+
const doc = await openFixture$20(engine, opts);
|
|
33
33
|
try {
|
|
34
34
|
const p = doc.metadata.read();
|
|
35
35
|
p.abort("test");
|
|
@@ -39,7 +39,7 @@ function runMetadataConformance(runner, opts) {
|
|
|
39
39
|
}
|
|
40
40
|
});
|
|
41
41
|
test("aborting one read does not affect a concurrent one", async () => {
|
|
42
|
-
const doc = await openFixture$
|
|
42
|
+
const doc = await openFixture$20(engine, opts);
|
|
43
43
|
try {
|
|
44
44
|
const a = doc.metadata.read();
|
|
45
45
|
const b = doc.metadata.read();
|
|
@@ -51,7 +51,7 @@ function runMetadataConformance(runner, opts) {
|
|
|
51
51
|
}
|
|
52
52
|
});
|
|
53
53
|
test("metadata after close throws DocNotOpen", async () => {
|
|
54
|
-
const doc = await openFixture$
|
|
54
|
+
const doc = await openFixture$20(engine, opts);
|
|
55
55
|
await doc.close();
|
|
56
56
|
let caught;
|
|
57
57
|
try {
|
|
@@ -63,7 +63,7 @@ function runMetadataConformance(runner, opts) {
|
|
|
63
63
|
expect(require_fold.EngineError.is(caught, require_PageInsertBlankInput.EngineErrorCode.DocNotOpen)).toBe(true);
|
|
64
64
|
});
|
|
65
65
|
test("update() sets standard + custom fields and a subsequent read observes them", async () => {
|
|
66
|
-
const doc = await openFixture$
|
|
66
|
+
const doc = await openFixture$20(engine, opts);
|
|
67
67
|
try {
|
|
68
68
|
const result = await doc.metadata.update({
|
|
69
69
|
title: "Conformance Title",
|
|
@@ -82,7 +82,7 @@ function runMetadataConformance(runner, opts) {
|
|
|
82
82
|
}
|
|
83
83
|
});
|
|
84
84
|
test("update() clears a field with null", async () => {
|
|
85
|
-
const doc = await openFixture$
|
|
85
|
+
const doc = await openFixture$20(engine, opts);
|
|
86
86
|
try {
|
|
87
87
|
await doc.metadata.update({ title: "To Be Cleared" });
|
|
88
88
|
const set = await doc.metadata.read();
|
|
@@ -95,7 +95,7 @@ function runMetadataConformance(runner, opts) {
|
|
|
95
95
|
}
|
|
96
96
|
});
|
|
97
97
|
test("update() after close throws DocNotOpen", async () => {
|
|
98
|
-
const doc = await openFixture$
|
|
98
|
+
const doc = await openFixture$20(engine, opts);
|
|
99
99
|
await doc.close();
|
|
100
100
|
let caught;
|
|
101
101
|
try {
|
|
@@ -108,7 +108,7 @@ function runMetadataConformance(runner, opts) {
|
|
|
108
108
|
});
|
|
109
109
|
});
|
|
110
110
|
}
|
|
111
|
-
async function openFixture$
|
|
111
|
+
async function openFixture$20(engine, opts) {
|
|
112
112
|
if (opts.openKind === "bytes") {
|
|
113
113
|
const bytes = await opts.fixture.bytes();
|
|
114
114
|
return engine.open({
|
|
@@ -135,7 +135,7 @@ function runAnnotationReadConformance(runner, opts) {
|
|
|
135
135
|
if (engine) await engine.destroy();
|
|
136
136
|
});
|
|
137
137
|
test("listRawAll returns one entry per page with valid PageState", async () => {
|
|
138
|
-
const doc = await openFixture$
|
|
138
|
+
const doc = await openFixture$19(engine, opts);
|
|
139
139
|
try {
|
|
140
140
|
const snap = await doc.annotations.listRawAll();
|
|
141
141
|
expect(require_schemas.AnnotationListSnapshotAllPagesSchema.safeParse(snap).success).toBe(true);
|
|
@@ -149,7 +149,7 @@ function runAnnotationReadConformance(runner, opts) {
|
|
|
149
149
|
}
|
|
150
150
|
});
|
|
151
151
|
test("listRaw on the test page returns the expected counts and DTO shape", async () => {
|
|
152
|
-
const doc = await openFixture$
|
|
152
|
+
const doc = await openFixture$19(engine, opts);
|
|
153
153
|
try {
|
|
154
154
|
const snap = await doc.annotations.listRaw(opts.fixture.pageObjectNumber);
|
|
155
155
|
expect(require_schemas.AnnotationListPageSnapshotSchema.safeParse(snap).success).toBe(true);
|
|
@@ -179,7 +179,7 @@ function runAnnotationReadConformance(runner, opts) {
|
|
|
179
179
|
}
|
|
180
180
|
});
|
|
181
181
|
test("shape/vertex/line annotations expose their family fields", async () => {
|
|
182
|
-
const doc = await openFixture$
|
|
182
|
+
const doc = await openFixture$19(engine, opts);
|
|
183
183
|
try {
|
|
184
184
|
const snap = await doc.annotations.listRaw(opts.fixture.pageObjectNumber);
|
|
185
185
|
for (const a of snap.annotations) {
|
|
@@ -238,7 +238,7 @@ function runAnnotationReadConformance(runner, opts) {
|
|
|
238
238
|
}
|
|
239
239
|
});
|
|
240
240
|
test("full page list dispatches per-subtype and matches raw counts", async () => {
|
|
241
|
-
const doc = await openFixture$
|
|
241
|
+
const doc = await openFixture$19(engine, opts);
|
|
242
242
|
try {
|
|
243
243
|
const full = await doc.page(opts.fixture.pageObjectNumber).annotations.list();
|
|
244
244
|
expect(require_schemas.AnnotationListPageSnapshotSchema.safeParse(full).success).toBe(true);
|
|
@@ -248,7 +248,7 @@ function runAnnotationReadConformance(runner, opts) {
|
|
|
248
248
|
}
|
|
249
249
|
});
|
|
250
250
|
test("every annotation DTO satisfies AnnotationDTOSchema (discriminated union)", async () => {
|
|
251
|
-
const doc = await openFixture$
|
|
251
|
+
const doc = await openFixture$19(engine, opts);
|
|
252
252
|
try {
|
|
253
253
|
const snap = await doc.annotations.listRaw(opts.fixture.pageObjectNumber);
|
|
254
254
|
for (const a of snap.annotations) {
|
|
@@ -288,7 +288,7 @@ function runAnnotationReadConformance(runner, opts) {
|
|
|
288
288
|
});
|
|
289
289
|
if (opts.fixture.expectsWeakAnnotation) {
|
|
290
290
|
test("weak annotations carry identityQuality: weak and ref.kind: index", async () => {
|
|
291
|
-
const doc = await openFixture$
|
|
291
|
+
const doc = await openFixture$19(engine, opts);
|
|
292
292
|
try {
|
|
293
293
|
const weak = (await doc.annotations.listRaw(opts.fixture.pageObjectNumber)).annotations.find((a) => a.identityQuality === "weak");
|
|
294
294
|
expect(weak !== void 0).toBe(true);
|
|
@@ -298,7 +298,7 @@ function runAnnotationReadConformance(runner, opts) {
|
|
|
298
298
|
}
|
|
299
299
|
});
|
|
300
300
|
test("a stale revision token throws InvalidReference", async () => {
|
|
301
|
-
const doc = await openFixture$
|
|
301
|
+
const doc = await openFixture$19(engine, opts);
|
|
302
302
|
try {
|
|
303
303
|
const snap = await doc.page(opts.fixture.pageObjectNumber).annotations.list();
|
|
304
304
|
const weak = snap.annotations.find((a) => a.identityQuality === "weak");
|
|
@@ -314,7 +314,7 @@ function runAnnotationReadConformance(runner, opts) {
|
|
|
314
314
|
});
|
|
315
315
|
}
|
|
316
316
|
test("abort() on listRaw rejects with AbortError", async () => {
|
|
317
|
-
const doc = await openFixture$
|
|
317
|
+
const doc = await openFixture$19(engine, opts);
|
|
318
318
|
try {
|
|
319
319
|
const p = doc.annotations.listRaw(opts.fixture.pageObjectNumber);
|
|
320
320
|
p.abort("test");
|
|
@@ -324,7 +324,7 @@ function runAnnotationReadConformance(runner, opts) {
|
|
|
324
324
|
}
|
|
325
325
|
});
|
|
326
326
|
test("listRaw on an unknown pageObjectNumber throws NotFound", async () => {
|
|
327
|
-
const doc = await openFixture$
|
|
327
|
+
const doc = await openFixture$19(engine, opts);
|
|
328
328
|
try {
|
|
329
329
|
let caught;
|
|
330
330
|
try {
|
|
@@ -340,7 +340,7 @@ function runAnnotationReadConformance(runner, opts) {
|
|
|
340
340
|
});
|
|
341
341
|
});
|
|
342
342
|
}
|
|
343
|
-
async function openFixture$
|
|
343
|
+
async function openFixture$19(engine, opts) {
|
|
344
344
|
if (opts.openKind === "bytes") {
|
|
345
345
|
const bytes = await opts.fixture.bytes();
|
|
346
346
|
return engine.open({
|
|
@@ -389,7 +389,7 @@ function runActionsConformance(runner, opts) {
|
|
|
389
389
|
if (engine) await engine.destroy();
|
|
390
390
|
});
|
|
391
391
|
test("reads catalog additional actions and preserves /CO identity", async () => {
|
|
392
|
-
const doc = await open(engine, opts, opts.fixtures.document);
|
|
392
|
+
const doc = await open$1(engine, opts, opts.fixtures.document);
|
|
393
393
|
try {
|
|
394
394
|
expect(Boolean(doc.actions)).toBe(true);
|
|
395
395
|
const snapshot = await doc.actions.read();
|
|
@@ -410,7 +410,7 @@ function runActionsConformance(runner, opts) {
|
|
|
410
410
|
}
|
|
411
411
|
});
|
|
412
412
|
test("attaches page /AA to PageLayout", async () => {
|
|
413
|
-
const doc = await open(engine, opts, opts.fixtures.page);
|
|
413
|
+
const doc = await open$1(engine, opts, opts.fixtures.page);
|
|
414
414
|
try {
|
|
415
415
|
const pages = await doc.pages.list();
|
|
416
416
|
expect(pages.pages[0].actions?.open?.root?.type).toBe("goto-embedded");
|
|
@@ -422,7 +422,7 @@ function runActionsConformance(runner, opts) {
|
|
|
422
422
|
}
|
|
423
423
|
});
|
|
424
424
|
test("attaches activation actions to AnnotationBase, including links", async () => {
|
|
425
|
-
const doc = await open(engine, opts, opts.fixtures.annotation);
|
|
425
|
+
const doc = await open$1(engine, opts, opts.fixtures.annotation);
|
|
426
426
|
try {
|
|
427
427
|
const firstPage = (await doc.pages.list()).pages[0];
|
|
428
428
|
const snapshot = await doc.page(firstPage.pageObjectNumber).annotations.list();
|
|
@@ -439,7 +439,7 @@ function runActionsConformance(runner, opts) {
|
|
|
439
439
|
test("classifies a /S /JavaScript link target, script staying on base.actions", async () => {
|
|
440
440
|
const fixture = opts.fixtures.javascriptLink;
|
|
441
441
|
if (!fixture) return;
|
|
442
|
-
const doc = await open(engine, opts, fixture);
|
|
442
|
+
const doc = await open$1(engine, opts, fixture);
|
|
443
443
|
try {
|
|
444
444
|
const firstPage = (await doc.pages.list()).pages[0];
|
|
445
445
|
const link = (await doc.page(firstPage.pageObjectNumber).annotations.list()).annotations.find((annotation) => annotation.subtype === "link");
|
|
@@ -452,7 +452,7 @@ function runActionsConformance(runner, opts) {
|
|
|
452
452
|
}
|
|
453
453
|
});
|
|
454
454
|
test("keeps field actions separate from merged widget events", async () => {
|
|
455
|
-
const doc = await open(engine, opts, opts.fixtures.field);
|
|
455
|
+
const doc = await open$1(engine, opts, opts.fixtures.field);
|
|
456
456
|
try {
|
|
457
457
|
const form = await doc.forms.list();
|
|
458
458
|
expect(form.fields).toHaveLength(1);
|
|
@@ -466,7 +466,7 @@ function runActionsConformance(runner, opts) {
|
|
|
466
466
|
}
|
|
467
467
|
});
|
|
468
468
|
test("carries interpreter payloads on every executable node", async () => {
|
|
469
|
-
const doc = await open(engine, opts, opts.fixtures.payloads);
|
|
469
|
+
const doc = await open$1(engine, opts, opts.fixtures.payloads);
|
|
470
470
|
try {
|
|
471
471
|
const page = (await doc.pages.list()).pages[0];
|
|
472
472
|
const snapshot = await doc.page(page.pageObjectNumber).annotations.list();
|
|
@@ -627,7 +627,7 @@ function runActionsConformance(runner, opts) {
|
|
|
627
627
|
}
|
|
628
628
|
});
|
|
629
629
|
test("degrades unreadable payloads to unknown with payload-dropped", async () => {
|
|
630
|
-
const doc = await open(engine, opts, opts.fixtures.payloads);
|
|
630
|
+
const doc = await open$1(engine, opts, opts.fixtures.payloads);
|
|
631
631
|
try {
|
|
632
632
|
const page = (await doc.pages.list()).pages[0];
|
|
633
633
|
const snapshot = await doc.page(page.pageObjectNumber).annotations.list();
|
|
@@ -651,7 +651,7 @@ function runActionsConformance(runner, opts) {
|
|
|
651
651
|
}
|
|
652
652
|
});
|
|
653
653
|
test("reads a destination-form /OpenAction as openDestination", async () => {
|
|
654
|
-
const doc = await open(engine, opts, opts.fixtures.openDestination);
|
|
654
|
+
const doc = await open$1(engine, opts, opts.fixtures.openDestination);
|
|
655
655
|
try {
|
|
656
656
|
expect(Boolean(doc.actions)).toBe(true);
|
|
657
657
|
const snapshot = await doc.actions.read();
|
|
@@ -671,7 +671,7 @@ function runActionsConformance(runner, opts) {
|
|
|
671
671
|
});
|
|
672
672
|
});
|
|
673
673
|
}
|
|
674
|
-
async function open(engine, opts, fixture) {
|
|
674
|
+
async function open$1(engine, opts, fixture) {
|
|
675
675
|
if (opts.openKind === "bytes") return engine.open({
|
|
676
676
|
kind: "bytes",
|
|
677
677
|
id: fixture.id,
|
|
@@ -809,7 +809,7 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
809
809
|
if (engine) await engine.destroy();
|
|
810
810
|
});
|
|
811
811
|
test("create appends without shifting indices and leaves weak refs valid", async () => {
|
|
812
|
-
const doc = await openFixture$
|
|
812
|
+
const doc = await openFixture$18(engine, opts);
|
|
813
813
|
try {
|
|
814
814
|
const page = doc.page(fix.pageObjectNumber);
|
|
815
815
|
const before = await page.annotations.list();
|
|
@@ -845,7 +845,7 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
845
845
|
}
|
|
846
846
|
});
|
|
847
847
|
test("create + update honor annotation flags (set on create, merge on patch)", async () => {
|
|
848
|
-
const doc = await openFixture$
|
|
848
|
+
const doc = await openFixture$18(engine, opts);
|
|
849
849
|
try {
|
|
850
850
|
const page = doc.page(fix.pageObjectNumber);
|
|
851
851
|
const draft = {
|
|
@@ -880,7 +880,7 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
880
880
|
}
|
|
881
881
|
});
|
|
882
882
|
test("create shape annotations (circle + square) round-trip shape fields", async () => {
|
|
883
|
-
const doc = await openFixture$
|
|
883
|
+
const doc = await openFixture$18(engine, opts);
|
|
884
884
|
try {
|
|
885
885
|
const page = doc.page(fix.pageObjectNumber);
|
|
886
886
|
const circleDraft = {
|
|
@@ -961,7 +961,7 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
961
961
|
}
|
|
962
962
|
});
|
|
963
963
|
test("cloudy border (/BE) + rect differences (/RD) are tri-state", async () => {
|
|
964
|
-
const doc = await openFixture$
|
|
964
|
+
const doc = await openFixture$18(engine, opts);
|
|
965
965
|
try {
|
|
966
966
|
const page = doc.page(fix.pageObjectNumber);
|
|
967
967
|
const plainDraft = {
|
|
@@ -1040,7 +1040,7 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
1040
1040
|
}
|
|
1041
1041
|
});
|
|
1042
1042
|
test("update echoes the appearance verdict: moves preserve /AP, restyles re-bake", async () => {
|
|
1043
|
-
const doc = await openFixture$
|
|
1043
|
+
const doc = await openFixture$18(engine, opts);
|
|
1044
1044
|
try {
|
|
1045
1045
|
const page = doc.page(fix.pageObjectNumber);
|
|
1046
1046
|
const draft = {
|
|
@@ -1115,7 +1115,7 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
1115
1115
|
}
|
|
1116
1116
|
});
|
|
1117
1117
|
test("a partial /DA patch preserves the unpatched font members", async () => {
|
|
1118
|
-
const doc = await openFixture$
|
|
1118
|
+
const doc = await openFixture$18(engine, opts);
|
|
1119
1119
|
try {
|
|
1120
1120
|
const page = doc.page(fix.pageObjectNumber);
|
|
1121
1121
|
const draft = {
|
|
@@ -1171,7 +1171,7 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
1171
1171
|
}
|
|
1172
1172
|
});
|
|
1173
1173
|
test("box transform is tri-state: rect-only moves keep rotation, null flattens", async () => {
|
|
1174
|
-
const doc = await openFixture$
|
|
1174
|
+
const doc = await openFixture$18(engine, opts);
|
|
1175
1175
|
try {
|
|
1176
1176
|
const page = doc.page(fix.pageObjectNumber);
|
|
1177
1177
|
const draft = {
|
|
@@ -1232,7 +1232,7 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
1232
1232
|
}
|
|
1233
1233
|
});
|
|
1234
1234
|
test("create vertex + line annotations (polygon/polyline/line) round-trip geometry", async () => {
|
|
1235
|
-
const doc = await openFixture$
|
|
1235
|
+
const doc = await openFixture$18(engine, opts);
|
|
1236
1236
|
try {
|
|
1237
1237
|
const page = doc.page(fix.pageObjectNumber);
|
|
1238
1238
|
const polygonDraft = {
|
|
@@ -1363,7 +1363,7 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
1363
1363
|
}
|
|
1364
1364
|
});
|
|
1365
1365
|
test("create free-text + callout round-trip text/colour/intent fields", async () => {
|
|
1366
|
-
const doc = await openFixture$
|
|
1366
|
+
const doc = await openFixture$18(engine, opts);
|
|
1367
1367
|
try {
|
|
1368
1368
|
const page = doc.page(fix.pageObjectNumber);
|
|
1369
1369
|
const freeTextDraft = {
|
|
@@ -1459,7 +1459,7 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
1459
1459
|
}
|
|
1460
1460
|
});
|
|
1461
1461
|
test("update a free-text patches alignment + colours and is non-structural", async () => {
|
|
1462
|
-
const doc = await openFixture$
|
|
1462
|
+
const doc = await openFixture$18(engine, opts);
|
|
1463
1463
|
try {
|
|
1464
1464
|
const page = doc.page(fix.pageObjectNumber);
|
|
1465
1465
|
const created = await page.annotations.create({
|
|
@@ -1519,8 +1519,289 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
1519
1519
|
await doc.close();
|
|
1520
1520
|
}
|
|
1521
1521
|
});
|
|
1522
|
+
test("rich text: a plain draft reads back body-style paragraphs and a body from its font", async () => {
|
|
1523
|
+
const doc = await openFixture$18(engine, opts);
|
|
1524
|
+
try {
|
|
1525
|
+
const created = await doc.page(fix.pageObjectNumber).annotations.create({
|
|
1526
|
+
subtype: "free-text",
|
|
1527
|
+
intent: "free-text",
|
|
1528
|
+
contents: "Plain\rtext",
|
|
1529
|
+
rect: shapeRect,
|
|
1530
|
+
fontFamily: "helvetica-bold",
|
|
1531
|
+
fontSize: 18,
|
|
1532
|
+
textAlign: "center",
|
|
1533
|
+
color: {
|
|
1534
|
+
r: 0,
|
|
1535
|
+
g: 0,
|
|
1536
|
+
b: 0
|
|
1537
|
+
}
|
|
1538
|
+
});
|
|
1539
|
+
expect(created.created.subtype).toBe("free-text");
|
|
1540
|
+
if (created.created.subtype === "free-text") {
|
|
1541
|
+
const rich = created.created.richText;
|
|
1542
|
+
expect(created.created.fontFamily).toBe("helvetica-bold");
|
|
1543
|
+
expect(rich.body.family).toBe("Helvetica");
|
|
1544
|
+
expect(rich.body.weight).toBe(700);
|
|
1545
|
+
expect(rich.body.italic).toBe(false);
|
|
1546
|
+
expect(rich.body.size).toBe(18);
|
|
1547
|
+
expect(rich.body.align).toBe("center");
|
|
1548
|
+
expect(rich.paragraphs).toEqual([{ runs: [{ text: "Plain" }] }, { runs: [{ text: "text" }] }]);
|
|
1549
|
+
}
|
|
1550
|
+
} finally {
|
|
1551
|
+
await doc.close();
|
|
1552
|
+
}
|
|
1553
|
+
});
|
|
1554
|
+
test("rich text: a draft with runs round-trips the body, the runs and the plain projection", async () => {
|
|
1555
|
+
const doc = await openFixture$18(engine, opts);
|
|
1556
|
+
try {
|
|
1557
|
+
const page = doc.page(fix.pageObjectNumber);
|
|
1558
|
+
const created = await page.annotations.create({
|
|
1559
|
+
subtype: "free-text",
|
|
1560
|
+
intent: "free-text",
|
|
1561
|
+
rect: shapeRect,
|
|
1562
|
+
fontFamily: "helvetica",
|
|
1563
|
+
fontSize: 12,
|
|
1564
|
+
textAlign: "left",
|
|
1565
|
+
color: {
|
|
1566
|
+
r: 0,
|
|
1567
|
+
g: 0,
|
|
1568
|
+
b: 255
|
|
1569
|
+
},
|
|
1570
|
+
richText: {
|
|
1571
|
+
body: {
|
|
1572
|
+
family: "Helvetica",
|
|
1573
|
+
size: 18,
|
|
1574
|
+
color: "#102030"
|
|
1575
|
+
},
|
|
1576
|
+
paragraphs: [{ runs: [
|
|
1577
|
+
{ text: "Hello " },
|
|
1578
|
+
{
|
|
1579
|
+
text: "bold",
|
|
1580
|
+
style: { weight: 700 }
|
|
1581
|
+
},
|
|
1582
|
+
{
|
|
1583
|
+
text: " red",
|
|
1584
|
+
style: { color: "#FF0000" }
|
|
1585
|
+
}
|
|
1586
|
+
] }, {
|
|
1587
|
+
align: "center",
|
|
1588
|
+
runs: [{ text: "H" }, {
|
|
1589
|
+
text: "2",
|
|
1590
|
+
style: { script: "sub" }
|
|
1591
|
+
}]
|
|
1592
|
+
}]
|
|
1593
|
+
}
|
|
1594
|
+
});
|
|
1595
|
+
expect(require_schemas.AnnotationCreateResultSchema.safeParse(created).success).toBe(true);
|
|
1596
|
+
expect(created.created.subtype).toBe("free-text");
|
|
1597
|
+
if (created.created.subtype === "free-text") {
|
|
1598
|
+
const dto = created.created;
|
|
1599
|
+
expect(dto.contents).toBe("Hello bold red\rH2");
|
|
1600
|
+
expect(dto.fontFamily).toBe("helvetica");
|
|
1601
|
+
expect(dto.fontSize).toBe(18);
|
|
1602
|
+
expect(dto.color).toMatchObject({
|
|
1603
|
+
r: 0,
|
|
1604
|
+
g: 0,
|
|
1605
|
+
b: 255
|
|
1606
|
+
});
|
|
1607
|
+
expect(dto.richText.body.color).toBe("#102030");
|
|
1608
|
+
expect(dto.richText.paragraphs[0].runs).toEqual([
|
|
1609
|
+
{ text: "Hello " },
|
|
1610
|
+
{
|
|
1611
|
+
text: "bold",
|
|
1612
|
+
style: { weight: 700 }
|
|
1613
|
+
},
|
|
1614
|
+
{
|
|
1615
|
+
text: " red",
|
|
1616
|
+
style: { color: "#FF0000" }
|
|
1617
|
+
}
|
|
1618
|
+
]);
|
|
1619
|
+
expect(dto.richText.paragraphs[1].align).toBe("center");
|
|
1620
|
+
expect(dto.richText.paragraphs[1].runs[1]).toEqual({
|
|
1621
|
+
text: "2",
|
|
1622
|
+
style: { script: "sub" }
|
|
1623
|
+
});
|
|
1624
|
+
}
|
|
1625
|
+
const listed = (await page.annotations.list()).annotations.find((a) => a.index === created.created.index);
|
|
1626
|
+
expect(listed?.subtype).toBe("free-text");
|
|
1627
|
+
if (listed?.subtype === "free-text") expect(listed.richText.paragraphs[0].runs[1]).toEqual({
|
|
1628
|
+
text: "bold",
|
|
1629
|
+
style: { weight: 700 }
|
|
1630
|
+
});
|
|
1631
|
+
} finally {
|
|
1632
|
+
await doc.close();
|
|
1633
|
+
}
|
|
1634
|
+
});
|
|
1635
|
+
test("rich text: paragraphs-only patch keeps the body; contents-only rewrites body-style paragraphs", async () => {
|
|
1636
|
+
const doc = await openFixture$18(engine, opts);
|
|
1637
|
+
try {
|
|
1638
|
+
const page = doc.page(fix.pageObjectNumber);
|
|
1639
|
+
const ref = (await page.annotations.create({
|
|
1640
|
+
subtype: "free-text",
|
|
1641
|
+
intent: "free-text",
|
|
1642
|
+
rect: shapeRect,
|
|
1643
|
+
fontFamily: "helvetica",
|
|
1644
|
+
fontSize: 14,
|
|
1645
|
+
textAlign: "center",
|
|
1646
|
+
color: {
|
|
1647
|
+
r: 0,
|
|
1648
|
+
g: 0,
|
|
1649
|
+
b: 0
|
|
1650
|
+
},
|
|
1651
|
+
richText: {
|
|
1652
|
+
body: {
|
|
1653
|
+
family: "Helvetica",
|
|
1654
|
+
size: 14
|
|
1655
|
+
},
|
|
1656
|
+
paragraphs: [{ runs: [{ text: "a" }, {
|
|
1657
|
+
text: "b",
|
|
1658
|
+
style: { weight: 700 }
|
|
1659
|
+
}] }]
|
|
1660
|
+
}
|
|
1661
|
+
})).created.ref;
|
|
1662
|
+
const restyled = await page.annotations.update(ref, {
|
|
1663
|
+
subtype: "free-text",
|
|
1664
|
+
richText: { paragraphs: [{ runs: [{
|
|
1665
|
+
text: "ab",
|
|
1666
|
+
style: { italic: true }
|
|
1667
|
+
}, { text: "c" }] }] }
|
|
1668
|
+
});
|
|
1669
|
+
expect(require_schemas.AnnotationUpdateResultSchema.safeParse(restyled).success).toBe(true);
|
|
1670
|
+
expect(restyled.updated.subtype).toBe("free-text");
|
|
1671
|
+
if (restyled.updated.subtype === "free-text") {
|
|
1672
|
+
expect(restyled.updated.contents).toBe("abc");
|
|
1673
|
+
expect(restyled.updated.richText.body.size).toBe(14);
|
|
1674
|
+
expect(restyled.updated.richText.body.align).toBe("center");
|
|
1675
|
+
expect(restyled.updated.textAlign).toBe("center");
|
|
1676
|
+
expect(restyled.updated.richText.paragraphs[0].runs[0]).toEqual({
|
|
1677
|
+
text: "ab",
|
|
1678
|
+
style: { italic: true }
|
|
1679
|
+
});
|
|
1680
|
+
}
|
|
1681
|
+
const rewritten = await page.annotations.update(ref, {
|
|
1682
|
+
subtype: "free-text",
|
|
1683
|
+
contents: "one\rtwo"
|
|
1684
|
+
});
|
|
1685
|
+
expect(rewritten.updated.subtype).toBe("free-text");
|
|
1686
|
+
if (rewritten.updated.subtype === "free-text") {
|
|
1687
|
+
expect(rewritten.updated.contents).toBe("one\rtwo");
|
|
1688
|
+
expect(rewritten.updated.richText.paragraphs).toEqual([{ runs: [{ text: "one" }] }, { runs: [{ text: "two" }] }]);
|
|
1689
|
+
expect(rewritten.updated.richText.body.size).toBe(14);
|
|
1690
|
+
}
|
|
1691
|
+
expect(rewritten.meta.weakRefsInvalidated).toBe(false);
|
|
1692
|
+
} finally {
|
|
1693
|
+
await doc.close();
|
|
1694
|
+
}
|
|
1695
|
+
});
|
|
1696
|
+
test("rich text: fontSize / fontColor / fontFamily move the body while runs keep their deltas", async () => {
|
|
1697
|
+
const doc = await openFixture$18(engine, opts);
|
|
1698
|
+
try {
|
|
1699
|
+
const page = doc.page(fix.pageObjectNumber);
|
|
1700
|
+
const created = await page.annotations.create({
|
|
1701
|
+
subtype: "free-text",
|
|
1702
|
+
intent: "free-text",
|
|
1703
|
+
rect: shapeRect,
|
|
1704
|
+
fontFamily: "helvetica",
|
|
1705
|
+
fontSize: 14,
|
|
1706
|
+
textAlign: "left",
|
|
1707
|
+
color: {
|
|
1708
|
+
r: 0,
|
|
1709
|
+
g: 0,
|
|
1710
|
+
b: 0
|
|
1711
|
+
},
|
|
1712
|
+
richText: {
|
|
1713
|
+
body: {
|
|
1714
|
+
family: "Helvetica",
|
|
1715
|
+
size: 14
|
|
1716
|
+
},
|
|
1717
|
+
paragraphs: [{ runs: [{ text: "a" }, {
|
|
1718
|
+
text: "b",
|
|
1719
|
+
style: { size: 30 }
|
|
1720
|
+
}] }]
|
|
1721
|
+
}
|
|
1722
|
+
});
|
|
1723
|
+
const updated = await page.annotations.update(created.created.ref, {
|
|
1724
|
+
subtype: "free-text",
|
|
1725
|
+
fontSize: 20,
|
|
1726
|
+
fontColor: {
|
|
1727
|
+
r: 255,
|
|
1728
|
+
g: 0,
|
|
1729
|
+
b: 0
|
|
1730
|
+
},
|
|
1731
|
+
fontFamily: "times-bold"
|
|
1732
|
+
});
|
|
1733
|
+
expect(updated.updated.subtype).toBe("free-text");
|
|
1734
|
+
if (updated.updated.subtype === "free-text") {
|
|
1735
|
+
const dto = updated.updated;
|
|
1736
|
+
expect(dto.fontSize).toBe(20);
|
|
1737
|
+
expect(dto.fontFamily).toBe("times-bold");
|
|
1738
|
+
expect(dto.richText.body.size).toBe(20);
|
|
1739
|
+
expect(dto.richText.body.color).toBe("#FF0000");
|
|
1740
|
+
expect(dto.richText.body.family).toBe("Times");
|
|
1741
|
+
expect(dto.richText.body.weight).toBe(700);
|
|
1742
|
+
expect(dto.richText.paragraphs[0].runs[1]).toEqual({
|
|
1743
|
+
text: "b",
|
|
1744
|
+
style: { size: 30 }
|
|
1745
|
+
});
|
|
1746
|
+
}
|
|
1747
|
+
} finally {
|
|
1748
|
+
await doc.close();
|
|
1749
|
+
}
|
|
1750
|
+
});
|
|
1751
|
+
test("rich text: contents and richText that disagree reject with InvalidArg; agreeing writes the runs", async () => {
|
|
1752
|
+
const doc = await openFixture$18(engine, opts);
|
|
1753
|
+
try {
|
|
1754
|
+
const page = doc.page(fix.pageObjectNumber);
|
|
1755
|
+
const created = await page.annotations.create({
|
|
1756
|
+
subtype: "free-text",
|
|
1757
|
+
intent: "free-text",
|
|
1758
|
+
contents: "x",
|
|
1759
|
+
rect: shapeRect,
|
|
1760
|
+
fontFamily: "helvetica",
|
|
1761
|
+
fontSize: 14,
|
|
1762
|
+
textAlign: "left",
|
|
1763
|
+
color: {
|
|
1764
|
+
r: 0,
|
|
1765
|
+
g: 0,
|
|
1766
|
+
b: 0
|
|
1767
|
+
}
|
|
1768
|
+
});
|
|
1769
|
+
const ref = created.created.ref;
|
|
1770
|
+
let caught;
|
|
1771
|
+
try {
|
|
1772
|
+
await page.annotations.update(ref, {
|
|
1773
|
+
subtype: "free-text",
|
|
1774
|
+
contents: "stale",
|
|
1775
|
+
richText: { paragraphs: [{ runs: [{ text: "fresh" }] }] }
|
|
1776
|
+
});
|
|
1777
|
+
} catch (err) {
|
|
1778
|
+
caught = err;
|
|
1779
|
+
}
|
|
1780
|
+
expect(require_fold.EngineError.is(caught, require_PageInsertBlankInput.EngineErrorCode.InvalidArg)).toBe(true);
|
|
1781
|
+
const after = (await page.annotations.list()).annotations.find((a) => a.index === created.created.index);
|
|
1782
|
+
expect(after?.contents).toBe("x");
|
|
1783
|
+
const agreed = await page.annotations.update(ref, {
|
|
1784
|
+
subtype: "free-text",
|
|
1785
|
+
contents: "fresh",
|
|
1786
|
+
richText: { paragraphs: [{ runs: [{ text: "fr" }, {
|
|
1787
|
+
text: "esh",
|
|
1788
|
+
style: { weight: 700 }
|
|
1789
|
+
}] }] }
|
|
1790
|
+
});
|
|
1791
|
+
expect(agreed.updated.subtype).toBe("free-text");
|
|
1792
|
+
if (agreed.updated.subtype === "free-text") {
|
|
1793
|
+
expect(agreed.updated.contents).toBe("fresh");
|
|
1794
|
+
expect(agreed.updated.richText.paragraphs[0].runs[1]).toEqual({
|
|
1795
|
+
text: "esh",
|
|
1796
|
+
style: { weight: 700 }
|
|
1797
|
+
});
|
|
1798
|
+
}
|
|
1799
|
+
} finally {
|
|
1800
|
+
await doc.close();
|
|
1801
|
+
}
|
|
1802
|
+
});
|
|
1522
1803
|
test("create redact (area + text) round-trips label + colour fields", async () => {
|
|
1523
|
-
const doc = await openFixture$
|
|
1804
|
+
const doc = await openFixture$18(engine, opts);
|
|
1524
1805
|
try {
|
|
1525
1806
|
const page = doc.page(fix.pageObjectNumber);
|
|
1526
1807
|
const areaDraft = {
|
|
@@ -1600,7 +1881,7 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
1600
1881
|
}
|
|
1601
1882
|
});
|
|
1602
1883
|
test("update a redact patches the label and clears it, non-structurally", async () => {
|
|
1603
|
-
const doc = await openFixture$
|
|
1884
|
+
const doc = await openFixture$18(engine, opts);
|
|
1604
1885
|
try {
|
|
1605
1886
|
const page = doc.page(fix.pageObjectNumber);
|
|
1606
1887
|
const created = await page.annotations.create({
|
|
@@ -1677,7 +1958,7 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
1677
1958
|
}
|
|
1678
1959
|
});
|
|
1679
1960
|
test("create + update a caret round-trips color/opacity/rect differences", async () => {
|
|
1680
|
-
const doc = await openFixture$
|
|
1961
|
+
const doc = await openFixture$18(engine, opts);
|
|
1681
1962
|
try {
|
|
1682
1963
|
const page = doc.page(fix.pageObjectNumber);
|
|
1683
1964
|
const caretDraft = {
|
|
@@ -1757,7 +2038,7 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
1757
2038
|
}
|
|
1758
2039
|
});
|
|
1759
2040
|
test("update an ink annotation patches strokes + color and is non-structural", async () => {
|
|
1760
|
-
const doc = await openFixture$
|
|
2041
|
+
const doc = await openFixture$18(engine, opts);
|
|
1761
2042
|
try {
|
|
1762
2043
|
const page = doc.page(fix.pageObjectNumber);
|
|
1763
2044
|
const created = await page.annotations.create({
|
|
@@ -1808,7 +2089,7 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
1808
2089
|
}
|
|
1809
2090
|
});
|
|
1810
2091
|
test("ink highlight intent + blend round-trip and unrelated patches preserve blend", async () => {
|
|
1811
|
-
const doc = await openFixture$
|
|
2092
|
+
const doc = await openFixture$18(engine, opts);
|
|
1812
2093
|
try {
|
|
1813
2094
|
const page = doc.page(fix.pageObjectNumber);
|
|
1814
2095
|
const created = await page.annotations.create({
|
|
@@ -1851,7 +2132,7 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
1851
2132
|
}
|
|
1852
2133
|
});
|
|
1853
2134
|
test("update a polyline patches vertices + line endings and is non-structural", async () => {
|
|
1854
|
-
const doc = await openFixture$
|
|
2135
|
+
const doc = await openFixture$18(engine, opts);
|
|
1855
2136
|
try {
|
|
1856
2137
|
const page = doc.page(fix.pageObjectNumber);
|
|
1857
2138
|
const created = await page.annotations.create({
|
|
@@ -1900,7 +2181,7 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
1900
2181
|
}
|
|
1901
2182
|
});
|
|
1902
2183
|
test("update a shape annotation patches color and is non-structural", async () => {
|
|
1903
|
-
const doc = await openFixture$
|
|
2184
|
+
const doc = await openFixture$18(engine, opts);
|
|
1904
2185
|
try {
|
|
1905
2186
|
const page = doc.page(fix.pageObjectNumber);
|
|
1906
2187
|
const created = await page.annotations.create({
|
|
@@ -1949,7 +2230,7 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
1949
2230
|
}
|
|
1950
2231
|
});
|
|
1951
2232
|
if (opts.supportsAppearanceRasters) test("creating a shape bakes an /AP appearance the reader can rasterize", async () => {
|
|
1952
|
-
const doc = await openFixture$
|
|
2233
|
+
const doc = await openFixture$18(engine, opts);
|
|
1953
2234
|
try {
|
|
1954
2235
|
const page = doc.page(fix.pageObjectNumber);
|
|
1955
2236
|
const created = await page.annotations.create({
|
|
@@ -1979,7 +2260,7 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
1979
2260
|
}
|
|
1980
2261
|
});
|
|
1981
2262
|
test("update on a durable annotation is non-structural and never touches /NM", async () => {
|
|
1982
|
-
const doc = await openFixture$
|
|
2263
|
+
const doc = await openFixture$18(engine, opts);
|
|
1983
2264
|
try {
|
|
1984
2265
|
const page = doc.page(fix.pageObjectNumber);
|
|
1985
2266
|
const before = await page.annotations.list();
|
|
@@ -2005,7 +2286,7 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
2005
2286
|
}
|
|
2006
2287
|
});
|
|
2007
2288
|
if (fix.expectsWeakAnnotation) test("update on a weak annotation stamps a UUID v4 /NM and upgrades the ref", async () => {
|
|
2008
|
-
const doc = await openFixture$
|
|
2289
|
+
const doc = await openFixture$18(engine, opts);
|
|
2009
2290
|
try {
|
|
2010
2291
|
const page = doc.page(fix.pageObjectNumber);
|
|
2011
2292
|
const before = await page.annotations.list();
|
|
@@ -2035,7 +2316,7 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
2035
2316
|
}
|
|
2036
2317
|
});
|
|
2037
2318
|
test("delete by objectNumber removes the annotation and reports a stable id", async () => {
|
|
2038
|
-
const doc = await openFixture$
|
|
2319
|
+
const doc = await openFixture$18(engine, opts);
|
|
2039
2320
|
try {
|
|
2040
2321
|
const page = doc.page(fix.pageObjectNumber);
|
|
2041
2322
|
const draft = {
|
|
@@ -2065,7 +2346,7 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
2065
2346
|
}
|
|
2066
2347
|
});
|
|
2067
2348
|
if (fix.expectsWeakAnnotation) test("delete by index of a weak annotation reports deleted: null and refetch reason", async () => {
|
|
2068
|
-
const doc = await openFixture$
|
|
2349
|
+
const doc = await openFixture$18(engine, opts);
|
|
2069
2350
|
try {
|
|
2070
2351
|
const page = doc.page(fix.pageObjectNumber);
|
|
2071
2352
|
const weak = (await page.annotations.list()).annotations.find((a) => a.identityQuality === "weak");
|
|
@@ -2088,7 +2369,7 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
2088
2369
|
}
|
|
2089
2370
|
});
|
|
2090
2371
|
test("abort on create rejects with AbortError", async () => {
|
|
2091
|
-
const doc = await openFixture$
|
|
2372
|
+
const doc = await openFixture$18(engine, opts);
|
|
2092
2373
|
try {
|
|
2093
2374
|
const page = doc.page(fix.pageObjectNumber);
|
|
2094
2375
|
const draft = {
|
|
@@ -2104,7 +2385,7 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
2104
2385
|
}
|
|
2105
2386
|
});
|
|
2106
2387
|
test("update with a stale index revision throws InvalidReference", async () => {
|
|
2107
|
-
const doc = await openFixture$
|
|
2388
|
+
const doc = await openFixture$18(engine, opts);
|
|
2108
2389
|
try {
|
|
2109
2390
|
const page = doc.page(fix.pageObjectNumber);
|
|
2110
2391
|
const weak = (await page.annotations.list()).annotations.find((a) => a.identityQuality === "weak");
|
|
@@ -2131,7 +2412,7 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
2131
2412
|
}
|
|
2132
2413
|
});
|
|
2133
2414
|
test("move single durable annotation reorders within the page (single-as-batch)", async () => {
|
|
2134
|
-
const doc = await openFixture$
|
|
2415
|
+
const doc = await openFixture$18(engine, opts);
|
|
2135
2416
|
try {
|
|
2136
2417
|
const page = doc.page(fix.pageObjectNumber);
|
|
2137
2418
|
const aDraft = {
|
|
@@ -2175,7 +2456,7 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
2175
2456
|
}
|
|
2176
2457
|
});
|
|
2177
2458
|
test("move multi-block preserves caller-supplied order at the destination", async () => {
|
|
2178
|
-
const doc = await openFixture$
|
|
2459
|
+
const doc = await openFixture$18(engine, opts);
|
|
2179
2460
|
try {
|
|
2180
2461
|
const page = doc.page(fix.pageObjectNumber);
|
|
2181
2462
|
const ids = await Promise.all([
|
|
@@ -2214,7 +2495,7 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
2214
2495
|
}
|
|
2215
2496
|
});
|
|
2216
2497
|
if (fix.expectsWeakAnnotation) test("move on a weak annotation upgrades it to durable /NM (one rev bump for batch)", async () => {
|
|
2217
|
-
const doc = await openFixture$
|
|
2498
|
+
const doc = await openFixture$18(engine, opts);
|
|
2218
2499
|
try {
|
|
2219
2500
|
const page = doc.page(fix.pageObjectNumber);
|
|
2220
2501
|
const before = await page.annotations.list();
|
|
@@ -2239,7 +2520,7 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
2239
2520
|
}
|
|
2240
2521
|
});
|
|
2241
2522
|
test("move with a stale index revision rejects (locked rev-token guard)", async () => {
|
|
2242
|
-
const doc = await openFixture$
|
|
2523
|
+
const doc = await openFixture$18(engine, opts);
|
|
2243
2524
|
try {
|
|
2244
2525
|
const page = doc.page(fix.pageObjectNumber);
|
|
2245
2526
|
const a = await page.annotations.create({
|
|
@@ -2277,7 +2558,7 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
2277
2558
|
}
|
|
2278
2559
|
});
|
|
2279
2560
|
test("move with out-of-range toIndex rejects with InvalidArg", async () => {
|
|
2280
|
-
const doc = await openFixture$
|
|
2561
|
+
const doc = await openFixture$18(engine, opts);
|
|
2281
2562
|
try {
|
|
2282
2563
|
const page = doc.page(fix.pageObjectNumber);
|
|
2283
2564
|
const a = await page.annotations.create({
|
|
@@ -2301,7 +2582,7 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
2301
2582
|
}
|
|
2302
2583
|
});
|
|
2303
2584
|
test("move with duplicate refs rejects with InvalidArg", async () => {
|
|
2304
|
-
const doc = await openFixture$
|
|
2585
|
+
const doc = await openFixture$18(engine, opts);
|
|
2305
2586
|
try {
|
|
2306
2587
|
const page = doc.page(fix.pageObjectNumber);
|
|
2307
2588
|
const a = await page.annotations.create({
|
|
@@ -2324,7 +2605,7 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
2324
2605
|
}
|
|
2325
2606
|
});
|
|
2326
2607
|
test("abort on move rejects with AbortError", async () => {
|
|
2327
|
-
const doc = await openFixture$
|
|
2608
|
+
const doc = await openFixture$18(engine, opts);
|
|
2328
2609
|
try {
|
|
2329
2610
|
const page = doc.page(fix.pageObjectNumber);
|
|
2330
2611
|
const a = await page.annotations.create({
|
|
@@ -2345,7 +2626,7 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
2345
2626
|
}
|
|
2346
2627
|
});
|
|
2347
2628
|
test("a page move does NOT bump per-page RevisionTokens (weak refs survive reorder)", async () => {
|
|
2348
|
-
const doc = await openFixture$
|
|
2629
|
+
const doc = await openFixture$18(engine, opts);
|
|
2349
2630
|
try {
|
|
2350
2631
|
const list = await doc.pages.list();
|
|
2351
2632
|
if (list.pages.length < 1) return;
|
|
@@ -2360,7 +2641,7 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
2360
2641
|
}
|
|
2361
2642
|
});
|
|
2362
2643
|
test("create a reply links /IRT and defaults /RT to \"reply\", reporting the parent", async () => {
|
|
2363
|
-
const doc = await openFixture$
|
|
2644
|
+
const doc = await openFixture$18(engine, opts);
|
|
2364
2645
|
try {
|
|
2365
2646
|
const page = doc.page(fix.pageObjectNumber);
|
|
2366
2647
|
const parent = await page.annotations.create({
|
|
@@ -2394,7 +2675,7 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
2394
2675
|
}
|
|
2395
2676
|
});
|
|
2396
2677
|
test("create a grouped subordinate writes /RT /Group", async () => {
|
|
2397
|
-
const doc = await openFixture$
|
|
2678
|
+
const doc = await openFixture$18(engine, opts);
|
|
2398
2679
|
try {
|
|
2399
2680
|
const page = doc.page(fix.pageObjectNumber);
|
|
2400
2681
|
const primary = await page.annotations.create({
|
|
@@ -2425,7 +2706,7 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
2425
2706
|
}
|
|
2426
2707
|
});
|
|
2427
2708
|
test("create link annotations round-trip uri, goto/xyz, goto/fitH, and dead targets", async () => {
|
|
2428
|
-
const doc = await openFixture$
|
|
2709
|
+
const doc = await openFixture$18(engine, opts);
|
|
2429
2710
|
try {
|
|
2430
2711
|
const page = doc.page(fix.pageObjectNumber);
|
|
2431
2712
|
const uri = await page.annotations.create({
|
|
@@ -2502,7 +2783,7 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
2502
2783
|
}
|
|
2503
2784
|
});
|
|
2504
2785
|
test("a link patch retargets in both directions (uri→goto, goto→uri) and moves the rect", async () => {
|
|
2505
|
-
const doc = await openFixture$
|
|
2786
|
+
const doc = await openFixture$18(engine, opts);
|
|
2506
2787
|
try {
|
|
2507
2788
|
const page = doc.page(fix.pageObjectNumber);
|
|
2508
2789
|
const created = await page.annotations.create({
|
|
@@ -2557,7 +2838,7 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
2557
2838
|
}
|
|
2558
2839
|
});
|
|
2559
2840
|
test("a link patch clears the target with null — a dead link on re-read", async () => {
|
|
2560
|
-
const doc = await openFixture$
|
|
2841
|
+
const doc = await openFixture$18(engine, opts);
|
|
2561
2842
|
try {
|
|
2562
2843
|
const page = doc.page(fix.pageObjectNumber);
|
|
2563
2844
|
const created = await page.annotations.create({
|
|
@@ -2596,7 +2877,7 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
2596
2877
|
}
|
|
2597
2878
|
});
|
|
2598
2879
|
test("a link grouped to a highlight round-trips /IRT + /RT /Group with its target", async () => {
|
|
2599
|
-
const doc = await openFixture$
|
|
2880
|
+
const doc = await openFixture$18(engine, opts);
|
|
2600
2881
|
try {
|
|
2601
2882
|
const page = doc.page(fix.pageObjectNumber);
|
|
2602
2883
|
const parent = await page.annotations.create({
|
|
@@ -2630,7 +2911,7 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
2630
2911
|
}
|
|
2631
2912
|
});
|
|
2632
2913
|
test("replace-text round-trips /IT and groups StrikeOut under its Caret", async () => {
|
|
2633
|
-
const doc = await openFixture$
|
|
2914
|
+
const doc = await openFixture$18(engine, opts);
|
|
2634
2915
|
try {
|
|
2635
2916
|
const page = doc.page(fix.pageObjectNumber);
|
|
2636
2917
|
const caret = await page.annotations.create({
|
|
@@ -2682,7 +2963,7 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
2682
2963
|
}
|
|
2683
2964
|
});
|
|
2684
2965
|
test("patch inReplyTo: null clears /IRT and /RT (back to top-level)", async () => {
|
|
2685
|
-
const doc = await openFixture$
|
|
2966
|
+
const doc = await openFixture$18(engine, opts);
|
|
2686
2967
|
try {
|
|
2687
2968
|
const page = doc.page(fix.pageObjectNumber);
|
|
2688
2969
|
const parent = await page.annotations.create({
|
|
@@ -2709,7 +2990,7 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
2709
2990
|
}
|
|
2710
2991
|
});
|
|
2711
2992
|
test("create with a cross-page /IRT parent throws InvalidArg", async () => {
|
|
2712
|
-
const doc = await openFixture$
|
|
2993
|
+
const doc = await openFixture$18(engine, opts);
|
|
2713
2994
|
try {
|
|
2714
2995
|
const page = doc.page(fix.pageObjectNumber);
|
|
2715
2996
|
const parent = await page.annotations.create({
|
|
@@ -2740,7 +3021,7 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
2740
3021
|
}
|
|
2741
3022
|
});
|
|
2742
3023
|
test("a review-status reply round-trips /State + /StateModel + /Subj", async () => {
|
|
2743
|
-
const doc = await openFixture$
|
|
3024
|
+
const doc = await openFixture$18(engine, opts);
|
|
2744
3025
|
try {
|
|
2745
3026
|
const page = doc.page(fix.pageObjectNumber);
|
|
2746
3027
|
const target = await page.annotations.create({
|
|
@@ -2774,7 +3055,7 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
2774
3055
|
}
|
|
2775
3056
|
});
|
|
2776
3057
|
test("a draft /State without /StateModel is rejected with InvalidArg", async () => {
|
|
2777
|
-
const doc = await openFixture$
|
|
3058
|
+
const doc = await openFixture$18(engine, opts);
|
|
2778
3059
|
try {
|
|
2779
3060
|
const page = doc.page(fix.pageObjectNumber);
|
|
2780
3061
|
let caught;
|
|
@@ -2793,7 +3074,7 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
2793
3074
|
}
|
|
2794
3075
|
});
|
|
2795
3076
|
test("null-clear patches truly remove /State, /StateModel, /Subj and /Contents", async () => {
|
|
2796
|
-
const doc = await openFixture$
|
|
3077
|
+
const doc = await openFixture$18(engine, opts);
|
|
2797
3078
|
try {
|
|
2798
3079
|
const page = doc.page(fix.pageObjectNumber);
|
|
2799
3080
|
const note = await page.annotations.create({
|
|
@@ -2840,7 +3121,7 @@ function runAnnotationMutationConformance(runner, opts) {
|
|
|
2840
3121
|
}
|
|
2841
3122
|
});
|
|
2842
3123
|
test("custom state models round-trip verbatim", async () => {
|
|
2843
|
-
const doc = await openFixture$
|
|
3124
|
+
const doc = await openFixture$18(engine, opts);
|
|
2844
3125
|
try {
|
|
2845
3126
|
const page = doc.page(fix.pageObjectNumber);
|
|
2846
3127
|
const custom = await page.annotations.create({
|
|
@@ -2868,7 +3149,7 @@ async function beginWeakEditIfRequired(doc, pageObjectNumber, fix) {
|
|
|
2868
3149
|
if (doc.capabilities.weakAnnotationEditSessions !== "required" || !fix.expectsWeakAnnotation) return null;
|
|
2869
3150
|
return doc.annotations.beginWeakEdit([pageObjectNumber]);
|
|
2870
3151
|
}
|
|
2871
|
-
async function openFixture$
|
|
3152
|
+
async function openFixture$18(engine, opts) {
|
|
2872
3153
|
if (opts.openKind === "bytes") {
|
|
2873
3154
|
const bytes = await opts.fixture.bytes();
|
|
2874
3155
|
return engine.open({
|
|
@@ -3458,7 +3739,7 @@ function runAnnotationAppearanceConformance(runner, opts) {
|
|
|
3458
3739
|
if (engine) await engine.destroy();
|
|
3459
3740
|
});
|
|
3460
3741
|
test("renders the expected set of appearances with valid output", async () => {
|
|
3461
|
-
const doc = await openFixture$
|
|
3742
|
+
const doc = await openFixture$17(engine, opts);
|
|
3462
3743
|
try {
|
|
3463
3744
|
const { pageState, appearances } = await collect(doc, opts, useRaw);
|
|
3464
3745
|
expect(pageState.pageObjectNumber).toBe(opts.fixture.pageObjectNumber);
|
|
@@ -3485,7 +3766,7 @@ function runAnnotationAppearanceConformance(runner, opts) {
|
|
|
3485
3766
|
}
|
|
3486
3767
|
});
|
|
3487
3768
|
if (opts.fixture.expectsWeakAppearance) test("weak (index-only) annotations are emitted, not dropped", async () => {
|
|
3488
|
-
const doc = await openFixture$
|
|
3769
|
+
const doc = await openFixture$17(engine, opts);
|
|
3489
3770
|
try {
|
|
3490
3771
|
const { appearances } = await collect(doc, opts, useRaw);
|
|
3491
3772
|
const weak = appearances.find((a) => a.ref.kind === "index");
|
|
@@ -3495,7 +3776,7 @@ function runAnnotationAppearanceConformance(runner, opts) {
|
|
|
3495
3776
|
}
|
|
3496
3777
|
});
|
|
3497
3778
|
if (useRaw) test("rendered appearances are not blank (non-zero alpha)", async () => {
|
|
3498
|
-
const doc = await openFixture$
|
|
3779
|
+
const doc = await openFixture$17(engine, opts);
|
|
3499
3780
|
try {
|
|
3500
3781
|
const { appearances } = await collect(doc, opts, useRaw);
|
|
3501
3782
|
const anyVisible = appearances.some((a) => a.raster !== null && hasOpaquePixel(a.raster));
|
|
@@ -3505,7 +3786,7 @@ function runAnnotationAppearanceConformance(runner, opts) {
|
|
|
3505
3786
|
}
|
|
3506
3787
|
});
|
|
3507
3788
|
test("abort() rejects with AbortError", async () => {
|
|
3508
|
-
const doc = await openFixture$
|
|
3789
|
+
const doc = await openFixture$17(engine, opts);
|
|
3509
3790
|
try {
|
|
3510
3791
|
const page = doc.page(opts.fixture.pageObjectNumber);
|
|
3511
3792
|
const p = useRaw ? page.annotations.renderAppearances({ scale: 1 }) : page.annotations.renderAppearanceImages({
|
|
@@ -3560,7 +3841,7 @@ function hasOpaquePixel(raster) {
|
|
|
3560
3841
|
}
|
|
3561
3842
|
return false;
|
|
3562
3843
|
}
|
|
3563
|
-
async function openFixture$
|
|
3844
|
+
async function openFixture$17(engine, opts) {
|
|
3564
3845
|
if (opts.openKind === "bytes") {
|
|
3565
3846
|
const bytes = await opts.fixture.bytes();
|
|
3566
3847
|
return engine.open({
|
|
@@ -3629,7 +3910,7 @@ function runPageReorderConformance(runner, opts) {
|
|
|
3629
3910
|
if (engine) await engine.destroy();
|
|
3630
3911
|
});
|
|
3631
3912
|
test("pages.list() returns every page in display order, durable PONs only", async () => {
|
|
3632
|
-
const doc = await openFixture$
|
|
3913
|
+
const doc = await openFixture$16(engine, opts);
|
|
3633
3914
|
try {
|
|
3634
3915
|
const list = await doc.pages.list();
|
|
3635
3916
|
expect(require_schemas.PageListSnapshotSchema.safeParse(list).success).toBe(true);
|
|
@@ -3648,7 +3929,7 @@ function runPageReorderConformance(runner, opts) {
|
|
|
3648
3929
|
}
|
|
3649
3930
|
});
|
|
3650
3931
|
test("pages.move() reorders pages and returns the full post-move order", async () => {
|
|
3651
|
-
const doc = await openFixture$
|
|
3932
|
+
const doc = await openFixture$16(engine, opts);
|
|
3652
3933
|
try {
|
|
3653
3934
|
const before = await doc.pages.list();
|
|
3654
3935
|
if (before.pages.length < 3) return;
|
|
@@ -3672,7 +3953,7 @@ function runPageReorderConformance(runner, opts) {
|
|
|
3672
3953
|
}
|
|
3673
3954
|
});
|
|
3674
3955
|
test("weak index-based annotation refs survive a page reorder", async () => {
|
|
3675
|
-
const doc = await openFixture$
|
|
3956
|
+
const doc = await openFixture$16(engine, opts);
|
|
3676
3957
|
try {
|
|
3677
3958
|
const list = await doc.pages.list();
|
|
3678
3959
|
if (list.pages.length < 2) return;
|
|
@@ -3709,7 +3990,7 @@ function runPageReorderConformance(runner, opts) {
|
|
|
3709
3990
|
}
|
|
3710
3991
|
});
|
|
3711
3992
|
test("pages.move() rejects duplicate PONs with InvalidArg", async () => {
|
|
3712
|
-
const doc = await openFixture$
|
|
3993
|
+
const doc = await openFixture$16(engine, opts);
|
|
3713
3994
|
try {
|
|
3714
3995
|
const list = await doc.pages.list();
|
|
3715
3996
|
if (list.pages.length < 1) return;
|
|
@@ -3726,7 +4007,7 @@ function runPageReorderConformance(runner, opts) {
|
|
|
3726
4007
|
}
|
|
3727
4008
|
});
|
|
3728
4009
|
test("pages.move() rejects out-of-range destIndex with InvalidArg", async () => {
|
|
3729
|
-
const doc = await openFixture$
|
|
4010
|
+
const doc = await openFixture$16(engine, opts);
|
|
3730
4011
|
try {
|
|
3731
4012
|
const list = await doc.pages.list();
|
|
3732
4013
|
if (list.pages.length < 1) return;
|
|
@@ -3743,7 +4024,7 @@ function runPageReorderConformance(runner, opts) {
|
|
|
3743
4024
|
}
|
|
3744
4025
|
});
|
|
3745
4026
|
test("pages.move() rejects unknown PON with NotFound or InvalidArg", async () => {
|
|
3746
|
-
const doc = await openFixture$
|
|
4027
|
+
const doc = await openFixture$16(engine, opts);
|
|
3747
4028
|
try {
|
|
3748
4029
|
const list = await doc.pages.list();
|
|
3749
4030
|
let bogus = 0;
|
|
@@ -3761,7 +4042,7 @@ function runPageReorderConformance(runner, opts) {
|
|
|
3761
4042
|
}
|
|
3762
4043
|
});
|
|
3763
4044
|
test("abort on pages.move() rejects with AbortError", async () => {
|
|
3764
|
-
const doc = await openFixture$
|
|
4045
|
+
const doc = await openFixture$16(engine, opts);
|
|
3765
4046
|
try {
|
|
3766
4047
|
const list = await doc.pages.list();
|
|
3767
4048
|
if (list.pages.length < 1) return;
|
|
@@ -3775,7 +4056,7 @@ function runPageReorderConformance(runner, opts) {
|
|
|
3775
4056
|
});
|
|
3776
4057
|
});
|
|
3777
4058
|
}
|
|
3778
|
-
async function openFixture$
|
|
4059
|
+
async function openFixture$16(engine, opts) {
|
|
3779
4060
|
if (opts.openKind === "bytes") {
|
|
3780
4061
|
const bytes = await opts.fixture.bytes();
|
|
3781
4062
|
return engine.open({
|
|
@@ -3849,7 +4130,7 @@ function runPageRotateConformance(runner, opts) {
|
|
|
3849
4130
|
if (engine) await engine.destroy();
|
|
3850
4131
|
});
|
|
3851
4132
|
test("pages.rotate() sets the absolute rotation and returns the full layout", async () => {
|
|
3852
|
-
const doc = await openFixture$
|
|
4133
|
+
const doc = await openFixture$15(engine, opts);
|
|
3853
4134
|
try {
|
|
3854
4135
|
const before = await doc.pages.list();
|
|
3855
4136
|
const target = before.pages[0];
|
|
@@ -3868,7 +4149,7 @@ function runPageRotateConformance(runner, opts) {
|
|
|
3868
4149
|
}
|
|
3869
4150
|
});
|
|
3870
4151
|
test("absolute rotation is idempotent: setting the same value twice is a no-op", async () => {
|
|
3871
|
-
const doc = await openFixture$
|
|
4152
|
+
const doc = await openFixture$15(engine, opts);
|
|
3872
4153
|
try {
|
|
3873
4154
|
const pon = (await doc.pages.list()).pages[0].pageObjectNumber;
|
|
3874
4155
|
const first = await doc.pages.rotate([pon], 180);
|
|
@@ -3879,7 +4160,7 @@ function runPageRotateConformance(runner, opts) {
|
|
|
3879
4160
|
}
|
|
3880
4161
|
});
|
|
3881
4162
|
test("one rotation applies to EVERY listed page (the multi-select gesture)", async () => {
|
|
3882
|
-
const doc = await openFixture$
|
|
4163
|
+
const doc = await openFixture$15(engine, opts);
|
|
3883
4164
|
try {
|
|
3884
4165
|
const list = await doc.pages.list();
|
|
3885
4166
|
if (list.pages.length < 2) return;
|
|
@@ -3891,7 +4172,7 @@ function runPageRotateConformance(runner, opts) {
|
|
|
3891
4172
|
}
|
|
3892
4173
|
});
|
|
3893
4174
|
test("weak index-based annotation refs survive a rotate (no revision bump)", async () => {
|
|
3894
|
-
const doc = await openFixture$
|
|
4175
|
+
const doc = await openFixture$15(engine, opts);
|
|
3895
4176
|
try {
|
|
3896
4177
|
const hostPon = (await doc.pages.list()).pages[0].pageObjectNumber;
|
|
3897
4178
|
const hostPage = doc.page(hostPon);
|
|
@@ -3921,7 +4202,7 @@ function runPageRotateConformance(runner, opts) {
|
|
|
3921
4202
|
}
|
|
3922
4203
|
});
|
|
3923
4204
|
test("pages.rotate() rejects an invalid rotation value with InvalidArg", async () => {
|
|
3924
|
-
const doc = await openFixture$
|
|
4205
|
+
const doc = await openFixture$15(engine, opts);
|
|
3925
4206
|
try {
|
|
3926
4207
|
const pon = (await doc.pages.list()).pages[0].pageObjectNumber;
|
|
3927
4208
|
let caught;
|
|
@@ -3936,7 +4217,7 @@ function runPageRotateConformance(runner, opts) {
|
|
|
3936
4217
|
}
|
|
3937
4218
|
});
|
|
3938
4219
|
test("pages.rotate() rejects duplicate PONs with InvalidArg", async () => {
|
|
3939
|
-
const doc = await openFixture$
|
|
4220
|
+
const doc = await openFixture$15(engine, opts);
|
|
3940
4221
|
try {
|
|
3941
4222
|
const pon = (await doc.pages.list()).pages[0].pageObjectNumber;
|
|
3942
4223
|
let caught;
|
|
@@ -3951,7 +4232,7 @@ function runPageRotateConformance(runner, opts) {
|
|
|
3951
4232
|
}
|
|
3952
4233
|
});
|
|
3953
4234
|
test("pages.rotate() rejects unknown PON with NotFound or InvalidArg", async () => {
|
|
3954
|
-
const doc = await openFixture$
|
|
4235
|
+
const doc = await openFixture$15(engine, opts);
|
|
3955
4236
|
try {
|
|
3956
4237
|
const list = await doc.pages.list();
|
|
3957
4238
|
let bogus = 0;
|
|
@@ -3969,7 +4250,7 @@ function runPageRotateConformance(runner, opts) {
|
|
|
3969
4250
|
}
|
|
3970
4251
|
});
|
|
3971
4252
|
test("abort on pages.rotate() rejects with AbortError", async () => {
|
|
3972
|
-
const doc = await openFixture$
|
|
4253
|
+
const doc = await openFixture$15(engine, opts);
|
|
3973
4254
|
try {
|
|
3974
4255
|
const pon = (await doc.pages.list()).pages[0].pageObjectNumber;
|
|
3975
4256
|
const p = doc.pages.rotate([pon], 90);
|
|
@@ -3981,7 +4262,7 @@ function runPageRotateConformance(runner, opts) {
|
|
|
3981
4262
|
});
|
|
3982
4263
|
});
|
|
3983
4264
|
}
|
|
3984
|
-
async function openFixture$
|
|
4265
|
+
async function openFixture$15(engine, opts) {
|
|
3985
4266
|
if (opts.openKind === "bytes") {
|
|
3986
4267
|
const bytes = await opts.fixture.bytes();
|
|
3987
4268
|
return engine.open({
|
|
@@ -4044,7 +4325,7 @@ function runPageDeleteConformance(runner, opts) {
|
|
|
4044
4325
|
if (engine) await engine.destroy();
|
|
4045
4326
|
});
|
|
4046
4327
|
test("pages.delete() removes the page and returns the surviving layout", async () => {
|
|
4047
|
-
const doc = await openFixture$
|
|
4328
|
+
const doc = await openFixture$14(engine, opts);
|
|
4048
4329
|
try {
|
|
4049
4330
|
const before = await doc.pages.list();
|
|
4050
4331
|
if (before.pages.length < 2) return;
|
|
@@ -4065,7 +4346,7 @@ function runPageDeleteConformance(runner, opts) {
|
|
|
4065
4346
|
}
|
|
4066
4347
|
});
|
|
4067
4348
|
test("addressing a deleted page afterwards is a clean NotFound", async () => {
|
|
4068
|
-
const doc = await openFixture$
|
|
4349
|
+
const doc = await openFixture$14(engine, opts);
|
|
4069
4350
|
try {
|
|
4070
4351
|
const before = await doc.pages.list();
|
|
4071
4352
|
if (before.pages.length < 2) return;
|
|
@@ -4083,7 +4364,7 @@ function runPageDeleteConformance(runner, opts) {
|
|
|
4083
4364
|
}
|
|
4084
4365
|
});
|
|
4085
4366
|
test("surviving pages keep their weak index-based annotation refs", async () => {
|
|
4086
|
-
const doc = await openFixture$
|
|
4367
|
+
const doc = await openFixture$14(engine, opts);
|
|
4087
4368
|
try {
|
|
4088
4369
|
const list = await doc.pages.list();
|
|
4089
4370
|
if (list.pages.length < 2) return;
|
|
@@ -4116,7 +4397,7 @@ function runPageDeleteConformance(runner, opts) {
|
|
|
4116
4397
|
}
|
|
4117
4398
|
});
|
|
4118
4399
|
test("deleting EVERY page is rejected with InvalidArg", async () => {
|
|
4119
|
-
const doc = await openFixture$
|
|
4400
|
+
const doc = await openFixture$14(engine, opts);
|
|
4120
4401
|
try {
|
|
4121
4402
|
const list = await doc.pages.list();
|
|
4122
4403
|
let caught;
|
|
@@ -4131,7 +4412,7 @@ function runPageDeleteConformance(runner, opts) {
|
|
|
4131
4412
|
}
|
|
4132
4413
|
});
|
|
4133
4414
|
test("pages.delete() rejects duplicate PONs with InvalidArg", async () => {
|
|
4134
|
-
const doc = await openFixture$
|
|
4415
|
+
const doc = await openFixture$14(engine, opts);
|
|
4135
4416
|
try {
|
|
4136
4417
|
const pon = (await doc.pages.list()).pages[0].pageObjectNumber;
|
|
4137
4418
|
let caught;
|
|
@@ -4146,7 +4427,7 @@ function runPageDeleteConformance(runner, opts) {
|
|
|
4146
4427
|
}
|
|
4147
4428
|
});
|
|
4148
4429
|
test("pages.delete() rejects unknown PON with NotFound or InvalidArg", async () => {
|
|
4149
|
-
const doc = await openFixture$
|
|
4430
|
+
const doc = await openFixture$14(engine, opts);
|
|
4150
4431
|
try {
|
|
4151
4432
|
const list = await doc.pages.list();
|
|
4152
4433
|
let bogus = 0;
|
|
@@ -4164,7 +4445,7 @@ function runPageDeleteConformance(runner, opts) {
|
|
|
4164
4445
|
}
|
|
4165
4446
|
});
|
|
4166
4447
|
test("abort on pages.delete() rejects with AbortError", async () => {
|
|
4167
|
-
const doc = await openFixture$
|
|
4448
|
+
const doc = await openFixture$14(engine, opts);
|
|
4168
4449
|
try {
|
|
4169
4450
|
const list = await doc.pages.list();
|
|
4170
4451
|
if (list.pages.length < 2) return;
|
|
@@ -4178,7 +4459,224 @@ function runPageDeleteConformance(runner, opts) {
|
|
|
4178
4459
|
});
|
|
4179
4460
|
});
|
|
4180
4461
|
}
|
|
4181
|
-
async function openFixture$
|
|
4462
|
+
async function openFixture$14(engine, opts) {
|
|
4463
|
+
if (opts.openKind === "bytes") {
|
|
4464
|
+
const bytes = await opts.fixture.bytes();
|
|
4465
|
+
return engine.open({
|
|
4466
|
+
kind: "bytes",
|
|
4467
|
+
id: opts.fixture.id,
|
|
4468
|
+
bytes
|
|
4469
|
+
});
|
|
4470
|
+
}
|
|
4471
|
+
return engine.open({
|
|
4472
|
+
kind: "id",
|
|
4473
|
+
id: opts.fixture.cloudId ?? opts.fixture.id
|
|
4474
|
+
});
|
|
4475
|
+
}
|
|
4476
|
+
//#endregion
|
|
4477
|
+
//#region src/conformance/runNamedPagesConformance.ts
|
|
4478
|
+
/**
|
|
4479
|
+
* Named-pages conformance: the catalog's `/Names /Pages` registry as LAYOUT
|
|
4480
|
+
* data. Locks the invariants both engines must share — do not loosen
|
|
4481
|
+
* without re-reading `NamedPageEntry` and `PageNameResult`:
|
|
4482
|
+
*
|
|
4483
|
+
* 1. `pages.list().namedPages` is present (possibly empty) and every
|
|
4484
|
+
* registration resolves to a page in `pages`, a template, or is
|
|
4485
|
+
* reported dangling — never silently dropped.
|
|
4486
|
+
* 2. `pages.setName()` creates or replaces by decoded key (idempotent),
|
|
4487
|
+
* `replace` renames in one job, and the result IS the new layout.
|
|
4488
|
+
* 3. `pages.removeName()` drops the registration and nothing else.
|
|
4489
|
+
* 4. Deleting a page removes every registration pointing at it — inside
|
|
4490
|
+
* the delete, with no plugin in the loop.
|
|
4491
|
+
* 5. Non-ASCII keys round-trip unchanged (Acrobat writes UTF-16 + BOM).
|
|
4492
|
+
*
|
|
4493
|
+
* Skips cleanly when the engine does not implement the optional members.
|
|
4494
|
+
*/
|
|
4495
|
+
function runNamedPagesConformance(runner, opts) {
|
|
4496
|
+
const { describe, test, beforeAll, afterAll, expect } = runner;
|
|
4497
|
+
describe(`named pages conformance: ${opts.label}`, () => {
|
|
4498
|
+
let engine;
|
|
4499
|
+
beforeAll(async () => {
|
|
4500
|
+
engine = await opts.makeEngine();
|
|
4501
|
+
});
|
|
4502
|
+
afterAll(async () => {
|
|
4503
|
+
if (engine) await engine.destroy();
|
|
4504
|
+
});
|
|
4505
|
+
test("pages.list() carries namedPages and every entry resolves", async () => {
|
|
4506
|
+
const doc = await openFixture$13(engine, opts);
|
|
4507
|
+
try {
|
|
4508
|
+
const layout = await doc.pages.list();
|
|
4509
|
+
require_schemas.PageListSnapshotSchema.parse(layout);
|
|
4510
|
+
expect(Array.isArray(layout.namedPages)).toBe(true);
|
|
4511
|
+
const pons = new Set(layout.pages.map((page) => page.pageObjectNumber));
|
|
4512
|
+
for (const entry of layout.namedPages ?? []) {
|
|
4513
|
+
expect(entry.name.length > 0).toBe(true);
|
|
4514
|
+
if (entry.target.kind === "page") expect(pons.has(entry.target.pageObjectNumber)).toBe(true);
|
|
4515
|
+
}
|
|
4516
|
+
} finally {
|
|
4517
|
+
await doc.close();
|
|
4518
|
+
}
|
|
4519
|
+
});
|
|
4520
|
+
test("pages.setName() registers, replaces by key, and renames via `replace`", async () => {
|
|
4521
|
+
const doc = await openFixture$13(engine, opts);
|
|
4522
|
+
try {
|
|
4523
|
+
if (!doc.pages.setName) return;
|
|
4524
|
+
const before = await doc.pages.list();
|
|
4525
|
+
const [first, second] = before.pages;
|
|
4526
|
+
const baseline = (before.namedPages ?? []).length;
|
|
4527
|
+
const created = await doc.pages.setName({
|
|
4528
|
+
name: "Approved=Goedgekeurd",
|
|
4529
|
+
pageObjectNumber: first.pageObjectNumber
|
|
4530
|
+
});
|
|
4531
|
+
require_schemas.PageNameResultSchema.parse(created);
|
|
4532
|
+
expect(created.layout.pages.length).toBe(before.pages.length);
|
|
4533
|
+
expect(find(created, "Approved=Goedgekeurd")).toEqual({
|
|
4534
|
+
kind: "page",
|
|
4535
|
+
pageObjectNumber: first.pageObjectNumber
|
|
4536
|
+
});
|
|
4537
|
+
expect((created.layout.namedPages ?? []).length).toBe(baseline + 1);
|
|
4538
|
+
if (created.cache) {
|
|
4539
|
+
expect(created.cache.docVersion > created.cache.previousDocVersion).toBe(true);
|
|
4540
|
+
expect(created.cache.layoutVersion > 0).toBe(true);
|
|
4541
|
+
}
|
|
4542
|
+
const listed = await doc.pages.list();
|
|
4543
|
+
expect(listed.namedPages).toEqual(created.layout.namedPages);
|
|
4544
|
+
const replaced = await doc.pages.setName({
|
|
4545
|
+
name: "Approved=Goedgekeurd",
|
|
4546
|
+
pageObjectNumber: second.pageObjectNumber
|
|
4547
|
+
});
|
|
4548
|
+
expect(find(replaced, "Approved=Goedgekeurd")).toEqual({
|
|
4549
|
+
kind: "page",
|
|
4550
|
+
pageObjectNumber: second.pageObjectNumber
|
|
4551
|
+
});
|
|
4552
|
+
expect((replaced.layout.namedPages ?? []).length).toBe(baseline + 1);
|
|
4553
|
+
const renamed = await doc.pages.setName({
|
|
4554
|
+
name: "Approved=Approved",
|
|
4555
|
+
pageObjectNumber: second.pageObjectNumber,
|
|
4556
|
+
replace: "Approved=Goedgekeurd"
|
|
4557
|
+
});
|
|
4558
|
+
expect(find(renamed, "Approved=Goedgekeurd")).toBe(void 0);
|
|
4559
|
+
expect(find(renamed, "Approved=Approved")).toEqual({
|
|
4560
|
+
kind: "page",
|
|
4561
|
+
pageObjectNumber: second.pageObjectNumber
|
|
4562
|
+
});
|
|
4563
|
+
expect((renamed.layout.namedPages ?? []).length).toBe(baseline + 1);
|
|
4564
|
+
} finally {
|
|
4565
|
+
await doc.close();
|
|
4566
|
+
}
|
|
4567
|
+
});
|
|
4568
|
+
test("non-ASCII keys round-trip through set → list", async () => {
|
|
4569
|
+
const doc = await openFixture$13(engine, opts);
|
|
4570
|
+
try {
|
|
4571
|
+
if (!doc.pages.setName) return;
|
|
4572
|
+
const before = await doc.pages.list();
|
|
4573
|
+
const key = "Stämpel=Stämpel 日本";
|
|
4574
|
+
const result = await doc.pages.setName({
|
|
4575
|
+
name: key,
|
|
4576
|
+
pageObjectNumber: before.pages[0].pageObjectNumber
|
|
4577
|
+
});
|
|
4578
|
+
expect(find(result, key)).toEqual({
|
|
4579
|
+
kind: "page",
|
|
4580
|
+
pageObjectNumber: before.pages[0].pageObjectNumber
|
|
4581
|
+
});
|
|
4582
|
+
const listed = await doc.pages.list();
|
|
4583
|
+
expect((listed.namedPages ?? []).some((entry) => entry.name === key)).toBe(true);
|
|
4584
|
+
} finally {
|
|
4585
|
+
await doc.close();
|
|
4586
|
+
}
|
|
4587
|
+
});
|
|
4588
|
+
test("pages.removeName() drops the registration and keeps the page", async () => {
|
|
4589
|
+
const doc = await openFixture$13(engine, opts);
|
|
4590
|
+
try {
|
|
4591
|
+
if (!doc.pages.setName || !doc.pages.removeName) return;
|
|
4592
|
+
const before = await doc.pages.list();
|
|
4593
|
+
await doc.pages.setName({
|
|
4594
|
+
name: "Draft=Concept",
|
|
4595
|
+
pageObjectNumber: before.pages[0].pageObjectNumber
|
|
4596
|
+
});
|
|
4597
|
+
const removed = await doc.pages.removeName({ name: "Draft=Concept" });
|
|
4598
|
+
require_schemas.PageNameResultSchema.parse(removed);
|
|
4599
|
+
expect(find(removed, "Draft=Concept")).toBe(void 0);
|
|
4600
|
+
expect(removed.layout.pages.length).toBe(before.pages.length);
|
|
4601
|
+
await expect(doc.pages.removeName({ name: "Draft=Concept" })).rejects.toMatchObject({ code: require_PageInsertBlankInput.EngineErrorCode.NotFound });
|
|
4602
|
+
} finally {
|
|
4603
|
+
await doc.close();
|
|
4604
|
+
}
|
|
4605
|
+
});
|
|
4606
|
+
test("pages.setName() rejects an empty name and an unknown page", async () => {
|
|
4607
|
+
const doc = await openFixture$13(engine, opts);
|
|
4608
|
+
try {
|
|
4609
|
+
if (!doc.pages.setName) return;
|
|
4610
|
+
const before = await doc.pages.list();
|
|
4611
|
+
await expect(doc.pages.setName({
|
|
4612
|
+
name: "",
|
|
4613
|
+
pageObjectNumber: before.pages[0].pageObjectNumber
|
|
4614
|
+
})).rejects.toMatchObject({ code: require_PageInsertBlankInput.EngineErrorCode.InvalidArg });
|
|
4615
|
+
let unknownPage = null;
|
|
4616
|
+
try {
|
|
4617
|
+
await doc.pages.setName({
|
|
4618
|
+
name: "Ghost=Ghost",
|
|
4619
|
+
pageObjectNumber: 987654321
|
|
4620
|
+
});
|
|
4621
|
+
} catch (error) {
|
|
4622
|
+
unknownPage = error;
|
|
4623
|
+
}
|
|
4624
|
+
expect(require_fold.EngineError.is(unknownPage, require_PageInsertBlankInput.EngineErrorCode.NotFound) || require_fold.EngineError.is(unknownPage, require_PageInsertBlankInput.EngineErrorCode.InvalidArg)).toBe(true);
|
|
4625
|
+
} finally {
|
|
4626
|
+
await doc.close();
|
|
4627
|
+
}
|
|
4628
|
+
});
|
|
4629
|
+
test("deleting a page removes every registration pointing at it", async () => {
|
|
4630
|
+
const doc = await openFixture$13(engine, opts);
|
|
4631
|
+
try {
|
|
4632
|
+
if (!doc.pages.setName) return;
|
|
4633
|
+
const before = await doc.pages.list();
|
|
4634
|
+
if (before.pages.length < 2) return;
|
|
4635
|
+
const victim = before.pages[1].pageObjectNumber;
|
|
4636
|
+
const survivor = before.pages[0].pageObjectNumber;
|
|
4637
|
+
await doc.pages.setName({
|
|
4638
|
+
name: "Victim=One",
|
|
4639
|
+
pageObjectNumber: victim
|
|
4640
|
+
});
|
|
4641
|
+
await doc.pages.setName({
|
|
4642
|
+
name: "Victim=Two",
|
|
4643
|
+
pageObjectNumber: victim
|
|
4644
|
+
});
|
|
4645
|
+
await doc.pages.setName({
|
|
4646
|
+
name: "Survivor",
|
|
4647
|
+
pageObjectNumber: survivor
|
|
4648
|
+
});
|
|
4649
|
+
const deleted = await doc.pages.delete([victim]);
|
|
4650
|
+
const names = (deleted.layout.namedPages ?? []).map((entry) => entry.name);
|
|
4651
|
+
expect(names.includes("Victim=One")).toBe(false);
|
|
4652
|
+
expect(names.includes("Victim=Two")).toBe(false);
|
|
4653
|
+
expect(names).toContain("Survivor");
|
|
4654
|
+
expect((deleted.layout.namedPages ?? []).some((entry) => entry.target.kind === "dangling")).toBe(false);
|
|
4655
|
+
} finally {
|
|
4656
|
+
await doc.close();
|
|
4657
|
+
}
|
|
4658
|
+
});
|
|
4659
|
+
test("abort on pages.setName() rejects with AbortError", async () => {
|
|
4660
|
+
const doc = await openFixture$13(engine, opts);
|
|
4661
|
+
try {
|
|
4662
|
+
if (!doc.pages.setName) return;
|
|
4663
|
+
const before = await doc.pages.list();
|
|
4664
|
+
const promise = doc.pages.setName({
|
|
4665
|
+
name: "Aborted=Aborted",
|
|
4666
|
+
pageObjectNumber: before.pages[0].pageObjectNumber
|
|
4667
|
+
});
|
|
4668
|
+
promise.abort();
|
|
4669
|
+
await expect(promise).rejects.toBeInstanceOf(require_AbortError.AbortError);
|
|
4670
|
+
} finally {
|
|
4671
|
+
await doc.close();
|
|
4672
|
+
}
|
|
4673
|
+
});
|
|
4674
|
+
});
|
|
4675
|
+
}
|
|
4676
|
+
function find(result, name) {
|
|
4677
|
+
return (result.layout.namedPages ?? []).find((entry) => entry.name === name)?.target;
|
|
4678
|
+
}
|
|
4679
|
+
async function openFixture$13(engine, opts) {
|
|
4182
4680
|
if (opts.openKind === "bytes") {
|
|
4183
4681
|
const bytes = await opts.fixture.bytes();
|
|
4184
4682
|
return engine.open({
|
|
@@ -4193,6 +4691,243 @@ async function openFixture$11(engine, opts) {
|
|
|
4193
4691
|
});
|
|
4194
4692
|
}
|
|
4195
4693
|
//#endregion
|
|
4694
|
+
//#region src/conformance/runAnnotationFlattenConformance.ts
|
|
4695
|
+
const square$1 = (left, bottom) => ({
|
|
4696
|
+
subtype: "square",
|
|
4697
|
+
rect: {
|
|
4698
|
+
left,
|
|
4699
|
+
bottom,
|
|
4700
|
+
right: left + 40,
|
|
4701
|
+
top: bottom + 40
|
|
4702
|
+
},
|
|
4703
|
+
color: {
|
|
4704
|
+
r: 20,
|
|
4705
|
+
g: 40,
|
|
4706
|
+
b: 220
|
|
4707
|
+
},
|
|
4708
|
+
strokeWidth: 2
|
|
4709
|
+
});
|
|
4710
|
+
/**
|
|
4711
|
+
* Selective annotation flatten: `page(pon).annotations.flatten(refs)` is
|
|
4712
|
+
* `pages.flatten` for a chosen set. Locks, on every engine:
|
|
4713
|
+
* 1. only the given refs are painted and removed; the rest of the page's
|
|
4714
|
+
* annotations are untouched;
|
|
4715
|
+
* 2. per-ref status (`applied` / `skipped`), one `annotations.flattened`
|
|
4716
|
+
* event, a `meta` with that page's new pins, layout unchanged;
|
|
4717
|
+
* 3. a hidden ref is `skipped`; a ref on another page rejects `InvalidArg`
|
|
4718
|
+
* with nothing mutated.
|
|
4719
|
+
* Skips cleanly when the engine does not implement the optional member.
|
|
4720
|
+
*/
|
|
4721
|
+
function runAnnotationFlattenConformance(runner, opts) {
|
|
4722
|
+
const { describe, test, beforeAll, afterAll, expect } = runner;
|
|
4723
|
+
describe(`annotation flatten conformance: ${opts.label}`, () => {
|
|
4724
|
+
let engine;
|
|
4725
|
+
beforeAll(async () => {
|
|
4726
|
+
engine = await opts.makeEngine();
|
|
4727
|
+
});
|
|
4728
|
+
afterAll(async () => {
|
|
4729
|
+
if (engine) await engine.destroy();
|
|
4730
|
+
});
|
|
4731
|
+
test("flattens exactly the given refs and reports per-ref status", async () => {
|
|
4732
|
+
const doc = await openFixture$12(engine, opts);
|
|
4733
|
+
try {
|
|
4734
|
+
const layoutBefore = await doc.pages.list();
|
|
4735
|
+
const pon = layoutBefore.pages[0].pageObjectNumber;
|
|
4736
|
+
const page = doc.page(pon);
|
|
4737
|
+
if (!page.annotations.flatten) return;
|
|
4738
|
+
const a = (await page.annotations.create(square$1(20, 20))).created.ref;
|
|
4739
|
+
const b = (await page.annotations.create(square$1(80, 20))).created.ref;
|
|
4740
|
+
const c = (await page.annotations.create(square$1(140, 20))).created.ref;
|
|
4741
|
+
await page.annotations.update(c, {
|
|
4742
|
+
subtype: "square",
|
|
4743
|
+
flags: { hidden: true }
|
|
4744
|
+
});
|
|
4745
|
+
const before = await page.annotations.list();
|
|
4746
|
+
const events = [];
|
|
4747
|
+
const unsubscribe = doc.events.subscribe((event) => {
|
|
4748
|
+
if (event.type === "annotations.flattened") events.push(event);
|
|
4749
|
+
});
|
|
4750
|
+
const result = await page.annotations.flatten([a, c], "display");
|
|
4751
|
+
unsubscribe();
|
|
4752
|
+
expect(require_schemas.AnnotationFlattenResultSchema.safeParse(result).success).toBe(true);
|
|
4753
|
+
expect(result.pageObjectNumber).toBe(pon);
|
|
4754
|
+
expect(result.usage).toBe("display");
|
|
4755
|
+
expect(result.results.map((item) => item.status)).toEqual(["applied", "skipped"]);
|
|
4756
|
+
expect(result.meta === null).toBe(false);
|
|
4757
|
+
expect(events).toHaveLength(1);
|
|
4758
|
+
expect(await doc.pages.list()).toEqual(layoutBefore);
|
|
4759
|
+
const after = await page.annotations.list();
|
|
4760
|
+
expect(after.annotations.length).toBe(before.annotations.length - 1);
|
|
4761
|
+
expect(after.annotations.some((dto) => sameRef(dto.ref, b))).toBe(true);
|
|
4762
|
+
expect(after.annotations.some((dto) => sameRef(dto.ref, c))).toBe(true);
|
|
4763
|
+
expect(after.annotations.some((dto) => sameRef(dto.ref, a))).toBe(false);
|
|
4764
|
+
expect(after.pageState.revision.generation > before.pageState.revision.generation).toBe(true);
|
|
4765
|
+
} finally {
|
|
4766
|
+
await doc.close();
|
|
4767
|
+
}
|
|
4768
|
+
});
|
|
4769
|
+
test("a ref on another page rejects InvalidArg and mutates nothing", async () => {
|
|
4770
|
+
const doc = await openFixture$12(engine, opts);
|
|
4771
|
+
try {
|
|
4772
|
+
const layout = await doc.pages.list();
|
|
4773
|
+
if (layout.pages.length < 2) return;
|
|
4774
|
+
const page0 = doc.page(layout.pages[0].pageObjectNumber);
|
|
4775
|
+
const page1 = doc.page(layout.pages[1].pageObjectNumber);
|
|
4776
|
+
if (!page0.annotations.flatten) return;
|
|
4777
|
+
const own = (await page0.annotations.create(square$1(20, 100))).created.ref;
|
|
4778
|
+
const foreign = (await page1.annotations.create(square$1(20, 100))).created.ref;
|
|
4779
|
+
const countBefore = (await page0.annotations.list()).annotations.length;
|
|
4780
|
+
await expect(page0.annotations.flatten([own, foreign])).rejects.toMatchObject({ code: require_PageInsertBlankInput.EngineErrorCode.InvalidArg });
|
|
4781
|
+
expect((await page0.annotations.list()).annotations.length).toBe(countBefore);
|
|
4782
|
+
} finally {
|
|
4783
|
+
await doc.close();
|
|
4784
|
+
}
|
|
4785
|
+
});
|
|
4786
|
+
test("an empty ref list rejects InvalidArg; an unknown ref rejects", async () => {
|
|
4787
|
+
const doc = await openFixture$12(engine, opts);
|
|
4788
|
+
try {
|
|
4789
|
+
const pon = (await doc.pages.list()).pages[0].pageObjectNumber;
|
|
4790
|
+
const page = doc.page(pon);
|
|
4791
|
+
if (!page.annotations.flatten) return;
|
|
4792
|
+
await expect(page.annotations.flatten([])).rejects.toMatchObject({ code: require_PageInsertBlankInput.EngineErrorCode.InvalidArg });
|
|
4793
|
+
let unknown = null;
|
|
4794
|
+
try {
|
|
4795
|
+
await page.annotations.flatten([{
|
|
4796
|
+
kind: "objectNumber",
|
|
4797
|
+
pageObjectNumber: pon,
|
|
4798
|
+
annotObjectNumber: 987654321
|
|
4799
|
+
}]);
|
|
4800
|
+
} catch (error) {
|
|
4801
|
+
unknown = error;
|
|
4802
|
+
}
|
|
4803
|
+
expect(require_fold.EngineError.is(unknown, require_PageInsertBlankInput.EngineErrorCode.NotFound) || require_fold.EngineError.is(unknown, require_PageInsertBlankInput.EngineErrorCode.InvalidArg) || require_fold.EngineError.is(unknown, require_PageInsertBlankInput.EngineErrorCode.InvalidReference)).toBe(true);
|
|
4804
|
+
} finally {
|
|
4805
|
+
await doc.close();
|
|
4806
|
+
}
|
|
4807
|
+
});
|
|
4808
|
+
});
|
|
4809
|
+
}
|
|
4810
|
+
function sameRef(left, right) {
|
|
4811
|
+
if (left.kind === "objectNumber" && right.kind === "objectNumber") return left.annotObjectNumber === right.annotObjectNumber;
|
|
4812
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
4813
|
+
}
|
|
4814
|
+
async function openFixture$12(engine, opts) {
|
|
4815
|
+
if (opts.openKind === "bytes") return engine.open({
|
|
4816
|
+
kind: "bytes",
|
|
4817
|
+
id: opts.fixture.id,
|
|
4818
|
+
bytes: await opts.fixture.bytes()
|
|
4819
|
+
});
|
|
4820
|
+
return engine.open({
|
|
4821
|
+
kind: "id",
|
|
4822
|
+
id: opts.fixture.cloudId ?? opts.fixture.id
|
|
4823
|
+
});
|
|
4824
|
+
}
|
|
4825
|
+
//#endregion
|
|
4826
|
+
//#region src/conformance/runAnnotationAppearanceExportConformance.ts
|
|
4827
|
+
const PDF_MAGIC$1 = [
|
|
4828
|
+
37,
|
|
4829
|
+
80,
|
|
4830
|
+
68,
|
|
4831
|
+
70
|
|
4832
|
+
];
|
|
4833
|
+
const square = (left, bottom, size = 40) => ({
|
|
4834
|
+
subtype: "square",
|
|
4835
|
+
rect: {
|
|
4836
|
+
left,
|
|
4837
|
+
bottom,
|
|
4838
|
+
right: left + size,
|
|
4839
|
+
top: bottom + size
|
|
4840
|
+
},
|
|
4841
|
+
color: {
|
|
4842
|
+
r: 220,
|
|
4843
|
+
g: 20,
|
|
4844
|
+
b: 20
|
|
4845
|
+
},
|
|
4846
|
+
strokeWidth: 2
|
|
4847
|
+
});
|
|
4848
|
+
/**
|
|
4849
|
+
* `page(pon).annotations.exportAppearance(refs)`: the chosen annotations'
|
|
4850
|
+
* appearances as ONE single-page PDF sized to their union /Rect. Locks:
|
|
4851
|
+
* 1. bytes are a PDF; the source page is untouched (no mutation, no
|
|
4852
|
+
* revision bump);
|
|
4853
|
+
* 2. re-opened (local engines), the page is exactly the union size and the
|
|
4854
|
+
* requested count of annotations is gone — they are content now;
|
|
4855
|
+
* 3. a ref on another page rejects `InvalidArg`; an empty list rejects.
|
|
4856
|
+
* Skips cleanly when the engine does not implement the optional member.
|
|
4857
|
+
*/
|
|
4858
|
+
function runAnnotationAppearanceExportConformance(runner, opts) {
|
|
4859
|
+
const { describe, test, beforeAll, afterAll, expect } = runner;
|
|
4860
|
+
describe(`annotation appearance export conformance: ${opts.label}`, () => {
|
|
4861
|
+
let engine;
|
|
4862
|
+
beforeAll(async () => {
|
|
4863
|
+
engine = await opts.makeEngine();
|
|
4864
|
+
});
|
|
4865
|
+
afterAll(async () => {
|
|
4866
|
+
if (engine) await engine.destroy();
|
|
4867
|
+
});
|
|
4868
|
+
test("returns a single-page PDF sized to the union rect; source untouched", async () => {
|
|
4869
|
+
const doc = await openFixture$11(engine, opts);
|
|
4870
|
+
let exported = null;
|
|
4871
|
+
try {
|
|
4872
|
+
const pon = (await doc.pages.list()).pages[0].pageObjectNumber;
|
|
4873
|
+
const page = doc.page(pon);
|
|
4874
|
+
if (!page.annotations.exportAppearance) return;
|
|
4875
|
+
const a = (await page.annotations.create(square(20, 20))).created.ref;
|
|
4876
|
+
const b = (await page.annotations.create(square(100, 60, 30))).created.ref;
|
|
4877
|
+
const before = await page.annotations.list();
|
|
4878
|
+
const bytes = await page.annotations.exportAppearance([a, b]);
|
|
4879
|
+
expect(bytes.length > 4).toBe(true);
|
|
4880
|
+
expect([...bytes.subarray(0, 4)]).toEqual(PDF_MAGIC$1);
|
|
4881
|
+
const after = await page.annotations.list();
|
|
4882
|
+
expect(after.annotations.length).toBe(before.annotations.length);
|
|
4883
|
+
expect(after.pageState.revision.generation).toBe(before.pageState.revision.generation);
|
|
4884
|
+
if (opts.openKind !== "bytes") return;
|
|
4885
|
+
exported = await engine.open({
|
|
4886
|
+
kind: "bytes",
|
|
4887
|
+
id: `${opts.fixture.id}-appearance`,
|
|
4888
|
+
bytes
|
|
4889
|
+
});
|
|
4890
|
+
const layout = await exported.pages.list();
|
|
4891
|
+
expect(layout.pageCount).toBe(1);
|
|
4892
|
+
expect(Math.round(layout.pages[0].size.width)).toBe(110);
|
|
4893
|
+
expect(Math.round(layout.pages[0].size.height)).toBe(70);
|
|
4894
|
+
const exportedPage = exported.page(layout.pages[0].pageObjectNumber);
|
|
4895
|
+
expect((await exportedPage.annotations.list()).annotations).toHaveLength(0);
|
|
4896
|
+
} finally {
|
|
4897
|
+
if (exported) await exported.close();
|
|
4898
|
+
await doc.close();
|
|
4899
|
+
}
|
|
4900
|
+
});
|
|
4901
|
+
test("a ref on another page and an empty list reject InvalidArg", async () => {
|
|
4902
|
+
const doc = await openFixture$11(engine, opts);
|
|
4903
|
+
try {
|
|
4904
|
+
const layout = await doc.pages.list();
|
|
4905
|
+
const page0 = doc.page(layout.pages[0].pageObjectNumber);
|
|
4906
|
+
if (!page0.annotations.exportAppearance) return;
|
|
4907
|
+
await expect(page0.annotations.exportAppearance([])).rejects.toMatchObject({ code: require_PageInsertBlankInput.EngineErrorCode.InvalidArg });
|
|
4908
|
+
if (layout.pages.length < 2) return;
|
|
4909
|
+
const page1 = doc.page(layout.pages[1].pageObjectNumber);
|
|
4910
|
+
const own = (await page0.annotations.create(square(20, 100))).created.ref;
|
|
4911
|
+
const foreign = (await page1.annotations.create(square(20, 100))).created.ref;
|
|
4912
|
+
await expect(page0.annotations.exportAppearance([own, foreign])).rejects.toMatchObject({ code: require_PageInsertBlankInput.EngineErrorCode.InvalidArg });
|
|
4913
|
+
} finally {
|
|
4914
|
+
await doc.close();
|
|
4915
|
+
}
|
|
4916
|
+
});
|
|
4917
|
+
});
|
|
4918
|
+
}
|
|
4919
|
+
async function openFixture$11(engine, opts) {
|
|
4920
|
+
if (opts.openKind === "bytes") return engine.open({
|
|
4921
|
+
kind: "bytes",
|
|
4922
|
+
id: opts.fixture.id,
|
|
4923
|
+
bytes: await opts.fixture.bytes()
|
|
4924
|
+
});
|
|
4925
|
+
return engine.open({
|
|
4926
|
+
kind: "id",
|
|
4927
|
+
id: opts.fixture.cloudId ?? opts.fixture.id
|
|
4928
|
+
});
|
|
4929
|
+
}
|
|
4930
|
+
//#endregion
|
|
4196
4931
|
//#region src/conformance/runPageFlattenConformance.ts
|
|
4197
4932
|
/** Transport-neutral coverage for the content + annotation flatten rail. */
|
|
4198
4933
|
function runPageFlattenConformance(runner, opts) {
|
|
@@ -6221,17 +6956,804 @@ function diffAnnotation(i, a, b, errs) {
|
|
|
6221
6956
|
if (a.nm !== b.nm) errs.push(`annotations[${i}].nm mismatch: ${a.nm} vs ${b.nm}`);
|
|
6222
6957
|
}
|
|
6223
6958
|
//#endregion
|
|
6959
|
+
//#region src/conformance/runSignatureConformance.ts
|
|
6960
|
+
/**
|
|
6961
|
+
* Digital signatures, read side. The service is OPTIONAL on the contract
|
|
6962
|
+
* (`downloadLayer?` pattern) — the suite runs only where the engine
|
|
6963
|
+
* provides it.
|
|
6964
|
+
*
|
|
6965
|
+
* Invariants:
|
|
6966
|
+
* 1. Byte facts describe the LOADED bytes: a revision is a byte prefix,
|
|
6967
|
+
* a whole-revision signature seals exactly one, the digest over its
|
|
6968
|
+
* `/ByteRange` is what the file's bytes hash to, and an unsaved edit
|
|
6969
|
+
* changes none of it.
|
|
6970
|
+
* 2. Protection is derived from the signatures in the file and applied
|
|
6971
|
+
* as document-derived capabilities: a certification with permission 2
|
|
6972
|
+
* takes page assembly and annotation writes away, a FieldMDP freezes
|
|
6973
|
+
* the fields it names, a rewrite save is refused. Under
|
|
6974
|
+
* `signedDocumentPolicy: 'permit'` none of that applies and the
|
|
6975
|
+
* analysis is unchanged.
|
|
6976
|
+
* 3. The same answers come from a plain session and from a layer
|
|
6977
|
+
* session over the same bytes.
|
|
6978
|
+
*/
|
|
6979
|
+
function runSignatureConformance(runner, opts) {
|
|
6980
|
+
const { describe, test, beforeAll, afterAll, expect } = runner;
|
|
6981
|
+
describe(`signatures conformance: ${opts.label}`, () => {
|
|
6982
|
+
let engine;
|
|
6983
|
+
let permitEngine = null;
|
|
6984
|
+
let supported = false;
|
|
6985
|
+
beforeAll(async () => {
|
|
6986
|
+
engine = await opts.makeEngine();
|
|
6987
|
+
const probe = await open(engine, opts, opts.fixtures.unsignedForm);
|
|
6988
|
+
supported = probe.signatures !== void 0;
|
|
6989
|
+
await probe.close();
|
|
6990
|
+
if (supported && opts.makePermitEngine) permitEngine = await opts.makePermitEngine();
|
|
6991
|
+
});
|
|
6992
|
+
afterAll(async () => {
|
|
6993
|
+
if (permitEngine) await permitEngine.destroy();
|
|
6994
|
+
if (engine) await engine.destroy();
|
|
6995
|
+
});
|
|
6996
|
+
test("an unsigned form has no signatures and no protection; version() hashes the loaded bytes", async () => {
|
|
6997
|
+
if (!supported) return;
|
|
6998
|
+
const bytes = await opts.fixtures.unsignedForm.bytes();
|
|
6999
|
+
const doc = await open(engine, opts, opts.fixtures.unsignedForm);
|
|
7000
|
+
try {
|
|
7001
|
+
const snapshot = await doc.signatures.list();
|
|
7002
|
+
expect(snapshot.chainValid).toBe(true);
|
|
7003
|
+
expect(snapshot.signatures).toEqual([]);
|
|
7004
|
+
expect(snapshot.revisions.length >= 1).toBe(true);
|
|
7005
|
+
expect(snapshot.protection.judged).toBeNull();
|
|
7006
|
+
expect(snapshot.protection.enforced).toBeNull();
|
|
7007
|
+
expect(snapshot.protection.certification).toBeNull();
|
|
7008
|
+
expect(snapshot.protection.fieldLocks).toEqual([]);
|
|
7009
|
+
expect(doc.security.allows("doc.pages.assemble")).toBe(true);
|
|
7010
|
+
if (doc.version) {
|
|
7011
|
+
const version = await doc.version();
|
|
7012
|
+
expect(version.byteLength).toBe(bytes.byteLength);
|
|
7013
|
+
expect(version.sha256).toBe(await sha256Hex(bytes));
|
|
7014
|
+
}
|
|
7015
|
+
let caught;
|
|
7016
|
+
try {
|
|
7017
|
+
await doc.signatures.contents({
|
|
7018
|
+
kind: "fqn",
|
|
7019
|
+
name: "no-such-field"
|
|
7020
|
+
});
|
|
7021
|
+
} catch (err) {
|
|
7022
|
+
caught = err;
|
|
7023
|
+
}
|
|
7024
|
+
expect(require_fold.EngineError.is(caught, require_PageInsertBlankInput.EngineErrorCode.NotFound)).toBe(true);
|
|
7025
|
+
} finally {
|
|
7026
|
+
await doc.close();
|
|
7027
|
+
}
|
|
7028
|
+
});
|
|
7029
|
+
test("two approval signatures: revisions, coverage, digests, contents, revision bytes", async () => {
|
|
7030
|
+
if (!supported) return;
|
|
7031
|
+
const fixture = opts.fixtures.twoApprovals;
|
|
7032
|
+
const bytes = await fixture.bytes();
|
|
7033
|
+
const doc = await open(engine, opts, fixture);
|
|
7034
|
+
try {
|
|
7035
|
+
const snapshot = await doc.signatures.list();
|
|
7036
|
+
expect(snapshot.chainValid).toBe(true);
|
|
7037
|
+
expect(snapshot.revisions).toHaveLength(3);
|
|
7038
|
+
expect(snapshot.signatures).toHaveLength(2);
|
|
7039
|
+
expect(snapshot.revisions[2].end).toBe(bytes.byteLength);
|
|
7040
|
+
for (const [i, sig] of snapshot.signatures.entries()) {
|
|
7041
|
+
expect(sig.index).toBe(i);
|
|
7042
|
+
expect(sig.signed).toBe(true);
|
|
7043
|
+
expect(sig.kind).toBe("signature");
|
|
7044
|
+
expect(sig.field.kind).toBe("objectNumber");
|
|
7045
|
+
expect(sig.fieldName).toBe(fixture.fieldNames[i]);
|
|
7046
|
+
expect(sig.coverage).toBe("whole-revision");
|
|
7047
|
+
expect(sig.revisionIndex).toBe(i + 1);
|
|
7048
|
+
expect(snapshot.revisions[i + 1].signatureIndex).toBe(i);
|
|
7049
|
+
expect(sig.subFilter).toBe("ETSI.CAdES.detached");
|
|
7050
|
+
expect(typeof sig.signer.claimedTime).toBe("string");
|
|
7051
|
+
expect(sig.docMdp).toBeNull();
|
|
7052
|
+
expect(sig.catalogCertification).toBe(false);
|
|
7053
|
+
expect(sig.contentsSize > 0).toBe(true);
|
|
7054
|
+
expect(sig.byteRange[0]).toBe(0);
|
|
7055
|
+
expect(sig.byteRange[2] + sig.byteRange[3]).toBe(snapshot.revisions[i + 1].end);
|
|
7056
|
+
}
|
|
7057
|
+
expect(snapshot.protection.judged).toBe("annotate");
|
|
7058
|
+
expect(snapshot.protection.enforced).toBeNull();
|
|
7059
|
+
expect(snapshot.protection.certification).toBeNull();
|
|
7060
|
+
expect(snapshot.protection.fieldLocks).toEqual([]);
|
|
7061
|
+
const first = snapshot.signatures[0];
|
|
7062
|
+
const contents = await doc.signatures.contents(first.field);
|
|
7063
|
+
expect(contents.byteLength).toBe(first.contentsSize);
|
|
7064
|
+
expect(contents[0]).toBe(48);
|
|
7065
|
+
const [a, b, c, d] = first.byteRange;
|
|
7066
|
+
const sealed = await doc.signatures.revisionBytes(1);
|
|
7067
|
+
expect(sealed.byteLength).toBe(snapshot.revisions[1].end);
|
|
7068
|
+
expect(bytesEqual(sealed, bytes.subarray(0, sealed.byteLength))).toBe(true);
|
|
7069
|
+
const signedBytes = concat(sealed.subarray(a, a + b), sealed.subarray(c, c + d));
|
|
7070
|
+
const digest = await doc.signatures.digest(first.field, "sha256");
|
|
7071
|
+
expect(digest.byteLength).toBe(32);
|
|
7072
|
+
expect(toHex(digest)).toBe(await sha256Hex(signedBytes));
|
|
7073
|
+
expect((await doc.signatures.digest(first.field, "sha512")).byteLength).toBe(64);
|
|
7074
|
+
const byName = await doc.signatures.digest({
|
|
7075
|
+
kind: "fqn",
|
|
7076
|
+
name: first.fieldName
|
|
7077
|
+
}, "sha256");
|
|
7078
|
+
expect(toHex(byName)).toBe(toHex(digest));
|
|
7079
|
+
const text = (await doc.forms.list()).fields.find((f) => f.name === fixture.textField);
|
|
7080
|
+
expect(text).toBeTruthy();
|
|
7081
|
+
await doc.forms.setValue({
|
|
7082
|
+
kind: "objectNumber",
|
|
7083
|
+
fieldObjectNumber: text.fieldObjectNumber
|
|
7084
|
+
}, {
|
|
7085
|
+
type: "text",
|
|
7086
|
+
value: "unsaved"
|
|
7087
|
+
});
|
|
7088
|
+
const again = await doc.signatures.list();
|
|
7089
|
+
expect(again.revisions).toEqual(snapshot.revisions);
|
|
7090
|
+
expect(again.signatures.map((s) => [
|
|
7091
|
+
s.coverage,
|
|
7092
|
+
s.revisionIndex,
|
|
7093
|
+
s.byteRange
|
|
7094
|
+
])).toEqual(snapshot.signatures.map((s) => [
|
|
7095
|
+
s.coverage,
|
|
7096
|
+
s.revisionIndex,
|
|
7097
|
+
s.byteRange
|
|
7098
|
+
]));
|
|
7099
|
+
expect(toHex(await doc.signatures.digest(first.field, "sha256"))).toBe(toHex(digest));
|
|
7100
|
+
if (doc.version) expect((await doc.version()).sha256).toBe(await sha256Hex(bytes));
|
|
7101
|
+
} finally {
|
|
7102
|
+
await doc.close();
|
|
7103
|
+
}
|
|
7104
|
+
});
|
|
7105
|
+
test("a certification takes structural capabilities away; permit lifts it", async () => {
|
|
7106
|
+
if (!supported) return;
|
|
7107
|
+
const fixture = opts.fixtures.certified;
|
|
7108
|
+
const doc = await open(engine, opts, fixture);
|
|
7109
|
+
try {
|
|
7110
|
+
const snapshot = await doc.signatures.list();
|
|
7111
|
+
const signed = snapshot.signatures.filter((s) => s.signed);
|
|
7112
|
+
expect(signed).toHaveLength(1);
|
|
7113
|
+
const sig = signed[0];
|
|
7114
|
+
expect(sig.fieldName).toBe(fixture.fieldName);
|
|
7115
|
+
expect(sig.coverage).toBe("whole-revision");
|
|
7116
|
+
expect(sig.docMdp).toBe(2);
|
|
7117
|
+
expect(sig.catalogCertification).toBe(true);
|
|
7118
|
+
expect(snapshot.protection.judged).toBe("fill");
|
|
7119
|
+
expect(snapshot.protection.enforced).toBe("fill");
|
|
7120
|
+
expect(snapshot.protection.certification).toEqual({
|
|
7121
|
+
signatureIndex: sig.index,
|
|
7122
|
+
permission: 2
|
|
7123
|
+
});
|
|
7124
|
+
expect(doc.security.allows("doc.forms.read")).toBe(true);
|
|
7125
|
+
expect(doc.security.allows("doc.forms.fill")).toBe(true);
|
|
7126
|
+
expect(doc.security.allows("doc.annotate.modify")).toBe(false);
|
|
7127
|
+
expect(doc.security.allows("doc.pages.assemble")).toBe(false);
|
|
7128
|
+
expect(doc.security.allows("doc.redact")).toBe(false);
|
|
7129
|
+
const list = await doc.pages.list();
|
|
7130
|
+
let caught;
|
|
7131
|
+
try {
|
|
7132
|
+
await doc.pages.delete([list.pages[0].pageObjectNumber]);
|
|
7133
|
+
} catch (err) {
|
|
7134
|
+
caught = err;
|
|
7135
|
+
}
|
|
7136
|
+
expect(require_fold.EngineError.is(caught, require_PageInsertBlankInput.EngineErrorCode.ProtectedDocument)).toBe(true);
|
|
7137
|
+
caught = void 0;
|
|
7138
|
+
try {
|
|
7139
|
+
await doc.download({ mode: "rewrite" });
|
|
7140
|
+
} catch (err) {
|
|
7141
|
+
caught = err;
|
|
7142
|
+
}
|
|
7143
|
+
expect(require_fold.EngineError.is(caught, require_PageInsertBlankInput.EngineErrorCode.ProtectedDocument)).toBe(true);
|
|
7144
|
+
const saved = await doc.download();
|
|
7145
|
+
expect(saved.byteLength >= snapshot.revisions[1].end).toBe(true);
|
|
7146
|
+
} finally {
|
|
7147
|
+
await doc.close();
|
|
7148
|
+
}
|
|
7149
|
+
if (!permitEngine) return;
|
|
7150
|
+
const permitted = await open(permitEngine, opts, fixture);
|
|
7151
|
+
try {
|
|
7152
|
+
const snapshot = await permitted.signatures.list();
|
|
7153
|
+
expect(snapshot.protection.judged).toBe("fill");
|
|
7154
|
+
expect(snapshot.protection.enforced).toBe("fill");
|
|
7155
|
+
expect(permitted.security.allows("doc.pages.assemble")).toBe(true);
|
|
7156
|
+
const rewritten = await permitted.download({ mode: "rewrite" });
|
|
7157
|
+
expect(rewritten.byteLength > 0).toBe(true);
|
|
7158
|
+
} finally {
|
|
7159
|
+
await permitted.close();
|
|
7160
|
+
}
|
|
7161
|
+
});
|
|
7162
|
+
test("a FieldMDP freezes the fields it names; permit lifts it", async () => {
|
|
7163
|
+
if (!supported) return;
|
|
7164
|
+
const fixture = opts.fixtures.fieldMdp;
|
|
7165
|
+
const doc = await open(engine, opts, fixture);
|
|
7166
|
+
const lockedRef = {
|
|
7167
|
+
kind: "fqn",
|
|
7168
|
+
name: fixture.lockedField
|
|
7169
|
+
};
|
|
7170
|
+
try {
|
|
7171
|
+
const snapshot = await doc.signatures.list();
|
|
7172
|
+
const sig = snapshot.signatures[0];
|
|
7173
|
+
expect(sig.fieldName).toBe(fixture.fieldName);
|
|
7174
|
+
expect(sig.fieldMdp).toEqual({
|
|
7175
|
+
action: "include",
|
|
7176
|
+
fields: [fixture.lockedField]
|
|
7177
|
+
});
|
|
7178
|
+
expect(snapshot.protection.judged).toBe("annotate");
|
|
7179
|
+
expect(snapshot.protection.enforced).toBeNull();
|
|
7180
|
+
expect(snapshot.protection.fieldLocks[0]).toEqual({
|
|
7181
|
+
signatureIndex: 0,
|
|
7182
|
+
source: "fieldmdp",
|
|
7183
|
+
spec: {
|
|
7184
|
+
action: "include",
|
|
7185
|
+
fields: [fixture.lockedField]
|
|
7186
|
+
}
|
|
7187
|
+
});
|
|
7188
|
+
for (const lock of snapshot.protection.fieldLocks) {
|
|
7189
|
+
expect(lock.signatureIndex).toBe(0);
|
|
7190
|
+
expect(lock.spec).toEqual({
|
|
7191
|
+
action: "include",
|
|
7192
|
+
fields: [fixture.lockedField]
|
|
7193
|
+
});
|
|
7194
|
+
}
|
|
7195
|
+
expect(doc.security.allows("doc.forms.fill")).toBe(true);
|
|
7196
|
+
let caught;
|
|
7197
|
+
try {
|
|
7198
|
+
await doc.forms.setValue(lockedRef, {
|
|
7199
|
+
type: "text",
|
|
7200
|
+
value: "changed"
|
|
7201
|
+
});
|
|
7202
|
+
} catch (err) {
|
|
7203
|
+
caught = err;
|
|
7204
|
+
}
|
|
7205
|
+
expect(require_fold.EngineError.is(caught, require_PageInsertBlankInput.EngineErrorCode.ProtectedDocument)).toBe(true);
|
|
7206
|
+
const field = await doc.forms.get(lockedRef);
|
|
7207
|
+
expect(field.family).toBe("text");
|
|
7208
|
+
expect(field.value === "changed").toBe(false);
|
|
7209
|
+
} finally {
|
|
7210
|
+
await doc.close();
|
|
7211
|
+
}
|
|
7212
|
+
if (!permitEngine) return;
|
|
7213
|
+
const permitted = await open(permitEngine, opts, fixture);
|
|
7214
|
+
try {
|
|
7215
|
+
const result = await permitted.forms.setValue(lockedRef, {
|
|
7216
|
+
type: "text",
|
|
7217
|
+
value: "changed"
|
|
7218
|
+
});
|
|
7219
|
+
expect(result.field.name).toBe(fixture.lockedField);
|
|
7220
|
+
} finally {
|
|
7221
|
+
await permitted.close();
|
|
7222
|
+
}
|
|
7223
|
+
});
|
|
7224
|
+
test("well-formed ranges that seal no revision are partial, with no revision index", async () => {
|
|
7225
|
+
if (!supported) return;
|
|
7226
|
+
const doc = await open(engine, opts, opts.fixtures.partialChain);
|
|
7227
|
+
try {
|
|
7228
|
+
const snapshot = await doc.signatures.list();
|
|
7229
|
+
expect(snapshot.chainValid).toBe(true);
|
|
7230
|
+
expect(snapshot.revisions).toHaveLength(3);
|
|
7231
|
+
expect(snapshot.signatures).toHaveLength(2);
|
|
7232
|
+
for (const sig of snapshot.signatures) {
|
|
7233
|
+
expect(sig.signed).toBe(true);
|
|
7234
|
+
expect(sig.coverage).toBe("partial");
|
|
7235
|
+
expect(sig.revisionIndex).toBeNull();
|
|
7236
|
+
expect(sig.contentsSize).toBe(13);
|
|
7237
|
+
expect((await doc.signatures.contents(sig.field)).byteLength).toBe(13);
|
|
7238
|
+
expect((await doc.signatures.digest(sig.field, "sha256")).byteLength).toBe(32);
|
|
7239
|
+
}
|
|
7240
|
+
expect(snapshot.revisions.every((r) => r.signatureIndex === null)).toBe(true);
|
|
7241
|
+
expect(snapshot.protection.judged).toBe("annotate");
|
|
7242
|
+
expect(snapshot.protection.enforced).toBeNull();
|
|
7243
|
+
let caught;
|
|
7244
|
+
try {
|
|
7245
|
+
await doc.signatures.revisionBytes(3);
|
|
7246
|
+
} catch (err) {
|
|
7247
|
+
caught = err;
|
|
7248
|
+
}
|
|
7249
|
+
expect(require_fold.EngineError.is(caught, require_PageInsertBlankInput.EngineErrorCode.NotFound)).toBe(true);
|
|
7250
|
+
} finally {
|
|
7251
|
+
await doc.close();
|
|
7252
|
+
}
|
|
7253
|
+
});
|
|
7254
|
+
if (opts.openKind !== "id") {
|
|
7255
|
+
runSigningTests(runner, opts, () => engine, () => supported);
|
|
7256
|
+
runAnalysisTests(runner, opts, () => engine, () => permitEngine, () => supported);
|
|
7257
|
+
}
|
|
7258
|
+
});
|
|
7259
|
+
}
|
|
7260
|
+
function runAnalysisTests(runner, opts, engineOf, permitEngineOf, supported) {
|
|
7261
|
+
const { test, expect } = runner;
|
|
7262
|
+
test("analyze: a form fill and a second signature after an approval are permitted", async () => {
|
|
7263
|
+
if (!supported()) return;
|
|
7264
|
+
const doc = await open(engineOf(), opts, opts.fixtures.twoApprovals);
|
|
7265
|
+
try {
|
|
7266
|
+
const analysis = await doc.signatures.analyze({ since: { signatureIndex: 0 } });
|
|
7267
|
+
expect(analysis.mode).toBe("authoritative");
|
|
7268
|
+
expect(analysis.basis.source).toBe("persisted");
|
|
7269
|
+
expect(analysis.since).toEqual({
|
|
7270
|
+
revisionIndex: 1,
|
|
7271
|
+
signatureIndex: 0
|
|
7272
|
+
});
|
|
7273
|
+
expect(analysis.until.revisionIndex).toBe(2);
|
|
7274
|
+
expect(analysis.steps).toHaveLength(1);
|
|
7275
|
+
const [step] = analysis.steps;
|
|
7276
|
+
expect(step.levelInForce).toBe("annotate");
|
|
7277
|
+
expect(step.verdict).toBe("permitted");
|
|
7278
|
+
expect(step.changes.length > 0).toBe(true);
|
|
7279
|
+
const rules = new Set(step.findings.filter((f) => f.verdict === "permitted").map((f) => f.rule));
|
|
7280
|
+
expect(rules.has("form-fill")).toBe(true);
|
|
7281
|
+
expect(rules.has("signature-added")).toBe(true);
|
|
7282
|
+
expect(step.findings.filter((f) => f.verdict === "forbidden")).toEqual([]);
|
|
7283
|
+
expect(analysis.verdict).toBe("permitted");
|
|
7284
|
+
const latest = await doc.signatures.analyze({ since: { signatureIndex: 1 } });
|
|
7285
|
+
expect(latest.steps).toHaveLength(0);
|
|
7286
|
+
expect(latest.verdict).toBe("unchanged");
|
|
7287
|
+
const historic = await doc.signatures.analyze({
|
|
7288
|
+
since: { revisionIndex: 0 },
|
|
7289
|
+
until: { revisionIndex: 1 }
|
|
7290
|
+
});
|
|
7291
|
+
expect(historic.steps).toHaveLength(1);
|
|
7292
|
+
expect(historic.verdict).toBe("permitted");
|
|
7293
|
+
} finally {
|
|
7294
|
+
await doc.close();
|
|
7295
|
+
}
|
|
7296
|
+
});
|
|
7297
|
+
test("analyze: the working copy is one more revision; a partial signature cannot anchor", async () => {
|
|
7298
|
+
if (!supported()) return;
|
|
7299
|
+
const fx = opts.fixtures.unsignedSigField;
|
|
7300
|
+
const doc = await open(engineOf(), opts, fx);
|
|
7301
|
+
try {
|
|
7302
|
+
const prepared = await doc.signatures.prepare({
|
|
7303
|
+
field: {
|
|
7304
|
+
kind: "fqn",
|
|
7305
|
+
name: fx.fieldName
|
|
7306
|
+
},
|
|
7307
|
+
certify: { permission: 2 }
|
|
7308
|
+
});
|
|
7309
|
+
await doc.signatures.complete({
|
|
7310
|
+
signingId: prepared.signingId,
|
|
7311
|
+
cms: FAKE_CMS,
|
|
7312
|
+
expectedVersion: prepared.expectedVersion
|
|
7313
|
+
});
|
|
7314
|
+
const clean = await doc.signatures.analyze({
|
|
7315
|
+
since: { signatureIndex: 0 },
|
|
7316
|
+
until: "working-copy"
|
|
7317
|
+
});
|
|
7318
|
+
expect(clean.steps).toHaveLength(0);
|
|
7319
|
+
expect(clean.verdict).toBe("unchanged");
|
|
7320
|
+
await doc.forms.setValue({
|
|
7321
|
+
kind: "fqn",
|
|
7322
|
+
name: fx.textField
|
|
7323
|
+
}, {
|
|
7324
|
+
type: "text",
|
|
7325
|
+
value: "draft"
|
|
7326
|
+
});
|
|
7327
|
+
const persisted = await doc.signatures.analyze({ since: { signatureIndex: 0 } });
|
|
7328
|
+
expect(persisted.steps).toHaveLength(0);
|
|
7329
|
+
const working = await doc.signatures.analyze({
|
|
7330
|
+
since: { signatureIndex: 0 },
|
|
7331
|
+
until: "working-copy"
|
|
7332
|
+
});
|
|
7333
|
+
expect(working.basis.source).toBe("working-copy");
|
|
7334
|
+
expect(working.steps).toHaveLength(1);
|
|
7335
|
+
expect(working.steps[0].levelInForce).toBe("fill");
|
|
7336
|
+
expect(working.verdict).toBe("permitted");
|
|
7337
|
+
expect(working.steps[0].findings.some((f) => f.rule === "form-fill" && f.verdict === "permitted")).toBe(true);
|
|
7338
|
+
} finally {
|
|
7339
|
+
await doc.close();
|
|
7340
|
+
}
|
|
7341
|
+
const partial = await open(engineOf(), opts, opts.fixtures.partialChain);
|
|
7342
|
+
try {
|
|
7343
|
+
expect(await caughtCode(() => partial.signatures.analyze({ since: { signatureIndex: 0 } }))).toBe(require_PageInsertBlankInput.EngineErrorCode.InvalidArg);
|
|
7344
|
+
const byRevision = await partial.signatures.analyze({ since: { revisionIndex: 0 } });
|
|
7345
|
+
expect(byRevision.later.revisionCount).toBe(2);
|
|
7346
|
+
expect(byRevision.steps).toHaveLength(0);
|
|
7347
|
+
const inFull = await partial.signatures.analyze({
|
|
7348
|
+
since: { revisionIndex: 0 },
|
|
7349
|
+
detail: "full"
|
|
7350
|
+
});
|
|
7351
|
+
expect(inFull.steps).toHaveLength(2);
|
|
7352
|
+
expect(inFull.current.verdict).toBe(byRevision.current.verdict);
|
|
7353
|
+
} finally {
|
|
7354
|
+
await partial.close();
|
|
7355
|
+
}
|
|
7356
|
+
});
|
|
7357
|
+
test("analyze: a page edit under a certification and a locked field edit are forbidden", async () => {
|
|
7358
|
+
if (!supported()) return;
|
|
7359
|
+
const permit = permitEngineOf();
|
|
7360
|
+
if (!permit) return;
|
|
7361
|
+
const certified = await open(permit, opts, opts.fixtures.certified);
|
|
7362
|
+
let reopened = null;
|
|
7363
|
+
try {
|
|
7364
|
+
const list = await certified.pages.list();
|
|
7365
|
+
await certified.pages.rotate([list.pages[0].pageObjectNumber], 90);
|
|
7366
|
+
const working = await certified.signatures.analyze({
|
|
7367
|
+
since: { signatureIndex: 0 },
|
|
7368
|
+
until: "working-copy"
|
|
7369
|
+
});
|
|
7370
|
+
expect(working.verdict).toBe("forbidden");
|
|
7371
|
+
const bytes = await certified.download();
|
|
7372
|
+
reopened = await reopen(permit, opts, `${opts.fixtures.certified.id}-tampered`, bytes);
|
|
7373
|
+
const analysis = await reopened.signatures.analyze({ since: { signatureIndex: 0 } });
|
|
7374
|
+
expect(analysis.verdict).toBe("forbidden");
|
|
7375
|
+
expect(analysis.steps[0].findings.some((f) => f.verdict === "forbidden")).toBe(true);
|
|
7376
|
+
} finally {
|
|
7377
|
+
if (reopened) await reopened.close();
|
|
7378
|
+
await certified.close();
|
|
7379
|
+
}
|
|
7380
|
+
const locked = await open(permit, opts, opts.fixtures.fieldMdp);
|
|
7381
|
+
try {
|
|
7382
|
+
await locked.forms.setValue({
|
|
7383
|
+
kind: "fqn",
|
|
7384
|
+
name: opts.fixtures.fieldMdp.lockedField
|
|
7385
|
+
}, {
|
|
7386
|
+
type: "text",
|
|
7387
|
+
value: "tampered"
|
|
7388
|
+
});
|
|
7389
|
+
const working = await locked.signatures.analyze({
|
|
7390
|
+
since: { signatureIndex: 0 },
|
|
7391
|
+
until: "working-copy"
|
|
7392
|
+
});
|
|
7393
|
+
expect(working.verdict).toBe("forbidden");
|
|
7394
|
+
expect(working.steps[0].findings.some((f) => f.rule === "field-lock")).toBe(true);
|
|
7395
|
+
} finally {
|
|
7396
|
+
await locked.close();
|
|
7397
|
+
}
|
|
7398
|
+
});
|
|
7399
|
+
}
|
|
7400
|
+
/** A minimal DER SEQUENCE standing in for a CMS: the engine's job ends at the bytes. */
|
|
7401
|
+
const FAKE_CMS = new Uint8Array([
|
|
7402
|
+
48,
|
|
7403
|
+
6,
|
|
7404
|
+
2,
|
|
7405
|
+
1,
|
|
7406
|
+
1,
|
|
7407
|
+
2,
|
|
7408
|
+
1,
|
|
7409
|
+
2
|
|
7410
|
+
]);
|
|
7411
|
+
async function caughtCode(run) {
|
|
7412
|
+
try {
|
|
7413
|
+
await run();
|
|
7414
|
+
return null;
|
|
7415
|
+
} catch (err) {
|
|
7416
|
+
return err instanceof require_fold.EngineError ? err.code : "not-an-engine-error";
|
|
7417
|
+
}
|
|
7418
|
+
}
|
|
7419
|
+
function runSigningTests(runner, opts, engineOf, supported) {
|
|
7420
|
+
const { test, expect } = runner;
|
|
7421
|
+
const fx = () => opts.fixtures.unsignedSigField;
|
|
7422
|
+
const sigRef = () => ({
|
|
7423
|
+
kind: "fqn",
|
|
7424
|
+
name: fx().fieldName
|
|
7425
|
+
});
|
|
7426
|
+
const textRef = () => ({
|
|
7427
|
+
kind: "fqn",
|
|
7428
|
+
name: fx().textField
|
|
7429
|
+
});
|
|
7430
|
+
test("sign: prepare seals a candidate, complete installs it, download returns it verbatim", async () => {
|
|
7431
|
+
if (!supported()) return;
|
|
7432
|
+
const engine = engineOf();
|
|
7433
|
+
const doc = await open(engine, opts, fx());
|
|
7434
|
+
const events = [];
|
|
7435
|
+
const unsubscribe = doc.events.subscribe((event) => events.push(event.type));
|
|
7436
|
+
let reopened = null;
|
|
7437
|
+
try {
|
|
7438
|
+
const v0 = await doc.version();
|
|
7439
|
+
const before = await doc.signatures.list();
|
|
7440
|
+
expect(before.revisions).toHaveLength(1);
|
|
7441
|
+
expect(before.signatures).toHaveLength(1);
|
|
7442
|
+
expect(before.signatures[0].signed).toBe(false);
|
|
7443
|
+
expect(before.protection.judged).toBeNull();
|
|
7444
|
+
const prepared = await doc.signatures.prepare({
|
|
7445
|
+
field: sigRef(),
|
|
7446
|
+
subFilter: "ETSI.CAdES.detached",
|
|
7447
|
+
digest: "sha256",
|
|
7448
|
+
contentsSize: 1024,
|
|
7449
|
+
attribution: { reason: "conformance" }
|
|
7450
|
+
});
|
|
7451
|
+
expect(prepared.digest.byteLength).toBe(32);
|
|
7452
|
+
expect(prepared.algorithm).toBe("sha256");
|
|
7453
|
+
expect(prepared.subFilter).toBe("ETSI.CAdES.detached");
|
|
7454
|
+
expect(prepared.contentsSize).toBe(1024);
|
|
7455
|
+
expect(prepared.byteRange[0]).toBe(0);
|
|
7456
|
+
expect(prepared.byteRange[2] > prepared.byteRange[1]).toBe(true);
|
|
7457
|
+
expect(prepared.expectedVersion.baseSha256).toBe(v0.sha256);
|
|
7458
|
+
expect(await caughtCode(() => doc.signatures.prepare({ field: sigRef() }))).toBe(require_PageInsertBlankInput.EngineErrorCode.SigningPending);
|
|
7459
|
+
expect(await caughtCode(() => doc.forms.setValue(textRef(), {
|
|
7460
|
+
type: "text",
|
|
7461
|
+
value: "x"
|
|
7462
|
+
}))).toBe(require_PageInsertBlankInput.EngineErrorCode.SigningPending);
|
|
7463
|
+
const during = await doc.signatures.list();
|
|
7464
|
+
expect(during.signatures[0].signed).toBe(false);
|
|
7465
|
+
expect((await doc.version()).sha256).toBe(v0.sha256);
|
|
7466
|
+
expect(await caughtCode(() => doc.signatures.complete({
|
|
7467
|
+
signingId: prepared.signingId,
|
|
7468
|
+
cms: FAKE_CMS,
|
|
7469
|
+
expectedVersion: {
|
|
7470
|
+
baseSha256: prepared.expectedVersion.baseSha256,
|
|
7471
|
+
editsVersion: prepared.expectedVersion.editsVersion + 1
|
|
7472
|
+
}
|
|
7473
|
+
}))).toBe(require_PageInsertBlankInput.EngineErrorCode.SigningVersionMismatch);
|
|
7474
|
+
const result = await doc.signatures.complete({
|
|
7475
|
+
signingId: prepared.signingId,
|
|
7476
|
+
cms: FAKE_CMS,
|
|
7477
|
+
expectedVersion: prepared.expectedVersion
|
|
7478
|
+
});
|
|
7479
|
+
expect(result.status).toBe("completed");
|
|
7480
|
+
expect(result.signature.signed).toBe(true);
|
|
7481
|
+
expect(result.signature.fieldName).toBe(fx().fieldName);
|
|
7482
|
+
expect(result.signature.coverage).toBe("whole-revision");
|
|
7483
|
+
expect(result.signature.revisionIndex).toBe(1);
|
|
7484
|
+
expect(result.signature.subFilter).toBe("ETSI.CAdES.detached");
|
|
7485
|
+
expect(result.signature.signer.reason).toBe("conformance");
|
|
7486
|
+
expect(result.signature.contentsSize).toBe(FAKE_CMS.byteLength);
|
|
7487
|
+
expect(result.previous).toEqual(prepared.expectedVersion);
|
|
7488
|
+
expect(result.version.sha256 === v0.sha256).toBe(false);
|
|
7489
|
+
expect(result.protection.judged).toBe("annotate");
|
|
7490
|
+
expect(result.protection.enforced).toBeNull();
|
|
7491
|
+
expect(result.meta.affectedPages.length >= 1).toBe(true);
|
|
7492
|
+
expect(await doc.version()).toEqual(result.version);
|
|
7493
|
+
const after = await doc.signatures.list();
|
|
7494
|
+
expect(after.revisions).toHaveLength(2);
|
|
7495
|
+
expect(after.signatures[0].signed).toBe(true);
|
|
7496
|
+
expect(after.signatures[0].revisionIndex).toBe(1);
|
|
7497
|
+
expect(after.revisions[1].signatureIndex).toBe(0);
|
|
7498
|
+
expect(toHex(await doc.signatures.digest(sigRef(), "sha256"))).toBe(toHex(prepared.digest));
|
|
7499
|
+
expect(bytesEqual(await doc.signatures.contents(sigRef()), FAKE_CMS)).toBe(true);
|
|
7500
|
+
const bytes = await doc.download();
|
|
7501
|
+
expect(bytes.byteLength).toBe(result.version.byteLength);
|
|
7502
|
+
expect(await sha256Hex(bytes)).toBe(result.version.sha256);
|
|
7503
|
+
reopened = await reopen(engine, opts, `${fx().id}-signed`, bytes);
|
|
7504
|
+
const fresh = await reopened.signatures.list();
|
|
7505
|
+
expect(fresh.revisions).toHaveLength(2);
|
|
7506
|
+
expect(fresh.signatures[0].coverage).toBe("whole-revision");
|
|
7507
|
+
expect(toHex(await reopened.signatures.digest(sigRef(), "sha256"))).toBe(toHex(prepared.digest));
|
|
7508
|
+
const replay = await doc.signatures.complete({
|
|
7509
|
+
signingId: prepared.signingId,
|
|
7510
|
+
cms: FAKE_CMS,
|
|
7511
|
+
expectedVersion: prepared.expectedVersion
|
|
7512
|
+
});
|
|
7513
|
+
expect(replay.status).toBe("already-completed");
|
|
7514
|
+
expect(replay.version).toEqual(result.version);
|
|
7515
|
+
expect((await doc.signatures.abort(prepared.signingId)).status).toBe("already-completed");
|
|
7516
|
+
expect((await doc.signatures.abort("never-prepared")).status).toBe("unknown");
|
|
7517
|
+
await doc.forms.setValue(textRef(), {
|
|
7518
|
+
type: "text",
|
|
7519
|
+
value: "after"
|
|
7520
|
+
});
|
|
7521
|
+
expect(await caughtCode(() => doc.signatures.prepare({ field: sigRef() }))).toBe(require_PageInsertBlankInput.EngineErrorCode.SignatureRefused);
|
|
7522
|
+
expect(events.filter((t) => t === "signature.prepared")).toHaveLength(1);
|
|
7523
|
+
expect(events.filter((t) => t === "signature.completed")).toHaveLength(1);
|
|
7524
|
+
expect(events.filter((t) => t === "document.versioned")).toHaveLength(1);
|
|
7525
|
+
} finally {
|
|
7526
|
+
unsubscribe();
|
|
7527
|
+
if (reopened) await reopened.close();
|
|
7528
|
+
await doc.close();
|
|
7529
|
+
}
|
|
7530
|
+
});
|
|
7531
|
+
test("sign: unsaved edits are sealed in the signature's own revision (layer session)", async () => {
|
|
7532
|
+
if (!supported()) return;
|
|
7533
|
+
const doc = await open(engineOf(), opts, fx());
|
|
7534
|
+
try {
|
|
7535
|
+
await doc.forms.setValue(textRef(), {
|
|
7536
|
+
type: "text",
|
|
7537
|
+
value: "unsaved"
|
|
7538
|
+
});
|
|
7539
|
+
const prepared = await doc.signatures.prepare({ field: sigRef() });
|
|
7540
|
+
const result = await doc.signatures.complete({
|
|
7541
|
+
signingId: prepared.signingId,
|
|
7542
|
+
cms: FAKE_CMS,
|
|
7543
|
+
expectedVersion: prepared.expectedVersion
|
|
7544
|
+
});
|
|
7545
|
+
expect(result.signature.revisionIndex).toBe(1);
|
|
7546
|
+
const after = await doc.signatures.list();
|
|
7547
|
+
expect(after.revisions).toHaveLength(2);
|
|
7548
|
+
const text = await doc.forms.get(textRef());
|
|
7549
|
+
expect(text.value).toBe("unsaved");
|
|
7550
|
+
const bytes = await doc.download();
|
|
7551
|
+
expect(await sha256Hex(bytes)).toBe(result.version.sha256);
|
|
7552
|
+
} finally {
|
|
7553
|
+
await doc.close();
|
|
7554
|
+
}
|
|
7555
|
+
});
|
|
7556
|
+
test("sign: a certification protects the document from the moment it completes", async () => {
|
|
7557
|
+
if (!supported()) return;
|
|
7558
|
+
const doc = await open(engineOf(), opts, fx());
|
|
7559
|
+
try {
|
|
7560
|
+
expect(doc.security.allows("doc.pages.assemble")).toBe(true);
|
|
7561
|
+
const prepared = await doc.signatures.prepare({
|
|
7562
|
+
field: sigRef(),
|
|
7563
|
+
certify: { permission: 2 },
|
|
7564
|
+
subFilter: "adbe.pkcs7.detached"
|
|
7565
|
+
});
|
|
7566
|
+
const result = await doc.signatures.complete({
|
|
7567
|
+
signingId: prepared.signingId,
|
|
7568
|
+
cms: FAKE_CMS,
|
|
7569
|
+
expectedVersion: prepared.expectedVersion
|
|
7570
|
+
});
|
|
7571
|
+
expect(result.signature.docMdp).toBe(2);
|
|
7572
|
+
expect(result.signature.catalogCertification).toBe(true);
|
|
7573
|
+
expect(result.protection.judged).toBe("fill");
|
|
7574
|
+
expect(result.protection.enforced).toBe("fill");
|
|
7575
|
+
expect(result.protection.certification).toEqual({
|
|
7576
|
+
signatureIndex: result.signature.index,
|
|
7577
|
+
permission: 2
|
|
7578
|
+
});
|
|
7579
|
+
expect(doc.security.allows("doc.pages.assemble")).toBe(false);
|
|
7580
|
+
expect(doc.security.allows("doc.forms.fill")).toBe(true);
|
|
7581
|
+
const list = await doc.pages.list();
|
|
7582
|
+
expect(await caughtCode(() => doc.pages.delete([list.pages[0].pageObjectNumber]))).toBe(require_PageInsertBlankInput.EngineErrorCode.ProtectedDocument);
|
|
7583
|
+
expect(await caughtCode(() => doc.download({ mode: "rewrite" }))).toBe(require_PageInsertBlankInput.EngineErrorCode.ProtectedDocument);
|
|
7584
|
+
await doc.forms.setValue(textRef(), {
|
|
7585
|
+
type: "text",
|
|
7586
|
+
value: "filled"
|
|
7587
|
+
});
|
|
7588
|
+
const after = await doc.signatures.list();
|
|
7589
|
+
expect(after.signatures[0].coverage).toBe("whole-revision");
|
|
7590
|
+
} finally {
|
|
7591
|
+
await doc.close();
|
|
7592
|
+
}
|
|
7593
|
+
});
|
|
7594
|
+
test("sign: a lock freezes the named fields and is mirrored on the field", async () => {
|
|
7595
|
+
if (!supported()) return;
|
|
7596
|
+
const doc = await open(engineOf(), opts, fx());
|
|
7597
|
+
try {
|
|
7598
|
+
const prepared = await doc.signatures.prepare({
|
|
7599
|
+
field: sigRef(),
|
|
7600
|
+
lock: {
|
|
7601
|
+
action: "include",
|
|
7602
|
+
fields: [fx().textField]
|
|
7603
|
+
}
|
|
7604
|
+
});
|
|
7605
|
+
const result = await doc.signatures.complete({
|
|
7606
|
+
signingId: prepared.signingId,
|
|
7607
|
+
cms: FAKE_CMS,
|
|
7608
|
+
expectedVersion: prepared.expectedVersion
|
|
7609
|
+
});
|
|
7610
|
+
expect(result.signature.fieldMdp).toEqual({
|
|
7611
|
+
action: "include",
|
|
7612
|
+
fields: [fx().textField]
|
|
7613
|
+
});
|
|
7614
|
+
expect(result.signature.lock).toEqual({
|
|
7615
|
+
action: "include",
|
|
7616
|
+
fields: [fx().textField]
|
|
7617
|
+
});
|
|
7618
|
+
expect(result.protection.fieldLocks.length >= 1).toBe(true);
|
|
7619
|
+
expect(await caughtCode(() => doc.forms.setValue(textRef(), {
|
|
7620
|
+
type: "text",
|
|
7621
|
+
value: "nope"
|
|
7622
|
+
}))).toBe(require_PageInsertBlankInput.EngineErrorCode.ProtectedDocument);
|
|
7623
|
+
} finally {
|
|
7624
|
+
await doc.close();
|
|
7625
|
+
}
|
|
7626
|
+
});
|
|
7627
|
+
test("sign: abort discards the candidate and lifts the fence", async () => {
|
|
7628
|
+
if (!supported()) return;
|
|
7629
|
+
const doc = await open(engineOf(), opts, fx());
|
|
7630
|
+
try {
|
|
7631
|
+
const v0 = await doc.version();
|
|
7632
|
+
const prepared = await doc.signatures.prepare({ field: sigRef() });
|
|
7633
|
+
expect((await doc.signatures.abort(prepared.signingId)).status).toBe("aborted");
|
|
7634
|
+
expect((await doc.signatures.list()).signatures[0].signed).toBe(false);
|
|
7635
|
+
expect((await doc.version()).sha256).toBe(v0.sha256);
|
|
7636
|
+
await doc.forms.setValue(textRef(), {
|
|
7637
|
+
type: "text",
|
|
7638
|
+
value: "free again"
|
|
7639
|
+
});
|
|
7640
|
+
expect(await caughtCode(() => doc.signatures.complete({
|
|
7641
|
+
signingId: prepared.signingId,
|
|
7642
|
+
cms: FAKE_CMS,
|
|
7643
|
+
expectedVersion: prepared.expectedVersion
|
|
7644
|
+
}))).toBe(require_PageInsertBlankInput.EngineErrorCode.NotFound);
|
|
7645
|
+
} finally {
|
|
7646
|
+
await doc.close();
|
|
7647
|
+
}
|
|
7648
|
+
});
|
|
7649
|
+
test("sign: artwork from a PDF page lands on the widget", async () => {
|
|
7650
|
+
if (!supported()) return;
|
|
7651
|
+
const doc = await open(engineOf(), opts, fx());
|
|
7652
|
+
try {
|
|
7653
|
+
const artwork = await opts.fixtures.artwork.bytes();
|
|
7654
|
+
const prepared = await doc.signatures.prepare({
|
|
7655
|
+
field: sigRef(),
|
|
7656
|
+
appearance: {
|
|
7657
|
+
pdf: artwork,
|
|
7658
|
+
pageIndex: 0
|
|
7659
|
+
}
|
|
7660
|
+
});
|
|
7661
|
+
const result = await doc.signatures.complete({
|
|
7662
|
+
signingId: prepared.signingId,
|
|
7663
|
+
cms: FAKE_CMS,
|
|
7664
|
+
expectedVersion: prepared.expectedVersion
|
|
7665
|
+
});
|
|
7666
|
+
expect(result.signature.coverage).toBe("whole-revision");
|
|
7667
|
+
expect(result.signature.widget).toBeTruthy();
|
|
7668
|
+
const widget = (await doc.page(result.signature.widget.pageObjectNumber).annotations.list()).annotations.find((a) => a.ref.kind === "objectNumber" && a.ref.annotObjectNumber === result.signature.widget.annotObjectNumber);
|
|
7669
|
+
expect(widget).toBeTruthy();
|
|
7670
|
+
} finally {
|
|
7671
|
+
await doc.close();
|
|
7672
|
+
}
|
|
7673
|
+
});
|
|
7674
|
+
if (opts.openKind === "layerBytes") test("sign: a layer session keeps its kind, with an empty layer over the new base", async () => {
|
|
7675
|
+
if (!supported()) return;
|
|
7676
|
+
const doc = await open(engineOf(), opts, fx());
|
|
7677
|
+
try {
|
|
7678
|
+
const prepared = await doc.signatures.prepare({ field: sigRef() });
|
|
7679
|
+
const result = await doc.signatures.complete({
|
|
7680
|
+
signingId: prepared.signingId,
|
|
7681
|
+
cms: FAKE_CMS,
|
|
7682
|
+
expectedVersion: prepared.expectedVersion
|
|
7683
|
+
});
|
|
7684
|
+
const layer = await doc.downloadLayer();
|
|
7685
|
+
expect(layer.byteLength > 0).toBe(true);
|
|
7686
|
+
expect(await sha256Hex(await doc.download())).toBe(result.version.sha256);
|
|
7687
|
+
} finally {
|
|
7688
|
+
await doc.close();
|
|
7689
|
+
}
|
|
7690
|
+
});
|
|
7691
|
+
}
|
|
7692
|
+
async function reopen(engine, opts, id, bytes) {
|
|
7693
|
+
if (opts.openKind === "layerBytes") return engine.open({
|
|
7694
|
+
kind: "layerBytes",
|
|
7695
|
+
id,
|
|
7696
|
+
baseBytes: bytes
|
|
7697
|
+
});
|
|
7698
|
+
return engine.open({
|
|
7699
|
+
kind: "bytes",
|
|
7700
|
+
id,
|
|
7701
|
+
bytes
|
|
7702
|
+
});
|
|
7703
|
+
}
|
|
7704
|
+
async function open(engine, opts, fixture) {
|
|
7705
|
+
if (opts.openKind === "bytes") return engine.open({
|
|
7706
|
+
kind: "bytes",
|
|
7707
|
+
id: fixture.id,
|
|
7708
|
+
bytes: await fixture.bytes()
|
|
7709
|
+
});
|
|
7710
|
+
if (opts.openKind === "layerBytes") return engine.open({
|
|
7711
|
+
kind: "layerBytes",
|
|
7712
|
+
id: `${fixture.id}-layer`,
|
|
7713
|
+
baseBytes: await fixture.bytes()
|
|
7714
|
+
});
|
|
7715
|
+
return engine.open({
|
|
7716
|
+
kind: "id",
|
|
7717
|
+
id: fixture.cloudId ?? fixture.id
|
|
7718
|
+
});
|
|
7719
|
+
}
|
|
7720
|
+
function concat(a, b) {
|
|
7721
|
+
const out = new Uint8Array(a.byteLength + b.byteLength);
|
|
7722
|
+
out.set(a, 0);
|
|
7723
|
+
out.set(b, a.byteLength);
|
|
7724
|
+
return out;
|
|
7725
|
+
}
|
|
7726
|
+
function bytesEqual(a, b) {
|
|
7727
|
+
if (a.byteLength !== b.byteLength) return false;
|
|
7728
|
+
for (let i = 0; i < a.byteLength; i++) if (a[i] !== b[i]) return false;
|
|
7729
|
+
return true;
|
|
7730
|
+
}
|
|
7731
|
+
function toHex(bytes) {
|
|
7732
|
+
let out = "";
|
|
7733
|
+
for (const b of bytes) out += b.toString(16).padStart(2, "0");
|
|
7734
|
+
return out;
|
|
7735
|
+
}
|
|
7736
|
+
async function sha256Hex(bytes) {
|
|
7737
|
+
const copy = new Uint8Array(bytes.byteLength);
|
|
7738
|
+
copy.set(bytes);
|
|
7739
|
+
const digest = await globalThis.crypto.subtle.digest("SHA-256", copy);
|
|
7740
|
+
return toHex(new Uint8Array(digest));
|
|
7741
|
+
}
|
|
7742
|
+
//#endregion
|
|
6224
7743
|
exports.TEXT_DIVERGENCE_CASES = TEXT_DIVERGENCE_CASES;
|
|
6225
7744
|
exports.diffAnnotationListSnapshot = diffAnnotationListSnapshot;
|
|
6226
7745
|
exports.diffAnnotationListSnapshotAll = diffAnnotationListSnapshotAll;
|
|
6227
7746
|
exports.runActionsConformance = runActionsConformance;
|
|
6228
7747
|
exports.runAnnotationAppearanceConformance = runAnnotationAppearanceConformance;
|
|
7748
|
+
exports.runAnnotationAppearanceExportConformance = runAnnotationAppearanceExportConformance;
|
|
7749
|
+
exports.runAnnotationFlattenConformance = runAnnotationFlattenConformance;
|
|
6229
7750
|
exports.runAnnotationMutationConformance = runAnnotationMutationConformance;
|
|
6230
7751
|
exports.runAnnotationReadConformance = runAnnotationReadConformance;
|
|
6231
7752
|
exports.runAttachmentConformance = runAttachmentConformance;
|
|
6232
7753
|
exports.runDocumentEventsConformance = runDocumentEventsConformance;
|
|
6233
7754
|
exports.runFormConformance = runFormConformance;
|
|
6234
7755
|
exports.runMetadataConformance = runMetadataConformance;
|
|
7756
|
+
exports.runNamedPagesConformance = runNamedPagesConformance;
|
|
6235
7757
|
exports.runPageDeleteConformance = runPageDeleteConformance;
|
|
6236
7758
|
exports.runPageExtractConformance = runPageExtractConformance;
|
|
6237
7759
|
exports.runPageFlattenConformance = runPageFlattenConformance;
|
|
@@ -6244,6 +7766,7 @@ exports.runPageTextConformance = runPageTextConformance;
|
|
|
6244
7766
|
exports.runPieceInfoConformance = runPieceInfoConformance;
|
|
6245
7767
|
exports.runRedactionApplyConformance = runRedactionApplyConformance;
|
|
6246
7768
|
exports.runSearchConformance = runSearchConformance;
|
|
7769
|
+
exports.runSignatureConformance = runSignatureConformance;
|
|
6247
7770
|
exports.runTextDivergenceConformance = runTextDivergenceConformance;
|
|
6248
7771
|
|
|
6249
7772
|
//# sourceMappingURL=conformance.cjs.map
|