@adeu/core 1.6.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.
@@ -0,0 +1,93 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { createTestDocument, addParagraph } 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, AcceptChange, RejectChange } from './models.js';
7
+
8
+ describe('Batch Reliability (Node.js Port)', () => {
9
+ it('batch accept does not corrupt the document', async () => {
10
+ const doc = await createTestDocument();
11
+ addParagraph(doc, "Para 1");
12
+ addParagraph(doc, "Para 2");
13
+ addParagraph(doc, "Para 3");
14
+
15
+ const engine = new RedlineEngine(doc);
16
+ const edits: ModifyText[] = [
17
+ { type: 'modify', target_text: "Para 1", new_text: "Para One" },
18
+ { type: 'modify', target_text: "Para 2", new_text: "Para Two" },
19
+ { type: 'modify', target_text: "Para 3", new_text: "Para Three" },
20
+ ];
21
+
22
+ engine.apply_edits(edits);
23
+ const redlinedBuf = await doc.save();
24
+
25
+ const text = await extractTextFromBuffer(redlinedBuf);
26
+ expect(text).toContain("[Chg:1 ");
27
+ expect(text).toContain("[Chg:6 ");
28
+
29
+ // BATCH ACCEPT ALL
30
+ const midDoc = await DocumentObject.load(redlinedBuf);
31
+ const engine2 = new RedlineEngine(midDoc);
32
+
33
+ const actions: AcceptChange[] = [1, 2, 3, 4, 5, 6].map(id => ({ type: 'accept', target_id: `Chg:${id}` }));
34
+
35
+ // Test direct apply_review_actions (Note: method missing in raw TS port right now, needs implementing)
36
+ const [applied, skipped] = (engine2 as any).apply_review_actions(actions);
37
+
38
+ expect(applied).toBe(6);
39
+ expect(skipped).toBe(0);
40
+
41
+ const finalBuf = await midDoc.save();
42
+ const final_text = await extractTextFromBuffer(finalBuf);
43
+
44
+ expect(final_text).toContain("Para One");
45
+ expect(final_text).toContain("Para Two");
46
+ expect(final_text).toContain("Para Three");
47
+
48
+ expect(final_text).not.toContain("Para 1");
49
+ expect(final_text).not.toContain("Para 2");
50
+ expect(final_text).not.toContain("Para 3");
51
+
52
+ expect(final_text).not.toContain("[Chg:");
53
+ expect(final_text).not.toContain("{++");
54
+ expect(final_text).not.toContain("{--");
55
+ });
56
+
57
+ it('batch mixed accept and reject maintains integrity', async () => {
58
+ const doc = await createTestDocument();
59
+ addParagraph(doc, "Para 1");
60
+ addParagraph(doc, "Para 2");
61
+
62
+ const engine = new RedlineEngine(doc);
63
+ const edits: ModifyText[] = [
64
+ { type: 'modify', target_text: "Para 1", new_text: "Para One" },
65
+ { type: 'modify', target_text: "Para 2", new_text: "Para Two" },
66
+ ];
67
+
68
+ engine.apply_edits(edits);
69
+ const redlinedBuf = await doc.save();
70
+
71
+ const midDoc = await DocumentObject.load(redlinedBuf);
72
+ const engine2 = new RedlineEngine(midDoc);
73
+
74
+ const actions = [
75
+ { type: 'accept', target_id: "Chg:3" } as AcceptChange,
76
+ { type: 'accept', target_id: "Chg:4" } as AcceptChange,
77
+ { type: 'reject', target_id: "Chg:1" } as RejectChange,
78
+ { type: 'reject', target_id: "Chg:2" } as RejectChange,
79
+ ];
80
+
81
+ const [applied] = (engine2 as any).apply_review_actions(actions);
82
+ expect(applied).toBe(4);
83
+
84
+ const finalBuf = await midDoc.save();
85
+ const text_final = await extractTextFromBuffer(finalBuf);
86
+
87
+ expect(text_final).toContain("Para One");
88
+ expect(text_final).not.toContain("Para 1");
89
+
90
+ expect(text_final).toContain("Para 2");
91
+ expect(text_final).not.toContain("Para Two");
92
+ });
93
+ });
@@ -0,0 +1,42 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { createTestDocument, addParagraph } from './test-utils.js';
3
+ import { RedlineEngine } from './engine.js';
4
+ import { ModifyText } from './models.js';
5
+
6
+ describe('Safety Engine Constraints (Node.js Port)', () => {
7
+ it('rejects empty target heuristic to prevent accidental insertions', async () => {
8
+ const doc = await createTestDocument();
9
+ addParagraph(doc, "Content");
10
+
11
+ const engine = new RedlineEngine(doc);
12
+ const edit: ModifyText = { type: 'modify', target_text: "", new_text: "Unexpected Header" };
13
+
14
+ const [applied, skipped] = engine.apply_edits([edit]);
15
+
16
+ expect(applied).toBe(0);
17
+ expect(skipped).toBe(1);
18
+
19
+ const xml = (doc.element as Element).ownerDocument?.documentElement.toString();
20
+ expect(xml).not.toContain("Unexpected Header");
21
+ });
22
+
23
+ it('applies heuristic edits strictly once for multiple occurrences', async () => {
24
+ const doc = await createTestDocument();
25
+ addParagraph(doc, "Repeat");
26
+ addParagraph(doc, "Repeat");
27
+
28
+ const engine = new RedlineEngine(doc);
29
+ const edit: ModifyText = { type: 'modify', target_text: "Repeat", new_text: "Changed" };
30
+
31
+ const [applied, skipped] = engine.apply_edits([edit]);
32
+
33
+ expect(applied).toBe(1);
34
+ expect(skipped).toBe(0);
35
+
36
+ const xml = (doc.element as Element).ownerDocument?.documentElement.toString();
37
+
38
+ // We expect one deletion of Repeat and one insertion of Changed
39
+ expect((xml?.match(/<w:delText[^>]*>Repeat<\/w:delText>/g) || []).length).toBe(1);
40
+ expect((xml?.match(/<w:t[^>]*>Changed<\/w:t>/g) || []).length).toBe(1);
41
+ });
42
+ });
@@ -0,0 +1,166 @@
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 () => {
10
+ const doc = await createTestDocument();
11
+ addParagraph(doc, "Section 1");
12
+ const tbl = addTable(doc, 1, 1);
13
+ setCellText(tbl, 0, 0, "TableContent");
14
+ addParagraph(doc, "Section 2");
15
+
16
+ const buf = await doc.save();
17
+ const text = await extractTextFromBuffer(buf);
18
+
19
+ expect(text).toContain("Section 1");
20
+ expect(text).toContain("TableContent");
21
+ expect(text).toContain("Section 2");
22
+
23
+ const p1 = text.indexOf("Section 1");
24
+ const tIdx = text.indexOf("TableContent");
25
+ const p2 = text.indexOf("Section 2");
26
+
27
+ expect(p1).toBeLessThan(tIdx);
28
+ expect(tIdx).toBeLessThan(p2);
29
+ });
30
+
31
+ it('extracts and edits nested tables correctly', async () => {
32
+ const doc = await createTestDocument();
33
+ 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
+
38
+ const nestedTbl = addNestedTable(cells[0], 1, 1);
39
+ setCellText(nestedTbl, 0, 0, "InnerSecret");
40
+
41
+ const buf = await doc.save();
42
+ const text = await extractTextFromBuffer(buf);
43
+ expect(text).toContain("InnerSecret");
44
+
45
+ const midDoc = await DocumentObject.load(buf);
46
+ const engine = new RedlineEngine(midDoc);
47
+ const [applied] = engine.apply_edits([{ type: 'modify', target_text: "InnerSecret", new_text: "OuterSecret" } as ModifyText]);
48
+ expect(applied).toBe(1);
49
+
50
+ const finalBuf = await midDoc.save();
51
+ const final_text = await extractTextFromBuffer(finalBuf);
52
+ expect(final_text).toContain("{--InnerSecret--}{++OuterSecret++}");
53
+ });
54
+
55
+ it('merged cells do not duplicate content extraction', async () => {
56
+ const doc = await createTestDocument();
57
+ const tbl = addTable(doc, 1, 2);
58
+ setCellText(tbl, 0, 0, "MergedUnique");
59
+
60
+ // Simulate python-docx's cell.merge(cell)
61
+ mergeCells(tbl, 0, 0, 1);
62
+
63
+ const buf = await doc.save();
64
+ const text = await extractTextFromBuffer(buf);
65
+
66
+ const count = (text.match(/MergedUnique/g) || []).length;
67
+ expect(count).toBe(1);
68
+
69
+ const midDoc = await DocumentObject.load(buf);
70
+ const engine = new RedlineEngine(midDoc);
71
+ const [applied] = engine.apply_edits([{ type: 'modify', target_text: "MergedUnique", new_text: "ChangedUnique" } as ModifyText]);
72
+ expect(applied).toBe(1);
73
+ });
74
+
75
+ it('empty row mapping alignment stays synchronized', async () => {
76
+ const doc = await createTestDocument();
77
+ const tbl = addTable(doc, 3, 1);
78
+ setCellText(tbl, 0, 0, "RowA");
79
+ setCellText(tbl, 1, 0, ""); // Empty
80
+ setCellText(tbl, 2, 0, "RowB");
81
+
82
+ const buf = await doc.save();
83
+ const midDoc = await DocumentObject.load(buf);
84
+
85
+ const engine = new RedlineEngine(midDoc);
86
+ const [applied] = engine.apply_edits([{ type: 'modify', target_text: "RowB", new_text: "RowC" } as ModifyText]);
87
+
88
+ expect(applied).toBe(1);
89
+
90
+ const resBuf = await midDoc.save();
91
+ const resText = await extractTextFromBuffer(resBuf);
92
+
93
+ expect(resText).toContain("RowA");
94
+ expect(resText).toContain("{--RowB--}{++RowC++}");
95
+ });
96
+
97
+ it('inserts table row below', async () => {
98
+ const doc = await createTestDocument();
99
+ 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");
102
+
103
+ const buf = await doc.save();
104
+ const midDoc = await DocumentObject.load(buf);
105
+
106
+ const engine = new RedlineEngine(midDoc);
107
+ const stats = engine.process_batch([
108
+ { type: 'insert_row', target_text: "A1 | A2", position: "below", cells: ["New B1", "New B2"] } as InsertTableRow
109
+ ]);
110
+
111
+ expect(stats.edits_applied).toBe(1);
112
+
113
+ // Call accept_all_revisions (requires implementation in engine.ts)
114
+ (engine as any).accept_all_revisions();
115
+ const finalBuf = await midDoc.save();
116
+ const clean_text = await extractTextFromBuffer(finalBuf, true);
117
+
118
+ expect(clean_text).toContain("A1 | A2");
119
+ expect(clean_text).toContain("New B1 | New B2");
120
+ expect(clean_text).toContain("B1 | B2");
121
+ });
122
+
123
+ it('deletes table row', async () => {
124
+ const doc = await createTestDocument();
125
+ 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");
129
+
130
+ const buf = await doc.save();
131
+ const midDoc = await DocumentObject.load(buf);
132
+
133
+ const engine = new RedlineEngine(midDoc);
134
+ const stats = engine.process_batch([{ type: 'delete_row', target_text: "B1" } as DeleteTableRow]);
135
+
136
+ expect(stats.edits_applied).toBe(1);
137
+
138
+ (engine as any).accept_all_revisions();
139
+ const finalBuf = await midDoc.save();
140
+ const clean_text = await extractTextFromBuffer(finalBuf, true);
141
+
142
+ expect(clean_text).toContain("A1 | A2");
143
+ expect(clean_text).not.toContain("B1 | B2");
144
+ expect(clean_text).toContain("C1 | C2");
145
+ });
146
+
147
+ it('clean view naturally omits deleted row', async () => {
148
+ const doc = await createTestDocument();
149
+ 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");
152
+
153
+ const buf = await doc.save();
154
+ const midDoc = await DocumentObject.load(buf);
155
+
156
+ const engine = new RedlineEngine(midDoc);
157
+ engine.process_batch([{ type: 'delete_row', target_text: "B1" } as DeleteTableRow]);
158
+
159
+ // Do NOT accept revisions, extract as Clean View directly
160
+ const finalBuf = await midDoc.save();
161
+ const clean_text = await extractTextFromBuffer(finalBuf, true);
162
+
163
+ expect(clean_text).toContain("A1 | A2");
164
+ expect(clean_text).not.toContain("B1 | B2");
165
+ });
166
+ });