@adeu/core 1.17.0 → 1.17.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adeu/core",
3
- "version": "1.17.0",
3
+ "version": "1.17.2",
4
4
  "description": "",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -83,7 +83,7 @@ describe("QA Report V2: Engine Logic", () => {
83
83
  target_text: "Occurrence",
84
84
  new_text: "Match",
85
85
  match_mode: "all"
86
- };
86
+ } as any;
87
87
  const stats = engine.process_batch([edit], false);
88
88
 
89
89
  const report = stats.edits[0];
@@ -116,7 +116,7 @@ describe("QA Report V2: Engine Logic", () => {
116
116
  target_text: "Target word",
117
117
  new_text: "Replaced",
118
118
  match_mode: "all"
119
- };
119
+ } as any;
120
120
 
121
121
  expect(() => engine.process_batch([edit], false)).toThrow(/targets an active insertion from another author/);
122
122
  });
@@ -132,7 +132,7 @@ describe("QA Report V2: Engine Logic", () => {
132
132
  target_text: "part.\\n\\nSecond",
133
133
  new_text: "merged",
134
134
  regex: true
135
- };
135
+ } as any;
136
136
 
137
137
  expect(() => engine.process_batch([edit], false)).toThrow(/spans a paragraph boundary with body text on both sides/);
138
138
  });
@@ -155,7 +155,7 @@ describe("QA Report V2: Engine Logic", () => {
155
155
  target_text: "the Board of Directors",
156
156
  new_text: "the Supervisory Board",
157
157
  match_mode: "all"
158
- };
158
+ } as any;
159
159
 
160
160
  expect(() => engineBob.process_batch([editBob], false)).toThrow(/another author/i);
161
161
  });
