@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/README.md +1 -0
- package/dist/index.cjs +353 -85
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.js +353 -85
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/engine.qa.test.ts +4 -4
- package/src/engine.tables.test.ts +138 -36
- package/src/engine.ts +433 -105
- package/src/ingest.ts +16 -1
- package/src/mapper.ts +23 -1
- package/src/markup.test.ts +41 -6
- package/src/markup.ts +13 -2
- package/src/repro.feedback.test.ts +160 -0
- package/src/repro.report.test.ts +130 -0
- package/src/repro_qa_report_v3.test.ts +194 -0
- package/src/search_write_engine.test.ts +41 -0
package/package.json
CHANGED
package/src/engine.qa.test.ts
CHANGED
|
@@ -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
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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(
|
|
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(
|
|
36
|
-
|
|
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([
|
|
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(
|
|
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([
|
|
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(
|
|
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([
|
|
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(
|
|
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");
|
|
101
|
-
setCellText(tbl,
|
|
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
|
-
{
|
|
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(
|
|
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");
|
|
127
|
-
setCellText(tbl,
|
|
128
|
-
setCellText(tbl,
|
|
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([
|
|
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(
|
|
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");
|
|
151
|
-
setCellText(tbl,
|
|
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([
|
|
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
|
+
});
|