diff --git a/src/patcher/from-docx.spec.ts b/src/patcher/from-docx.spec.ts new file mode 100644 index 0000000000..1e82f86059 --- /dev/null +++ b/src/patcher/from-docx.spec.ts @@ -0,0 +1,41 @@ +import { expect } from "chai"; +import * as sinon from "sinon"; +import * as JSZip from "jszip"; + +import { TextRun } from "@file/paragraph"; + +import { patchDocument, PatchType } from "./from-docx"; + +describe("from-docx", () => { + describe("patchDocument", () => { + before(() => { + sinon.createStubInstance(JSZip, {}); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + sinon.stub(JSZip, "loadAsync").callsFake( + () => + new Promise((resolve) => { + const zip = new JSZip(); + + zip.file("word/document.xml", ``); + resolve(zip); + }), + ); + }); + + after(() => { + (JSZip.loadAsync as unknown as sinon.SinonStub).restore(); + }); + + it("should find the index of a run element with a token", async () => { + const output = await patchDocument(Buffer.from(""), { + patches: { + name: { + type: PatchType.PARAGRAPH, + children: [new TextRun("Sir. "), new TextRun("John Doe"), new TextRun("(The Conqueror)")], + }, + }, + }); + expect(output).to.not.be.undefined; + }); + }); +}); diff --git a/src/patcher/from-docx.ts b/src/patcher/from-docx.ts index 38d45b23c8..c464ce6031 100644 --- a/src/patcher/from-docx.ts +++ b/src/patcher/from-docx.ts @@ -55,7 +55,6 @@ const imageReplacer = new ImageReplacer(); export const patchDocument = async (data: InputDataType, options: PatchDocumentOptions): Promise => { const zipContent = await JSZip.loadAsync(data); - const contexts = new Map(); const file = { Media: new Media(), diff --git a/src/patcher/paragraph-split-inject.spec.ts b/src/patcher/paragraph-split-inject.spec.ts new file mode 100644 index 0000000000..bfb3cbdb0b --- /dev/null +++ b/src/patcher/paragraph-split-inject.spec.ts @@ -0,0 +1,101 @@ +import { expect } from "chai"; + +import { findRunElementIndexWithToken, splitRunElement } from "./paragraph-split-inject"; + +describe("paragraph-split-inject", () => { + describe("findRunElementIndexWithToken", () => { + it("should find the index of a run element with a token", () => { + const output = findRunElementIndexWithToken( + { + name: "w:p", + type: "element", + elements: [ + { + name: "w:r", + type: "element", + elements: [ + { + name: "w:t", + type: "element", + elements: [ + { + type: "text", + text: "hello world", + }, + ], + }, + ], + }, + ], + }, + "hello", + ); + expect(output).to.deep.equal(0); + }); + }); + + describe("splitRunElement", () => { + it("should split a run element", () => { + const output = splitRunElement( + { + name: "w:r", + type: "element", + elements: [ + { + name: "w:t", + type: "element", + elements: [ + { + type: "text", + text: "hello*world", + }, + ], + }, + ], + }, + "*", + ); + + expect(output).to.deep.equal({ + left: { + elements: [ + { + attributes: { + "xml:space": "preserve", + }, + elements: [ + { + text: "hello", + type: "text", + }, + ], + name: "w:t", + type: "element", + }, + ], + name: "w:r", + type: "element", + }, + right: { + elements: [ + { + attributes: { + "xml:space": "preserve", + }, + elements: [ + { + text: "world", + type: "text", + }, + ], + name: "w:t", + type: "element", + }, + ], + name: "w:r", + type: "element", + }, + }); + }); + }); +}); diff --git a/src/patcher/paragraph-split-inject.ts b/src/patcher/paragraph-split-inject.ts index d3fe8d5e6c..1a2f06ad06 100644 --- a/src/patcher/paragraph-split-inject.ts +++ b/src/patcher/paragraph-split-inject.ts @@ -14,7 +14,7 @@ export const findRunElementIndexWithToken = (paragraphElement: Element, token: s } } } - // return -1; + throw new Error("Token not found"); }; diff --git a/src/patcher/paragraph-token-replacer.spec.ts b/src/patcher/paragraph-token-replacer.spec.ts new file mode 100644 index 0000000000..1ce88b1b80 --- /dev/null +++ b/src/patcher/paragraph-token-replacer.spec.ts @@ -0,0 +1,74 @@ +import { expect } from "chai"; + +import { replaceTokenInParagraphElement } from "./paragraph-token-replacer"; + +describe("paragraph-token-replacer", () => { + describe("replaceTokenInParagraphElement", () => { + it("should replace token in paragraph", () => { + const output = replaceTokenInParagraphElement({ + paragraphElement: { + name: "w:p", + elements: [ + { + name: "w:r", + elements: [ + { + name: "w:t", + elements: [ + { + type: "text", + text: "hello", + }, + ], + }, + ], + }, + ], + }, + renderedParagraph: { + index: 0, + path: [0], + runs: [ + { + end: 4, + index: 0, + parts: [ + { + end: 4, + index: 0, + start: 0, + text: "hello", + }, + ], + start: 0, + text: "hello", + }, + ], + text: "hello", + }, + originalText: "hello", + replacementText: "world", + }); + + expect(output).to.deep.equal({ + elements: [ + { + elements: [ + { + elements: [ + { + text: "world", + type: "text", + }, + ], + name: "w:t", + }, + ], + name: "w:r", + }, + ], + name: "w:p", + }); + }); + }); +}); diff --git a/src/patcher/relationship-manager.spec.ts b/src/patcher/relationship-manager.spec.ts new file mode 100644 index 0000000000..9161ed1dec --- /dev/null +++ b/src/patcher/relationship-manager.spec.ts @@ -0,0 +1,61 @@ +import { TargetModeType } from "@file/relationships/relationship/relationship"; +import { expect } from "chai"; + +import { appendRelationship, getNextRelationshipIndex } from "./relationship-manager"; + +describe("relationship-manager", () => { + describe("getNextRelationshipIndex", () => { + it("should get next relationship index", () => { + const output = getNextRelationshipIndex({ + elements: [ + { + type: "element", + name: "Relationships", + elements: [ + { type: "element", attributes: { Id: "rId1" }, name: "Relationship" }, + { type: "element", attributes: { Id: "rId1" }, name: "Relationship" }, + ], + }, + ], + }); + expect(output).to.deep.equal(2); + }); + }); + + describe("appendRelationship", () => { + it("should append a relationship", () => { + const output = appendRelationship( + { + elements: [ + { + type: "element", + name: "Relationships", + elements: [ + { type: "element", attributes: { Id: "rId1" }, name: "Relationship" }, + { type: "element", attributes: { Id: "rId1" }, name: "Relationship" }, + ], + }, + ], + }, + 1, + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image", + "test", + TargetModeType.EXTERNAL, + ); + expect(output).to.deep.equal([ + { type: "element", attributes: { Id: "rId1" }, name: "Relationship" }, + { type: "element", attributes: { Id: "rId1" }, name: "Relationship" }, + { + attributes: { + Id: "rId1", + Type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image", + TargetMode: TargetModeType.EXTERNAL, + Target: "test", + }, + name: "Relationship", + type: "element", + }, + ]); + }); + }); +}); diff --git a/src/patcher/relationship-manager.ts b/src/patcher/relationship-manager.ts index 35eaab8e71..3779500ffe 100644 --- a/src/patcher/relationship-manager.ts +++ b/src/patcher/relationship-manager.ts @@ -21,7 +21,7 @@ export const appendRelationship = ( type: RelationshipType, target: string, targetMode?: TargetModeType, -): void => { +): readonly Element[] => { const relationshipElements = getFirstLevelElements(relationships, "Relationships"); // eslint-disable-next-line functional/immutable-data relationshipElements.push({ @@ -34,4 +34,6 @@ export const appendRelationship = ( name: "Relationship", type: "element", }); + + return relationshipElements; }; diff --git a/src/patcher/replacer.spec.ts b/src/patcher/replacer.spec.ts new file mode 100644 index 0000000000..af747a3f4a --- /dev/null +++ b/src/patcher/replacer.spec.ts @@ -0,0 +1,153 @@ +import { IViewWrapper } from "@file/document-wrapper"; +import { File } from "@file/file"; +import { TextRun } from "@file/paragraph"; +import { IContext } from "@file/xml-components"; +import { expect } from "chai"; +import * as sinon from "sinon"; + +import { PatchType } from "./from-docx"; + +import { replacer } from "./replacer"; + +const MOCK_JSON = { + elements: [ + { + type: "element", + name: "w:hdr", + attributes: { + "xmlns:wpc": "http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas", + "xmlns:cx": "http://schemas.microsoft.com/office/drawing/2014/chartex", + "xmlns:cx1": "http://schemas.microsoft.com/office/drawing/2015/9/8/chartex", + "xmlns:cx2": "http://schemas.microsoft.com/office/drawing/2015/10/21/chartex", + "xmlns:cx3": "http://schemas.microsoft.com/office/drawing/2016/5/9/chartex", + "xmlns:cx4": "http://schemas.microsoft.com/office/drawing/2016/5/10/chartex", + "xmlns:cx5": "http://schemas.microsoft.com/office/drawing/2016/5/11/chartex", + "xmlns:cx6": "http://schemas.microsoft.com/office/drawing/2016/5/12/chartex", + "xmlns:cx7": "http://schemas.microsoft.com/office/drawing/2016/5/13/chartex", + "xmlns:cx8": "http://schemas.microsoft.com/office/drawing/2016/5/14/chartex", + "xmlns:mc": "http://schemas.openxmlformats.org/markup-compatibility/2006", + "xmlns:aink": "http://schemas.microsoft.com/office/drawing/2016/ink", + "xmlns:am3d": "http://schemas.microsoft.com/office/drawing/2017/model3d", + "xmlns:o": "urn:schemas-microsoft-com:office:office", + "xmlns:oel": "http://schemas.microsoft.com/office/2019/extlst", + "xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships", + "xmlns:m": "http://schemas.openxmlformats.org/officeDocument/2006/math", + "xmlns:v": "urn:schemas-microsoft-com:vml", + "xmlns:wp14": "http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing", + "xmlns:wp": "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing", + "xmlns:w10": "urn:schemas-microsoft-com:office:word", + "xmlns:w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main", + "xmlns:w14": "http://schemas.microsoft.com/office/word/2010/wordml", + "xmlns:w15": "http://schemas.microsoft.com/office/word/2012/wordml", + "xmlns:w16cex": "http://schemas.microsoft.com/office/word/2018/wordml/cex", + "xmlns:w16cid": "http://schemas.microsoft.com/office/word/2016/wordml/cid", + "xmlns:w16": "http://schemas.microsoft.com/office/word/2018/wordml", + "xmlns:w16sdtdh": "http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash", + "xmlns:w16se": "http://schemas.microsoft.com/office/word/2015/wordml/symex", + "xmlns:wpg": "http://schemas.microsoft.com/office/word/2010/wordprocessingGroup", + "xmlns:wpi": "http://schemas.microsoft.com/office/word/2010/wordprocessingInk", + "xmlns:wne": "http://schemas.microsoft.com/office/word/2006/wordml", + "xmlns:wps": "http://schemas.microsoft.com/office/word/2010/wordprocessingShape", + }, + elements: [ + { + type: "element", + name: "w:p", + attributes: { "w14:paraId": "3BE1A671", "w14:textId": "74E856C4", "w:rsidR": "000D38A7", "w:rsidRDefault": "000D38A7" }, + elements: [ + { + type: "element", + name: "w:pPr", + elements: [{ type: "element", name: "w:pStyle", attributes: { "w:val": "Header" } }], + }, + { + type: "element", + name: "w:r", + elements: [{ type: "element", name: "w:t", elements: [{ type: "text", text: "This is a {{head" }] }], + }, + { + type: "element", + name: "w:r", + attributes: { "w:rsidR": "004A3A99" }, + elements: [{ type: "element", name: "w:t", elements: [{ type: "text", text: "er" }] }], + }, + { + type: "element", + name: "w:r", + elements: [ + { type: "element", name: "w:t", elements: [{ type: "text", text: "_adjective}} don’t you think?" }] }, + ], + }, + ], + }, + ], + }, + ], +}; + +describe("replacer", () => { + describe("replacer", () => { + it("should return the same object if nothing is added", () => { + const output = replacer( + { + elements: [], + }, + { + type: PatchType.PARAGRAPH, + children: [], + }, + "hello", + [], + sinon.mock() as unknown as IContext, + ); + + expect(output).to.deep.equal({ + elements: [], + }); + }); + + it("should return the same object if nothing is added", () => { + const output = replacer( + MOCK_JSON, + { + type: PatchType.PARAGRAPH, + children: [new TextRun("Delightful Header")], + }, + "{{header_adjective}}", + [ + { + text: "This is a {{header_adjective}} don’t you think?", + runs: [ + { + text: "This is a {{head", + parts: [{ text: "This is a {{head", index: 0, start: 0, end: 15 }], + index: 1, + start: 0, + end: 15, + }, + { text: "er", parts: [{ text: "er", index: 0, start: 16, end: 17 }], index: 2, start: 16, end: 17 }, + { + text: "_adjective}} don’t you think?", + parts: [{ text: "_adjective}} don’t you think?", index: 0, start: 18, end: 46 }], + index: 3, + start: 18, + end: 46, + }, + ], + index: 0, + path: [0, 0, 0], + }, + ], + { + file: {} as unknown as File, + viewWrapper: { + Relationships: {}, + } as unknown as IViewWrapper, + stack: [], + }, + ); + + expect(JSON.stringify(output)).to.contain("Delightful Header"); + }); + }); +}); diff --git a/src/patcher/run-renderer.spec.ts b/src/patcher/run-renderer.spec.ts new file mode 100644 index 0000000000..949f4a7635 --- /dev/null +++ b/src/patcher/run-renderer.spec.ts @@ -0,0 +1,96 @@ +import { expect } from "chai"; +import { renderParagraphNode } from "./run-renderer"; + +describe("run-renderer", () => { + describe("renderParagraphNode", () => { + it("should return a rendered paragraph node if theres no elements", () => { + const output = renderParagraphNode({ element: { name: "w:p" }, index: 0, parent: undefined }); + expect(output).to.deep.equal({ + index: -1, + path: [], + runs: [], + text: "", + }); + }); + + it("should return a rendered paragraph node if there are elements", () => { + const output = renderParagraphNode({ + element: { + name: "w:p", + elements: [ + { + name: "w:r", + elements: [ + { + name: "w:t", + elements: [ + { + type: "text", + text: "hello", + }, + ], + }, + ], + }, + ], + }, + index: 0, + parent: undefined, + }); + expect(output).to.deep.equal({ + index: 0, + path: [0], + runs: [ + { + end: 4, + index: 0, + parts: [ + { + end: 4, + index: 0, + start: 0, + text: "hello", + }, + ], + start: 0, + text: "hello", + }, + ], + text: "hello", + }); + }); + + it("should throw an error if the element is not a paragraph", () => { + expect(() => renderParagraphNode({ element: { name: "w:r" }, index: 0, parent: undefined })).to.throw(); + }); + + it("should return blank defaults if run is empty", () => { + const output = renderParagraphNode({ + element: { + name: "w:p", + elements: [ + { + name: "w:r", + }, + ], + }, + index: 0, + parent: undefined, + }); + expect(output).to.deep.equal({ + index: 0, + path: [0], + runs: [ + { + end: 0, + index: -1, + parts: [], + start: 0, + text: "", + }, + ], + text: "", + }); + }); + }); +}); diff --git a/src/patcher/run-renderer.ts b/src/patcher/run-renderer.ts index 344898c10a..262d6f265a 100644 --- a/src/patcher/run-renderer.ts +++ b/src/patcher/run-renderer.ts @@ -64,10 +64,6 @@ export const renderParagraphNode = (node: ElementWrapper): IRenderedParagraphNod }; const renderRunNode = (node: Element, index: number, currentRunStringIndex: number): IRenderedRunNode => { - if (node.name !== "w:r") { - throw new Error(`Invalid node type: ${node.name}`); - } - if (!node.elements) { return { text: "", diff --git a/src/patcher/traverser.spec.ts b/src/patcher/traverser.spec.ts new file mode 100644 index 0000000000..65ec717e3f --- /dev/null +++ b/src/patcher/traverser.spec.ts @@ -0,0 +1,600 @@ +import { expect } from "chai"; + +import { findLocationOfText } from "./traverser"; + +const MOCK_JSON = { + elements: [ + { + type: "element", + name: "w:document", + attributes: { + "xmlns:wpc": "http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas", + "xmlns:cx": "http://schemas.microsoft.com/office/drawing/2014/chartex", + "xmlns:cx1": "http://schemas.microsoft.com/office/drawing/2015/9/8/chartex", + "xmlns:cx2": "http://schemas.microsoft.com/office/drawing/2015/10/21/chartex", + "xmlns:cx3": "http://schemas.microsoft.com/office/drawing/2016/5/9/chartex", + "xmlns:cx4": "http://schemas.microsoft.com/office/drawing/2016/5/10/chartex", + "xmlns:cx5": "http://schemas.microsoft.com/office/drawing/2016/5/11/chartex", + "xmlns:cx6": "http://schemas.microsoft.com/office/drawing/2016/5/12/chartex", + "xmlns:cx7": "http://schemas.microsoft.com/office/drawing/2016/5/13/chartex", + "xmlns:cx8": "http://schemas.microsoft.com/office/drawing/2016/5/14/chartex", + "xmlns:mc": "http://schemas.openxmlformats.org/markup-compatibility/2006", + "xmlns:aink": "http://schemas.microsoft.com/office/drawing/2016/ink", + "xmlns:am3d": "http://schemas.microsoft.com/office/drawing/2017/model3d", + "xmlns:o": "urn:schemas-microsoft-com:office:office", + "xmlns:oel": "http://schemas.microsoft.com/office/2019/extlst", + "xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships", + "xmlns:m": "http://schemas.openxmlformats.org/officeDocument/2006/math", + "xmlns:v": "urn:schemas-microsoft-com:vml", + "xmlns:wp14": "http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing", + "xmlns:wp": "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing", + "xmlns:w10": "urn:schemas-microsoft-com:office:word", + "xmlns:w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main", + "xmlns:w14": "http://schemas.microsoft.com/office/word/2010/wordml", + "xmlns:w15": "http://schemas.microsoft.com/office/word/2012/wordml", + "xmlns:w16cex": "http://schemas.microsoft.com/office/word/2018/wordml/cex", + "xmlns:w16cid": "http://schemas.microsoft.com/office/word/2016/wordml/cid", + "xmlns:w16": "http://schemas.microsoft.com/office/word/2018/wordml", + "xmlns:w16sdtdh": "http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash", + "xmlns:w16se": "http://schemas.microsoft.com/office/word/2015/wordml/symex", + "xmlns:wpg": "http://schemas.microsoft.com/office/word/2010/wordprocessingGroup", + "xmlns:wpi": "http://schemas.microsoft.com/office/word/2010/wordprocessingInk", + "xmlns:wne": "http://schemas.microsoft.com/office/word/2006/wordml", + "xmlns:wps": "http://schemas.microsoft.com/office/word/2010/wordprocessingShape", + "mc:Ignorable": "w14 w15 w16se w16cid w16 w16cex w16sdtdh wp14", + }, + elements: [ + { + type: "element", + name: "w:body", + elements: [ + { + type: "element", + name: "w:p", + attributes: { + "w14:paraId": "2499FE9F", + "w14:textId": "0A3D130F", + "w:rsidR": "00B51233", + "w:rsidRDefault": "007B52ED", + "w:rsidP": "007B52ED", + }, + elements: [ + { + type: "element", + name: "w:pPr", + elements: [{ type: "element", name: "w:pStyle", attributes: { "w:val": "Title" } }], + }, + { + type: "element", + name: "w:r", + elements: [{ type: "element", name: "w:t", elements: [{ type: "text", text: "Hello World" }] }], + }, + ], + }, + { + type: "element", + name: "w:p", + attributes: { + "w14:paraId": "6410D9A0", + "w14:textId": "7579AB49", + "w:rsidR": "007B52ED", + "w:rsidRDefault": "007B52ED", + }, + }, + { + type: "element", + name: "w:p", + attributes: { + "w14:paraId": "57ACF964", + "w14:textId": "315D7A05", + "w:rsidR": "007B52ED", + "w:rsidRDefault": "007B52ED", + }, + elements: [ + { + type: "element", + name: "w:r", + elements: [{ type: "element", name: "w:t", elements: [{ type: "text", text: "Hello {{name}}," }] }], + }, + { + type: "element", + name: "w:r", + attributes: { "w:rsidR": "008126CB" }, + elements: [ + { + type: "element", + name: "w:t", + attributes: { "xml:space": "preserve" }, + elements: [{ type: "text", text: " how are you?" }], + }, + ], + }, + ], + }, + { + type: "element", + name: "w:p", + attributes: { + "w14:paraId": "38C7DF4A", + "w14:textId": "66CDEC9A", + "w:rsidR": "007B52ED", + "w:rsidRDefault": "007B52ED", + }, + }, + { + type: "element", + name: "w:p", + attributes: { + "w14:paraId": "04FABE2B", + "w14:textId": "3DACA001", + "w:rsidR": "007B52ED", + "w:rsidRDefault": "007B52ED", + }, + elements: [ + { + type: "element", + name: "w:r", + elements: [ + { type: "element", name: "w:t", elements: [{ type: "text", text: "{{paragraph_replace}}" }] }, + ], + }, + ], + }, + { + type: "element", + name: "w:p", + attributes: { + "w14:paraId": "7AD7975D", + "w14:textId": "77777777", + "w:rsidR": "00EF161F", + "w:rsidRDefault": "00EF161F", + }, + }, + { + type: "element", + name: "w:p", + attributes: { + "w14:paraId": "3BD6D75A", + "w14:textId": "19AE3121", + "w:rsidR": "00EF161F", + "w:rsidRDefault": "00EF161F", + }, + elements: [ + { + type: "element", + name: "w:r", + elements: [{ type: "element", name: "w:t", elements: [{ type: "text", text: "{{table}}" }] }], + }, + ], + }, + { + type: "element", + name: "w:p", + attributes: { + "w14:paraId": "76023962", + "w14:textId": "4E606AB9", + "w:rsidR": "007B52ED", + "w:rsidRDefault": "007B52ED", + }, + }, + { + type: "element", + name: "w:tbl", + elements: [ + { + type: "element", + name: "w:tblPr", + elements: [ + { type: "element", name: "w:tblStyle", attributes: { "w:val": "TableGrid" } }, + { type: "element", name: "w:tblW", attributes: { "w:w": "0", "w:type": "auto" } }, + { + type: "element", + name: "w:tblLook", + attributes: { + "w:val": "04A0", + "w:firstRow": "1", + "w:lastRow": "0", + "w:firstColumn": "1", + "w:lastColumn": "0", + "w:noHBand": "0", + "w:noVBand": "1", + }, + }, + ], + }, + { + type: "element", + name: "w:tblGrid", + elements: [ + { type: "element", name: "w:gridCol", attributes: { "w:w": "3003" } }, + { type: "element", name: "w:gridCol", attributes: { "w:w": "3003" } }, + { type: "element", name: "w:gridCol", attributes: { "w:w": "3004" } }, + ], + }, + { + type: "element", + name: "w:tr", + attributes: { + "w:rsidR": "00EF161F", + "w14:paraId": "1DEC5955", + "w14:textId": "77777777", + "w:rsidTr": "00EF161F", + }, + elements: [ + { + type: "element", + name: "w:tc", + elements: [ + { + type: "element", + name: "w:tcPr", + elements: [ + { type: "element", name: "w:tcW", attributes: { "w:w": "3003", "w:type": "dxa" } }, + ], + }, + { + type: "element", + name: "w:p", + attributes: { + "w14:paraId": "54DA5587", + "w14:textId": "625BAC60", + "w:rsidR": "00EF161F", + "w:rsidRDefault": "00EF161F", + }, + elements: [ + { + type: "element", + name: "w:r", + elements: [ + { + type: "element", + name: "w:t", + elements: [{ type: "text", text: "{{table_heading_1}}" }], + }, + ], + }, + ], + }, + ], + }, + { + type: "element", + name: "w:tc", + elements: [ + { + type: "element", + name: "w:tcPr", + elements: [ + { type: "element", name: "w:tcW", attributes: { "w:w": "3003", "w:type": "dxa" } }, + ], + }, + { + type: "element", + name: "w:p", + attributes: { + "w14:paraId": "57100910", + "w14:textId": "71FD5616", + "w:rsidR": "00EF161F", + "w:rsidRDefault": "00EF161F", + }, + }, + ], + }, + { + type: "element", + name: "w:tc", + elements: [ + { + type: "element", + name: "w:tcPr", + elements: [ + { type: "element", name: "w:tcW", attributes: { "w:w": "3004", "w:type": "dxa" } }, + ], + }, + { + type: "element", + name: "w:p", + attributes: { + "w14:paraId": "1D388FAB", + "w14:textId": "77777777", + "w:rsidR": "00EF161F", + "w:rsidRDefault": "00EF161F", + }, + }, + ], + }, + ], + }, + { + type: "element", + name: "w:tr", + attributes: { + "w:rsidR": "00EF161F", + "w14:paraId": "0F53D2DC", + "w14:textId": "77777777", + "w:rsidTr": "00EF161F", + }, + elements: [ + { + type: "element", + name: "w:tc", + elements: [ + { + type: "element", + name: "w:tcPr", + elements: [ + { type: "element", name: "w:tcW", attributes: { "w:w": "3003", "w:type": "dxa" } }, + ], + }, + { + type: "element", + name: "w:p", + attributes: { + "w14:paraId": "0F2BCCED", + "w14:textId": "3C3B6706", + "w:rsidR": "00EF161F", + "w:rsidRDefault": "00EF161F", + }, + elements: [ + { + type: "element", + name: "w:r", + elements: [ + { + type: "element", + name: "w:t", + elements: [{ type: "text", text: "Item: {{item_1}}" }], + }, + ], + }, + ], + }, + ], + }, + { + type: "element", + name: "w:tc", + elements: [ + { + type: "element", + name: "w:tcPr", + elements: [ + { type: "element", name: "w:tcW", attributes: { "w:w": "3003", "w:type": "dxa" } }, + ], + }, + { + type: "element", + name: "w:p", + attributes: { + "w14:paraId": "1E6158AC", + "w14:textId": "77777777", + "w:rsidR": "00EF161F", + "w:rsidRDefault": "00EF161F", + }, + }, + ], + }, + { + type: "element", + name: "w:tc", + elements: [ + { + type: "element", + name: "w:tcPr", + elements: [ + { type: "element", name: "w:tcW", attributes: { "w:w": "3004", "w:type": "dxa" } }, + ], + }, + { + type: "element", + name: "w:p", + attributes: { + "w14:paraId": "17937748", + "w14:textId": "77777777", + "w:rsidR": "00EF161F", + "w:rsidRDefault": "00EF161F", + }, + }, + ], + }, + ], + }, + { + type: "element", + name: "w:tr", + attributes: { + "w:rsidR": "00EF161F", + "w14:paraId": "781DAC1A", + "w14:textId": "77777777", + "w:rsidTr": "00EF161F", + }, + elements: [ + { + type: "element", + name: "w:tc", + elements: [ + { + type: "element", + name: "w:tcPr", + elements: [ + { type: "element", name: "w:tcW", attributes: { "w:w": "3003", "w:type": "dxa" } }, + ], + }, + { + type: "element", + name: "w:p", + attributes: { + "w14:paraId": "1DCD0343", + "w14:textId": "77777777", + "w:rsidR": "00EF161F", + "w:rsidRDefault": "00EF161F", + }, + }, + ], + }, + { + type: "element", + name: "w:tc", + elements: [ + { + type: "element", + name: "w:tcPr", + elements: [ + { type: "element", name: "w:tcW", attributes: { "w:w": "3003", "w:type": "dxa" } }, + ], + }, + { + type: "element", + name: "w:p", + attributes: { + "w14:paraId": "5D02E3CD", + "w14:textId": "77777777", + "w:rsidR": "00EF161F", + "w:rsidRDefault": "00EF161F", + }, + }, + ], + }, + { + type: "element", + name: "w:tc", + elements: [ + { + type: "element", + name: "w:tcPr", + elements: [ + { type: "element", name: "w:tcW", attributes: { "w:w": "3004", "w:type": "dxa" } }, + ], + }, + { + type: "element", + name: "w:p", + attributes: { + "w14:paraId": "52EA0DBB", + "w14:textId": "77777777", + "w:rsidR": "00EF161F", + "w:rsidRDefault": "00EF161F", + }, + }, + ], + }, + ], + }, + ], + }, + { + type: "element", + name: "w:p", + attributes: { + "w14:paraId": "47CD1FBC", + "w14:textId": "23474CBC", + "w:rsidR": "007B52ED", + "w:rsidRDefault": "007B52ED", + }, + }, + { + type: "element", + name: "w:p", + attributes: { + "w14:paraId": "0ACCEE90", + "w14:textId": "67907499", + "w:rsidR": "00EF161F", + "w:rsidRDefault": "0077578F", + }, + elements: [ + { + type: "element", + name: "w:r", + elements: [{ type: "element", name: "w:t", elements: [{ type: "text", text: "{{image_test}}" }] }], + }, + ], + }, + { + type: "element", + name: "w:p", + attributes: { + "w14:paraId": "23FA9862", + "w14:textId": "77777777", + "w:rsidR": "0077578F", + "w:rsidRDefault": "0077578F", + }, + }, + { + type: "element", + name: "w:p", + attributes: { + "w14:paraId": "01578F2F", + "w14:textId": "3BDC6C85", + "w:rsidR": "007B52ED", + "w:rsidRDefault": "007B52ED", + }, + elements: [ + { + type: "element", + name: "w:r", + elements: [{ type: "element", name: "w:t", elements: [{ type: "text", text: "Thank you" }] }], + }, + ], + }, + { + type: "element", + name: "w:sectPr", + attributes: { "w:rsidR": "007B52ED", "w:rsidSect": "0072043F" }, + elements: [ + { type: "element", name: "w:headerReference", attributes: { "w:type": "default", "r:id": "rId6" } }, + { type: "element", name: "w:footerReference", attributes: { "w:type": "default", "r:id": "rId7" } }, + { type: "element", name: "w:pgSz", attributes: { "w:w": "11900", "w:h": "16840" } }, + { + type: "element", + name: "w:pgMar", + attributes: { + "w:top": "1440", + "w:right": "1440", + "w:bottom": "1440", + "w:left": "1440", + "w:header": "708", + "w:footer": "708", + "w:gutter": "0", + }, + }, + { type: "element", name: "w:cols", attributes: { "w:space": "708" } }, + { type: "element", name: "w:docGrid", attributes: { "w:linePitch": "360" } }, + ], + }, + ], + }, + ], + }, + ], +}; + +describe("traverser", () => { + describe("findLocationOfText", () => { + it("should find the location of text", () => { + const output = findLocationOfText(MOCK_JSON, "{{table_heading_1}}"); + expect(output).to.deep.equal([ + { + index: 1, + path: [0, 0, 0, 8, 2, 0, 1], + runs: [ + { + end: 18, + index: 0, + parts: [ + { + end: 18, + index: 0, + start: 0, + text: "{{table_heading_1}}", + }, + ], + start: 0, + text: "{{table_heading_1}}", + }, + ], + text: "{{table_heading_1}}", + }, + ]); + }); + }); +}); diff --git a/src/patcher/util.spec.ts b/src/patcher/util.spec.ts index 6482298754..c303b4ffe6 100644 --- a/src/patcher/util.spec.ts +++ b/src/patcher/util.spec.ts @@ -32,14 +32,18 @@ describe("util", () => { describe("getFirstLevelElements", () => { it("should return an empty array if no elements are found", () => { - const output = toJson(""); - const elements = getFirstLevelElements(output, "Relationships"); + const elements = getFirstLevelElements( + { elements: [{ type: "element", name: "Relationships", elements: [] }] }, + "Relationships", + ); expect(elements).to.deep.equal([]); }); it("should return an array if elements are found", () => { - const output = toJson(""); - const elements = getFirstLevelElements(output, "Relationships"); + const elements = getFirstLevelElements( + { elements: [{ type: "element", name: "Relationships", elements: [{ type: "element", name: "Relationship" }] }] }, + "Relationships", + ); expect(elements).to.deep.equal([{ type: "element", name: "Relationship" }]); }); });