@@ -1,12 +1,24 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { createTestDocument, addParagraph, addTable, setCellText, mergeCells, addNestedTable } from './test-utils.js';
3
- import { DocumentObject } from './docx/bridge.js';
4
- import { extractTextFromBuffer } from './ingest.js';
5
- import { RedlineEngine } from './engine.js';
6
- import { ModifyText, InsertTableRow, DeleteTableRow, RejectChange } from './models.js';
7
-
8
- describe('Table Interop & Engine (Node.js Port)', () => {
9
- it('interleaved tables and text remain ordered', async () => {
1
+ import { describe, it, expect } from "vitest";
2
+ import {
3
+ createTestDocument,
4
+ addParagraph,
5
+ addTable,
6
+ setCellText,
7
+ mergeCells,
8
+ addNestedTable,
9
+ } from "./test-utils.js";
10
+ import { DocumentObject } from "./docx/bridge.js";
11
+ import { extractTextFromBuffer } from "./ingest.js";
12
+ import { RedlineEngine } from "./engine.js";
13
+ import {
14
+ ModifyText,
15
+ InsertTableRow,
16
+ DeleteTableRow,
17
+ RejectChange,
18
+ } from "./models.js";
19
+
20
+ describe("Table Interop & Engine (Node.js Port)", () => {
21
+ it("interleaved tables and text remain ordered", async () => {
10
22
  const doc = await createTestDocument();
11
23
  addParagraph(doc, "Section 1");
12
24
  const tbl = addTable(doc, 1, 1);
@@ -28,13 +40,17 @@ describe('Table Interop & Engine (Node.js Port)', () => {
28
40
  expect(tIdx).toBeLessThan(p2);
29
41
  });
30
42
 
31
- it('extracts and edits nested tables correctly', async () => {
43
+ it("extracts and edits nested tables correctly", async () => {
32
44
  const doc = await createTestDocument();
33
45
  const outerTbl = addTable(doc, 1, 1);
34
-
35
- const rows = Array.from(outerTbl.childNodes).filter(n => (n as Element).tagName === 'w:tr') as Element[];
36
- const cells = Array.from(rows[0].childNodes).filter(n => (n as Element).tagName === 'w:tc') as Element[];
37
-
46
+
47
+ const rows = Array.from(outerTbl.childNodes).filter(
48
+ (n) => (n as Element).tagName === "w:tr",
49
+ ) as Element[];
50
+ const cells = Array.from(rows[0].childNodes).filter(
51
+ (n) => (n as Element).tagName === "w:tc",
52
+ ) as Element[];
53
+
38
54
  const nestedTbl = addNestedTable(cells[0], 1, 1);
39
55
  setCellText(nestedTbl, 0, 0, "InnerSecret");
40
56
 
@@ -44,7 +60,13 @@ describe('Table Interop & Engine (Node.js Port)', () => {
44
60
 
45
61
  const midDoc = await DocumentObject.load(buf);
46
62
  const engine = new RedlineEngine(midDoc);
47
- const [applied] = engine.apply_edits([{ type: 'modify', target_text: "InnerSecret", new_text: "OuterSecret" } as ModifyText]);
63
+ const [applied] = engine.apply_edits([
64
+ {
65
+ type: "modify",
66
+ target_text: "InnerSecret",
67
+ new_text: "OuterSecret",
68
+ } as ModifyText,
69
+ ]);
48
70
  expect(applied).toBe(1);
49
71
 
50
72
  const finalBuf = await midDoc.save();
@@ -52,11 +74,11 @@ describe('Table Interop & Engine (Node.js Port)', () => {
52
74
  expect(final_text).toContain("{--InnerSecret--}{++OuterSecret++}");
53
75
  });
54
76
 
55
- it('merged cells do not duplicate content extraction', async () => {
77
+ it("merged cells do not duplicate content extraction", async () => {
56
78
  const doc = await createTestDocument();
57
79
  const tbl = addTable(doc, 1, 2);
58
80
  setCellText(tbl, 0, 0, "MergedUnique");
59
-
81
+
60
82
  // Simulate python-docx's cell.merge(cell)
61
83
  mergeCells(tbl, 0, 0, 1);
62
84
 
@@ -68,11 +90,17 @@ describe('Table Interop & Engine (Node.js Port)', () => {
68
90
 
69
91
  const midDoc = await DocumentObject.load(buf);
70
92
  const engine = new RedlineEngine(midDoc);
71
- const [applied] = engine.apply_edits([{ type: 'modify', target_text: "MergedUnique", new_text: "ChangedUnique" } as ModifyText]);
93
+ const [applied] = engine.apply_edits([
94
+ {
95
+ type: "modify",
96
+ target_text: "MergedUnique",
97
+ new_text: "ChangedUnique",
98
+ } as ModifyText,
99
+ ]);
72
100
  expect(applied).toBe(1);
73
101
  });
74
102
 
75
- it('empty row mapping alignment stays synchronized', async () => {
103
+ it("empty row mapping alignment stays synchronized", async () => {
76
104
  const doc = await createTestDocument();
77
105
  const tbl = addTable(doc, 3, 1);
78
106
  setCellText(tbl, 0, 0, "RowA");
@@ -81,10 +109,12 @@ describe('Table Interop & Engine (Node.js Port)', () => {
81
109
 
82
110
  const buf = await doc.save();
83
111
  const midDoc = await DocumentObject.load(buf);
84
-
112
+
85
113
  const engine = new RedlineEngine(midDoc);
86
- const [applied] = engine.apply_edits([{ type: 'modify', target_text: "RowB", new_text: "RowC" } as ModifyText]);
87
-
114
+ const [applied] = engine.apply_edits([
115
+ { type: "modify", target_text: "RowB", new_text: "RowC" } as ModifyText,
116
+ ]);
117
+
88
118
  expect(applied).toBe(1);
89
119
 
90
120
  const resBuf = await midDoc.save();
@@ -94,18 +124,25 @@ describe('Table Interop & Engine (Node.js Port)', () => {
94
124
  expect(resText).toContain("{--RowB--}{++RowC++}");
95
125
  });
96
126
 
97
- it('inserts table row below', async () => {
127
+ it("inserts table row below", async () => {
98
128
  const doc = await createTestDocument();
99
129
  const tbl = addTable(doc, 2, 2);
100
- setCellText(tbl, 0, 0, "A1"); setCellText(tbl, 0, 1, "A2");
101
- setCellText(tbl, 1, 0, "B1"); setCellText(tbl, 1, 1, "B2");
130
+ setCellText(tbl, 0, 0, "A1");
131
+ setCellText(tbl, 0, 1, "A2");
132
+ setCellText(tbl, 1, 0, "B1");
133
+ setCellText(tbl, 1, 1, "B2");
102
134
 
103
135
  const buf = await doc.save();
104
136
  const midDoc = await DocumentObject.load(buf);
105
137
 
106
138
  const engine = new RedlineEngine(midDoc);
107
139
  const stats = engine.process_batch([
108
- { type: 'insert_row', target_text: "A1 | A2", position: "below", cells: ["New B1", "New B2"] } as InsertTableRow
140
+ {
141
+ type: "insert_row",
142
+ target_text: "A1 | A2",
143
+ position: "below",
144
+ cells: ["New B1", "New B2"],
145
+ } as InsertTableRow,
109
146
  ]);
110
147
 
111
148
  expect(stats.edits_applied).toBe(1);
@@ -120,18 +157,23 @@ describe('Table Interop & Engine (Node.js Port)', () => {
120
157
  expect(clean_text).toContain("B1 | B2");
121
158
  });
122
159
 
123
- it('deletes table row', async () => {
160
+ it("deletes table row", async () => {
124
161
  const doc = await createTestDocument();
125
162
  const tbl = addTable(doc, 3, 2);
126
- setCellText(tbl, 0, 0, "A1"); setCellText(tbl, 0, 1, "A2");
127
- setCellText(tbl, 1, 0, "B1"); setCellText(tbl, 1, 1, "B2");
128
- setCellText(tbl, 2, 0, "C1"); setCellText(tbl, 2, 1, "C2");
163
+ setCellText(tbl, 0, 0, "A1");
164
+ setCellText(tbl, 0, 1, "A2");
165
+ setCellText(tbl, 1, 0, "B1");
166
+ setCellText(tbl, 1, 1, "B2");
167
+ setCellText(tbl, 2, 0, "C1");
168
+ setCellText(tbl, 2, 1, "C2");
129
169
 
130
170
  const buf = await doc.save();
131
171
  const midDoc = await DocumentObject.load(buf);
132
172
 
133
173
  const engine = new RedlineEngine(midDoc);
134
- const stats = engine.process_batch([{ type: 'delete_row', target_text: "B1" } as DeleteTableRow]);
174
+ const stats = engine.process_batch([
175
+ { type: "delete_row", target_text: "B1" } as DeleteTableRow,
176
+ ]);
135
177
 
136
178
  expect(stats.edits_applied).toBe(1);
137
179
 
@@ -144,17 +186,21 @@ describe('Table Interop & Engine (Node.js Port)', () => {
144
186
  expect(clean_text).toContain("C1 | C2");
145
187
  });
146
188
 
147
- it('clean view naturally omits deleted row', async () => {
189
+ it("clean view naturally omits deleted row", async () => {
148
190
  const doc = await createTestDocument();
149
191
  const tbl = addTable(doc, 2, 2);
150
- setCellText(tbl, 0, 0, "A1"); setCellText(tbl, 0, 1, "A2");
151
- setCellText(tbl, 1, 0, "B1"); setCellText(tbl, 1, 1, "B2");
192
+ setCellText(tbl, 0, 0, "A1");
193
+ setCellText(tbl, 0, 1, "A2");
194
+ setCellText(tbl, 1, 0, "B1");
195
+ setCellText(tbl, 1, 1, "B2");
152
196
 
153
197
  const buf = await doc.save();
154
198
  const midDoc = await DocumentObject.load(buf);
155
199
 
156
200
  const engine = new RedlineEngine(midDoc);
157
- engine.process_batch([{ type: 'delete_row', target_text: "B1" } as DeleteTableRow]);
201
+ engine.process_batch([
202
+ { type: "delete_row", target_text: "B1" } as DeleteTableRow,
203
+ ]);
158
204
 
159
205
  // Do NOT accept revisions, extract as Clean View directly
160
206
  const finalBuf = await midDoc.save();
@@ -163,4 +209,60 @@ describe('Table Interop & Engine (Node.js Port)', () => {
163
209
  expect(clean_text).toContain("A1 | A2");
164
210
  expect(clean_text).not.toContain("B1 | B2");
165
211
  });
166
- });
212
+ it("P0 Case 1: writes into an empty value cell via its {#cell:paraId} anchor", async () => {
213
+ const doc = await createTestDocument();
214
+ const tbl = addTable(doc, 1, 2);
215
+ setCellText(tbl, 0, 0, "Date");
216
+ // Leave cell (0,1) empty — mirrors the cloud-service-agreement form row.
217
+
218
+ // The empty cell's paragraph needs a w14:paraId for the anchor scheme.
219
+ const rows = Array.from(tbl.childNodes).filter(
220
+ (n) => (n as Element).tagName === "w:tr",
221
+ ) as Element[];
222
+ const cells = Array.from(rows[0].childNodes).filter(
223
+ (n) => (n as Element).tagName === "w:tc",
224
+ ) as Element[];
225
+ const emptyP = cells[1].getElementsByTagName("w:p")[0];
226
+ emptyP.setAttribute("w14:paraId", "DEADBEEF");
227
+
228
+ const buf = await doc.save();
229
+ const projected = await extractTextFromBuffer(buf, false);
230
+ // The anchor must be present and addressable.
231
+ expect(projected).toContain("{#cell:DEADBEEF}");
232
+
233
+ const midDoc = await DocumentObject.load(buf);
234
+ const engine = new RedlineEngine(midDoc);
235
+ // Target the anchor, insert text before it.
236
+ const stats = engine.process_batch([
237
+ {
238
+ type: "modify",
239
+ target_text: "{#cell:DEADBEEF}",
240
+ new_text: "June 22, 2026",
241
+ } as any,
242
+ ]);
243
+ expect(stats.edits_applied).toBe(1);
244
+
245
+ (engine as any).accept_all_revisions();
246
+ const cleanText = await extractTextFromBuffer(await midDoc.save(), true);
247
+ expect(cleanText).toContain("June 22, 2026");
248
+ });
249
+
250
+ it('P0 Case 2: anchored-regex miss on "( x )" yields a literal nearest-match hint', async () => {
251
+ const doc = await createTestDocument();
252
+ addParagraph(doc, "Some intro text. ( x ) Date of last signature.");
253
+ const engine = new RedlineEngine(doc);
254
+
255
+ const errors = engine.validate_edits([
256
+ {
257
+ type: "modify",
258
+ regex: true,
259
+ target_text: "^\\( x \\)$",
260
+ new_text: "",
261
+ } as any,
262
+ ]);
263
+ expect(errors.length).toBe(1);
264
+ expect(errors[0]).toContain("Target text not found");
265
+ expect(errors[0]).toContain('Did you mean the literal "( x )"');
266
+ expect(errors[0]).toContain("drop the ^/$ anchors");
267
+ });
268
+ });