const { expectToThrow, createDoc, createDocV4, shouldBeSame, expect, resolveSoon, createXmlTemplaterDocxNoRender, cleanRecursive, } = require("./utils"); const printy = require("./printy"); const { cloneDeep } = require("lodash"); const { expectedPrintedPostParsed, rawXMLValue } = require("./data-fixtures"); const angularParser = require("./angular-parser"); const Errors = require("../errors.js"); describe("Simple templating", function () { describe("text templating", function () { it("should change values with template data", function () { const tags = { first_name: "Hipp", last_name: "Edgar", phone: "0652455478", description: "New Website", }; const doc = createDocV4("tag-example.docx"); doc.setData(tags); doc.render(); expect(doc.getFullText()).to.be.equal("Edgar Hipp"); expect(doc.getFullText("word/header1.xml")).to.be.equal( "Edgar Hipp0652455478New Website" ); expect(doc.getFullText("word/footer1.xml")).to.be.equal( "EdgarHipp0652455478" ); shouldBeSame({ doc, expectedName: "expected-tag-example.docx" }); }); }); it("should replace custom properties text", function () { const doc = createDoc("properties.docx"); let app = doc.getZip().files["docProps/app.xml"].asText(); let core = doc.getZip().files["docProps/core.xml"].asText(); expect(app).to.contain("{tag1}"); expect(core).to.contain("{tag1}"); expect(core).to.contain("{tag2}"); expect(core).to.contain("{tag3}"); expect(app).to.contain("{tag4}"); expect(app).to.contain("{tag5}"); expect(core).to.contain("{tag6}"); expect(core).to.contain("{tag7}"); expect(core).to.contain("{tag8}"); expect(app).to.contain("{tag9}"); doc .setData({ tag1: "resolvedvalue1", tag2: "resolvedvalue2", tag3: "resolvedvalue3", tag4: "resolvedvalue4", tag5: "resolvedvalue5", tag6: "resolvedvalue6", tag7: "resolvedvalue7", tag8: "resolvedvalue8", tag9: "resolvedvalue9", }) .render(); app = doc.getZip().files["docProps/app.xml"].asText(); core = doc.getZip().files["docProps/core.xml"].asText(); expect(app).to.contain("resolvedvalue1"); expect(core).to.contain("resolvedvalue1"); expect(core).to.contain("resolvedvalue2"); expect(core).to.contain("resolvedvalue3"); expect(app).to.contain("resolvedvalue4"); expect(app).to.contain("resolvedvalue5"); expect(core).to.contain("resolvedvalue6"); expect(core).to.contain("resolvedvalue7"); expect(core).to.contain("resolvedvalue8"); expect(app).to.contain("resolvedvalue9"); }); }); describe("Spacing/Linebreaks", function () { it("should show spaces with linebreak option", function () { const doc = createDoc("tag-multiline.docx"); doc.setData({ description: `hello there deep indentation goes here end`, }); doc.setOptions({ linebreaks: true }); doc.render(); shouldBeSame({ doc, expectedName: "expected-multiline-indent.docx" }); }); it("should be possible to have linebreaks if setting the option", function () { const doc = createDoc("tag-multiline.docx"); doc.setData({ description: "The description,\nmultiline", }); doc.setOptions({ linebreaks: true }); doc.render(); shouldBeSame({ doc, expectedName: "expected-multiline.docx" }); }); it("should work with linebreaks without changing the style", function () { const doc = createDoc("multi-tags.docx"); doc.setData({ test: "The tag1,\nmultiline\nfoobaz", test2: "The tag2,\nmultiline\nfoobar", }); doc.setOptions({ linebreaks: true }); doc.render(); shouldBeSame({ doc, expectedName: "expected-two-multiline.docx" }); }); it("should be possible to have linebreaks if setting the option", function () { const doc = createDoc("tag-multiline.pptx"); doc.setData({ description: "The description,\nmultiline", }); doc.setOptions({ linebreaks: true }); doc.render(); shouldBeSame({ doc, expectedName: "expected-multiline.pptx" }); }); it("should not fail when using linebreaks and tagvalue not a string", function () { const doc = createDoc("tag-multiline.pptx"); doc.setData({ description: true, }); doc.setOptions({ linebreaks: true }); doc.render(); shouldBeSame({ doc, expectedName: "expected-regression-multiline.pptx" }); }); }); describe("Assignment", function () { it("should be possible to assign a value from the template", function () { const doc = createDoc("assignment.docx"); doc.setData({ first_name: "Jane", last_name: "Doe", }); doc.setOptions({ paragraphLoop: true, parser: angularParser, }); doc.render(); shouldBeSame({ doc, expectedName: "expected-assignment.docx" }); }); }); describe("Docm/Pptm generation", function () { it("should work with docm", function () { const tags = { user: "John", }; const doc = createDoc("input.docm"); doc.setData(tags); doc.render(); shouldBeSame({ doc, expectedName: "expected-docm.docx" }); }); it("should work with pptm", function () { const tags = { user: "John", }; const doc = createDoc("input.pptm"); doc.setData(tags); doc.render(); shouldBeSame({ doc, expectedName: "expected-pptm.pptx" }); }); }); describe("Dotm/dotx generation", function () { it("should work with dotx", function () { const tags = { user: "John", }; const doc = createDoc("input.dotx"); doc.setData(tags); doc.render(); shouldBeSame({ doc, expectedName: "expected-dotx.docx" }); }); it("should work with dotm", function () { const tags = { user: "John", }; const doc = createDoc("input.dotm"); doc.setData(tags); doc.render(); shouldBeSame({ doc, expectedName: "expected-dotm.docx" }); }); }); describe("Pptx generation", function () { it("should work with title", function () { const doc = createDoc("title-example.pptx"); let con = doc.getZip().files["docProps/app.xml"].asText(); expect(con).not.to.contain("Edgar"); doc.setData({ name: "Edgar" }).render(); con = doc.getZip().files["docProps/app.xml"].asText(); expect(con).to.contain("Edgar"); }); it("should work with simple pptx", function () { const doc = createDoc("simple-example.pptx"); doc.setData({ name: "Edgar" }).render(); expect(doc.getFullText()).to.be.equal("Hello Edgar"); }); it("should work with table pptx", function () { const doc = createDoc("table-example.pptx"); doc .setData({ users: [ { msg: "hello", name: "mary" }, { msg: "hello", name: "john" }, ], }) .render(); shouldBeSame({ doc, expectedName: "expected-table-example.pptx" }); }); it("should work with loop pptx", function () { const doc = createDoc("loop-example.pptx"); doc.setData({ users: [{ name: "Doe" }, { name: "John" }] }).render(); expect(doc.getFullText()).to.be.equal(" Doe John "); shouldBeSame({ doc, expectedName: "expected-loop-example.pptx" }); }); it("should work with simple raw pptx", function () { const doc = createDoc("raw-xml-example.pptx"); let scope, meta, tag; let calls = 0; doc.setOptions({ parser: (t) => { tag = t; return { get: (s, m) => { scope = s; meta = m.meta; calls++; return scope[tag]; }, }; }, }); doc.setData({ raw: rawXMLValue }).render(); expect(calls).to.equal(1); expect(scope.raw).to.be.a("string"); expect(meta).to.be.an("object"); expect(meta.part).to.be.an("object"); expect(meta.part.expanded).to.be.an("array"); expect(doc.getFullText()).to.be.equal("Hello World"); shouldBeSame({ doc, expectedName: "expected-raw-xml-example.pptx" }); }); it("should work with simple raw pptx async", function () { const doc = createDoc("raw-xml-example.pptx"); let scope, meta, tag; let calls = 0; doc.setOptions({ parser: (t) => { tag = t; return { get: (s, m) => { scope = s; meta = m.meta; calls++; return scope[tag]; }, }; }, }); doc.compile(); return doc.resolveData({ raw: rawXMLValue }).then(function () { doc.render(); expect(calls).to.equal(1); expect(scope.raw).to.be.a("string"); expect(meta).to.be.an("object"); expect(meta.part).to.be.an("object"); expect(meta.part.expanded).to.be.an("array"); expect(doc.getFullText()).to.be.equal("Hello World"); shouldBeSame({ doc, expectedName: "expected-raw-xml-example.pptx" }); }); }); }); describe("Table", function () { it("should work with selfclosing tag inside table with paragraphLoop", function () { const tags = { a: [ { b: { c: "Foo", d: "Hello ", }, }, { b: { c: "Foo", d: "Hello ", }, }, ], }; const doc = createDoc("loop-valid.docx"); doc.setData(tags); doc.setOptions({ paragraphLoop: true }); doc.render(); shouldBeSame({ doc, expectedName: "expected-loop-valid.docx" }); }); it("should work with tables", function () { const tags = { clients: [ { first_name: "John", last_name: "Doe", phone: "+33647874513" }, { first_name: "Jane", last_name: "Doe", phone: "+33454540124" }, { first_name: "Phil", last_name: "Kiel", phone: "+44578451245" }, { first_name: "Dave", last_name: "Sto", phone: "+44548787984" }, ], }; const doc = createDoc("tag-intelligent-loop-table.docx"); doc.setData(tags); doc.render(); const expectedText = "JohnDoe+33647874513JaneDoe+33454540124PhilKiel+44578451245DaveSto+44548787984"; const text = doc.getFullText(); expect(text).to.be.equal(expectedText); shouldBeSame({ doc, expectedName: "expected-tag-intelligent-loop-table.docx", }); }); it("should work with simple table", function () { const doc = createDoc("table-complex2-example.docx"); doc.setData({ table1: [ { t1data1: "t1-1row-data1", t1data2: "t1-1row-data2", t1data3: "t1-1row-data3", t1data4: "t1-1row-data4", }, { t1data1: "t1-2row-data1", t1data2: "t1-2row-data2", t1data3: "t1-2row-data3", t1data4: "t1-2row-data4", }, { t1data1: "t1-3row-data1", t1data2: "t1-3row-data2", t1data3: "t1-3row-data3", t1data4: "t1-3row-data4", }, ], t1total1: "t1total1-data", t1total2: "t1total2-data", t1total3: "t1total3-data", }); doc.render(); const fullText = doc.getFullText(); expect(fullText).to.be.equal( "TABLE1COLUMN1COLUMN2COLUMN3COLUMN4t1-1row-data1t1-1row-data2t1-1row-data3t1-1row-data4t1-2row-data1t1-2row-data2t1-2row-data3t1-2row-data4t1-3row-data1t1-3row-data2t1-3row-data3t1-3row-data4TOTALt1total1-datat1total2-datat1total3-data" ); }); it("should work with more complex table", function () { const doc = createDoc("table-complex-example.docx"); doc.setData({ table2: [ { t2data1: "t2-1row-data1", t2data2: "t2-1row-data2", t2data3: "t2-1row-data3", t2data4: "t2-1row-data4", }, { t2data1: "t2-2row-data1", t2data2: "t2-2row-data2", t2data3: "t2-2row-data3", t2data4: "t2-2row-data4", }, ], t1total1: "t1total1-data", t1total2: "t1total2-data", t1total3: "t1total3-data", t2total1: "t2total1-data", t2total2: "t2total2-data", t2total3: "t2total3-data", }); doc.render(); const fullText = doc.getFullText(); expect(fullText).to.be.equal( "TABLE1COLUMN1COLUMN2COLUMN3COLUMN4TOTALt1total1-datat1total2-datat1total3-dataTABLE2COLUMN1COLUMN2COLUMN3COLUMN4t2-1row-data1t2-1row-data2t2-1row-data3t2-1row-data4t2-2row-data1t2-2row-data2t2-2row-data3t2-2row-data4TOTALt2total1-datat2total2-datat2total3-data" ); }); it("should work when looping around tables", function () { const doc = createDoc("table-repeat.docx"); doc.setData({ table: [1, 2, 3, 4], }); doc.render(); const fullText = doc.getFullText(); expect(fullText).to.be.equal("1234123412341234"); }); it("should not corrupt table with empty rawxml", function () { const doc = createDoc("table-raw-xml.docx"); doc.setData({}); doc.render(); shouldBeSame({ doc, expectedName: "expected-raw-xml.docx" }); }); it("should not corrupt document with selfclosing w:sdtContent tag", function () { const doc = createDoc("self-closing-w-sdtcontent.docx"); doc.setData({}); doc.render(); shouldBeSame({ doc, expectedName: "expected-self-closing-w-sdtcontent.docx", }); }); it("should not corrupt loop containing section", function () { const doc = createDoc("loop-with-section.docx"); doc.setData({ loop1: [ { loop2: [1, 2], }, { loop2: [], }, { loop2: [3, 4, 5], }, ], }); doc.render(); shouldBeSame({ doc, expectedName: "expected-multi-section.docx" }); }); it("should not corrupt sdtcontent", function () { const doc = createDoc("regression-sdtcontent-paragraph.docx"); doc.setData({ loop: { name: "foo", Id: "bar", }, }); doc.render(); shouldBeSame({ doc, expectedName: "expected-sdtcontent-valid.docx" }); }); it("should not corrupt table with empty rawxml within loop", function () { const doc = createDoc("loops-with-table-raw-xml.docx"); doc.setData({ loop: [ { loop2: [] }, { loop2: {}, raw: "RAW" }, ], }); doc.setOptions({ paragraphLoop: true }); doc.render(); shouldBeSame({ doc, expectedName: "expected-loop-raw-xml.docx" }); }); it("should not corrupt table with empty loop", function () { const doc = createDoc("table-loop.docx"); doc.setData({}); doc.setOptions({ paragraphLoop: true }); doc.render(); shouldBeSame({ doc, expectedName: "expected-empty-table.docx" }); }); }); describe("Dash Loop", function () { it("should work on simple table -> w:tr", function () { const tags = { os: [ { type: "linux", price: "0", reference: "Ubuntu10" }, { type: "DOS", price: "500", reference: "Win7" }, { type: "apple", price: "1200", reference: "MACOSX" }, ], }; const doc = createDoc("tag-dash-loop.docx"); doc.setData(tags); doc.render(); const expectedText = "linux0Ubuntu10DOS500Win7apple1200MACOSX"; const text = doc.getFullText(); expect(text).to.be.equal(expectedText); }); it("should work on simple table -> w:table", function () { const tags = { os: [ { type: "linux", price: "0", reference: "Ubuntu10" }, { type: "DOS", price: "500", reference: "Win7" }, { type: "apple", price: "1200", reference: "MACOSX" }, ], }; const doc = createDoc("tag-dash-loop-table.docx"); doc.setData(tags); doc.render(); const expectedText = "linux0Ubuntu10DOS500Win7apple1200MACOSX"; const text = doc.getFullText(); expect(text).to.be.equal(expectedText); }); it("should work on simple list -> w:p", function () { const tags = { os: [ { type: "linux", price: "0", reference: "Ubuntu10" }, { type: "DOS", price: "500", reference: "Win7" }, { type: "apple", price: "1200", reference: "MACOSX" }, ], }; const doc = createDoc("tag-dash-loop-list.docx"); doc.setData(tags); doc.render(); const expectedText = "linux 0 Ubuntu10 DOS 500 Win7 apple 1200 MACOSX "; const text = doc.getFullText(); expect(text).to.be.equal(expectedText); }); it("should not corrupt document if using empty {-a:p} inside table cell", function () { const doc = createDoc("regression-dash-loop-in-table-cell.pptx"); doc.setData().render(); shouldBeSame({ doc, expectedName: "expected-table-3-cells.pptx" }); }); it("should not corrupt document if using empty {-a:p} inside table cell", function () { const doc = createDoc("regression-dash-loop-in-table-cell.pptx"); doc.setData({ cond: [1, 2, 3] }).render(); shouldBeSame({ doc, expectedName: "expected-table-3-true-cells.pptx" }); }); }); describe("Pagebreaks inside loops", function () { it("should work at beginning of paragraph loop with 3 elements", function () { // Warning : In libreoffice, this is not rendered correctly, use WPS or Word const doc = createDoc("page-break-inside-condition.docx"); doc.setOptions({ paragraphLoop: true }); doc.setData({ cond: [1, 2, 3] }).render(); shouldBeSame({ doc, expectedName: "expected-with-page-break-3-els.docx" }); }); it("should work at beginning of paragraph loop with false", function () { // Warning : In libreoffice, this is not rendered correctly, use WPS or Word const doc = createDoc("page-break-inside-condition.docx"); doc.setOptions({ paragraphLoop: true }); doc.setData({ cond: false }).render(); shouldBeSame({ doc, expectedName: "expected-with-page-break-falsy.docx" }); }); it("should work at beginning of std loop with false", function () { // Warning : In libreoffice, this is not rendered correctly, use WPS or Word const doc = createDoc("page-break-inside-condition.docx"); doc.setData({ cond: false }).render(); shouldBeSame({ doc, expectedName: "expected-page-break-falsy-std-loop.docx", }); }); it("should work at beginning of std loop with 3 elements", function () { // Warning : In libreoffice, this is not rendered correctly, use WPS or Word const doc = createDoc("page-break-inside-condition.docx"); doc.setData({ cond: [1, 2, 3] }).render(); shouldBeSame({ doc, expectedName: "expected-page-break-3-els-std-loop.docx", }); }); it("should work at beginning of std loop with truthy", function () { // Warning : In libreoffice, this is not rendered correctly, use WPS or Word const doc = createDoc("page-break-inside-condition.docx"); doc.setData({ cond: true }).render(); shouldBeSame({ doc, expectedName: "expected-page-break-truthy-std-loop.docx", }); }); it("should work with table inside paragraph loop", function () { const doc = createDoc("pagebreak-table-loop.docx"); doc.setOptions({ paragraphLoop: true }); doc.setData({ loop: [1, 2, 3] }).render(); shouldBeSame({ doc, expectedName: "expected-pagebreak-table-loop.docx", }); }); it("should work at end of std loop", function () { const doc = createDoc("paragraph-loop-with-pagebreak.docx"); doc .setData({ users: [{ name: "Bar" }, { name: "John" }, { name: "Baz" }], }) .render(); shouldBeSame({ doc, expectedName: "expected-noparagraph-loop-with-pagebreak.docx", }); }); it("should work at end of paragraph loop", function () { const doc = createDoc("paragraph-loop-with-pagebreak.docx"); doc.setOptions({ paragraphLoop: true, }); doc .setData({ users: [{ name: "Bar" }, { name: "John" }, { name: "Baz" }], }) .render(); shouldBeSame({ doc, expectedName: "expected-paragraph-loop-with-pagebreak.docx", }); }); it("should work with pagebreak afterwards with falsy value", function () { const doc = createDoc("paragraph-loop-with-pagebreak.docx"); doc.setOptions({ paragraphLoop: true, }); doc .setData({ users: false, }) .render(); shouldBeSame({ doc, expectedName: "expected-paragraph-loop-empty-with-pagebreak.docx", }); }); }); describe("ParagraphLoop", function () { it("should work with docx", function () { const doc = createDoc("users.docx"); doc.setOptions({ paragraphLoop: true, }); doc.setData({ users: ["John", "Jane", "Louis"] }).render(); shouldBeSame({ doc, expectedName: "expected-users.docx" }); }); it("should work without removing extra text", function () { const doc = createDoc("paragraph-loops.docx"); doc.setOptions({ paragraphLoop: true, }); doc .setData({ condition: [1, 2], l1: [ { l2: ["a", "b", "c"], }, { l2: ["d", "e", "f"], }, ], placeholder: "placeholder-value", }) .render(); shouldBeSame({ doc, expectedName: "expected-paragraph-loop.docx" }); }); it("should work with pptx", function () { const doc = createDoc("paragraph-loop.pptx"); doc.setOptions({ paragraphLoop: true, }); doc .setData({ users: [ { age: 10, name: "Bar" }, { age: 18, name: "Bar" }, { age: 22, name: "Bar" }, ], }) .render(); shouldBeSame({ doc, expectedName: "expected-paragraph-loop.pptx" }); }); it("should not fail when having paragraph in paragraph", function () { const doc = createDoc("regression-par-in-par.docx"); const printedPostparsed = []; let filePath = ""; doc.attachModule({ set(obj) { if (obj.inspect) { if (obj.inspect.filePath) { filePath = obj.inspect.filePath; } if (obj.inspect.postparsed) { printedPostparsed[filePath] = printy(obj.inspect.postparsed); } } }, }); doc.setOptions({ paragraphLoop: true, parser: () => ({ get: () => "foo", }), }); doc.setData({}); doc.render(); expect(printedPostparsed["word/document.xml"]).to.be.equal( expectedPrintedPostParsed ); shouldBeSame({ doc, expectedName: "expected-rendered-par-in-par.docx" }); }); it("should work with spacing at the end", function () { const doc = createDoc("spacing-end.docx"); doc.setOptions({ paragraphLoop: true, }); doc.setData({ name: "John" }).render(); shouldBeSame({ doc, expectedName: "expected-spacing-end.docx" }); }); it("should throw specific error if calling .render() on document with invalid tags", function () { const doc = createDoc("errors-footer-and-header.docx"); doc.setOptions({ paragraphLoop: true, parser: angularParser, }); let catched = false; try { doc.compile(); } catch (e) { catched = true; const expectedError = { name: "InternalError", message: "You should not call .render on a document that had compilation errors", properties: { id: "render_on_invalid_template", }, }; expectToThrow(() => doc.render(), Errors.XTInternalError, expectedError); /* handle error */ } expect(catched).to.equal(true); }); it("should fail with errors from header and footer", function () { const doc = createDoc("errors-footer-and-header.docx"); doc.setOptions({ paragraphLoop: true, parser: angularParser, }); doc.setData({}); const expectedError = { message: "Multi error", name: "TemplateError", properties: { id: "multi_error", errors: [ { name: "TemplateError", message: "Unclosed tag", properties: { file: "word/footer1.xml", xtag: "footer", id: "unclosed_tag", context: "{footer", offset: 2, }, }, { name: "TemplateError", message: "Duplicate close tag, expected one close tag", properties: { file: "word/header1.xml", xtag: "itle}}", id: "duplicate_close_tag", context: "itle}}", offset: 15, }, }, { name: "TemplateError", message: "Closing tag does not match opening tag", properties: { closingtag: "bang", openingtag: "users", file: "word/document.xml", id: "closing_tag_does_not_match_opening_tag", offset: [8, 16], }, }, ], }, }; const create = doc.render.bind(doc); expectToThrow(create, Errors.XTTemplateError, expectedError); }); it("should fail properly when having lexed + postparsed errors", function () { const doc = createDoc("multi-errors.docx"); doc.setOptions({ paragraphLoop: true, parser: angularParser, }); doc.setData({ users: [ { age: 10, name: "Bar" }, { age: 18, name: "Bar" }, { age: 22, name: "Bar" }, ], }); const expectedError = { message: "Multi error", name: "TemplateError", properties: { id: "multi_error", errors: [ { name: "TemplateError", message: "Unclosed tag", properties: { file: "word/document.xml", xtag: "firstName", id: "unclosed_tag", context: "{firstName ", offset: 0, }, }, { name: "TemplateError", message: "Unclosed tag", properties: { file: "word/document.xml", xtag: "error", id: "unclosed_tag", context: "{error ", offset: 22, }, }, { name: "TemplateError", message: "Duplicate close tag, expected one close tag", properties: { file: "word/document.xml", xtag: "{tag}}", id: "duplicate_close_tag", context: "{tag}}", offset: 34, }, }, { name: "TemplateError", message: "Duplicate open tag, expected one open tag", properties: { file: "word/document.xml", xtag: "{{bar}", id: "duplicate_open_tag", context: "{{bar}", offset: 42, }, }, ], }, }; const create = doc.render.bind(doc); expectToThrow(create, Errors.XTTemplateError, expectedError); }); it("should fail when placing paragraph loop inside normal loop", function () { const doc = createDoc("paragraph-loop-error.docx"); doc.setData({}); const expectedError = { message: "Multi error", name: "TemplateError", properties: { id: "multi_error", errors: [ { name: "TemplateError", message: 'No tag "w:p" was found at the left', properties: { file: "word/document.xml", id: "no_xml_tag_found_at_left", element: "w:p", index: 1, parsedLength: 4, offset: 12, part: { endLindex: 17, expandTo: "w:p", inverted: false, lIndex: 17, location: "start", module: "loop", offset: 12, raw: "-w:p loop", type: "placeholder", value: "loop", }, }, }, { name: "TemplateError", message: 'No tag "w:p" was found at the right', properties: { file: "word/document.xml", id: "no_xml_tag_found_at_right", element: "w:p", index: 3, parsedLength: 4, offset: 26, part: { endLindex: 21, lIndex: 21, location: "end", module: "loop", offset: 26, raw: "/", type: "placeholder", value: "", }, }, }, ], }, }; const create = doc.compile.bind(doc); expectToThrow(create, Errors.XTTemplateError, expectedError); }); }); describe("Prefixes", function () { it("should be possible to change the prefix of the loop module", function () { const content = "{##tables}{user}{/tables}"; const scope = { tables: [{ user: "John" }, { user: "Jane" }], }; const doc = createXmlTemplaterDocxNoRender(content, { tags: scope }); doc.modules.forEach(function (module) { if (module.name === "LoopModule") { module.prefix.start = "##"; } }); doc.render(); expect(doc.getFullText()).to.be.equal("JohnJane"); }); it("should be possible to change the prefix of the loop module to a regexp", function () { const content = "{##tables}{user}{/tables}{#tables}{user}{/tables}"; const scope = { tables: [{ user: "A" }, { user: "B" }], }; const doc = createXmlTemplaterDocxNoRender(content, { tags: scope }); doc.modules.forEach(function (module) { if (module.name === "LoopModule") { module.prefix.start = /^##?(.*)$/; } }); doc.render(); expect(doc.getFullText()).to.be.equal("ABAB"); }); it("should be possible to change the prefix of the raw xml module to a regexp", function () { const content = "{!!raw}"; const scope = { raw: "HoHo", }; const doc = createXmlTemplaterDocxNoRender(content, { tags: scope }); doc.modules.forEach(function (module) { if (module.name === "RawXmlModule") { module.prefix = /^!!?(.*)$/; } }); doc.render(); expect(doc.getFullText()).to.be.equal("HoHo"); }); }); describe("Load Office 365 file", function () { it("should handle files with word/document2.xml", function () { const doc = createDoc("office365.docx"); doc.setOptions({ paragraphLoop: true, }); doc .setData({ test: "Value", test2: "Value2", }) .render(); expect(doc.getFullText()).to.be.equal("Value Value2"); shouldBeSame({ doc, expectedName: "expected-office365.docx" }); }); }); describe("Resolver", function () { it("should work", function () { const doc = createDoc("office365.docx"); doc.setOptions({ paragraphLoop: true, }); doc.compile(); return doc .resolveData({ test: resolveSoon("Value"), test2: "Value2", }) .then(function () { doc.render(); expect(doc.getFullText()).to.be.equal("Value Value2"); shouldBeSame({ doc, expectedName: "expected-office365.docx" }); }); }); it("should work at parent level", function () { const doc = createDoc("office365.docx"); doc.setOptions({ paragraphLoop: true, }); doc.compile(); return doc .resolveData( resolveSoon({ test: resolveSoon("Value"), test2: "Value2", }) ) .then(function () { doc.render(); expect(doc.getFullText()).to.be.equal("Value Value2"); shouldBeSame({ doc, expectedName: "expected-office365.docx" }); }); }); it("should resolve loops", function () { const doc = createDoc("multi-loop.docx"); doc.setOptions({ paragraphLoop: true, }); doc.compile(); return doc .resolveData({ companies: resolveSoon([ { name: "Acme", users: resolveSoon([ { name: "John", }, { name: "James", }, ]), }, { name: resolveSoon("Emca"), users: resolveSoon([ { name: "Mary", }, { name: "Liz", }, ]), }, ]), test2: "Value2", }) .then(function () { doc.render(); shouldBeSame({ doc, expectedName: "expected-multi-loop.docx" }); }); }); it("should resolve with simple table", function () { const doc = createDoc("table-complex2-example.docx"); doc.compile(); return doc .resolveData({ table1: [ { t1data1: "t1-1row-data1", t1data2: "t1-1row-data2", t1data3: "t1-1row-data3", t1data4: "t1-1row-data4", }, { t1data1: "t1-2row-data1", t1data2: "t1-2row-data2", t1data3: "t1-2row-data3", t1data4: "t1-2row-data4", }, { t1data1: "t1-3row-data1", t1data2: "t1-3row-data2", t1data3: "t1-3row-data3", t1data4: "t1-3row-data4", }, ], t1total1: "t1total1-data", t1total2: "t1total2-data", t1total3: "t1total3-data", }) .then(function (resolved) { const myresolved = cloneDeep(resolved); cleanRecursive(myresolved); expect(myresolved).to.be.deep.equal([ { tag: "t1total1", value: "t1total1-data", }, { tag: "t1total2", value: "t1total2-data", }, { tag: "t1total3", value: "t1total3-data", }, { tag: "table1", value: [ [ { tag: "t1data1", value: "t1-1row-data1", }, { tag: "t1data2", value: "t1-1row-data2", }, { tag: "t1data3", value: "t1-1row-data3", }, { tag: "t1data4", value: "t1-1row-data4", }, ], [ { tag: "t1data1", value: "t1-2row-data1", }, { tag: "t1data2", value: "t1-2row-data2", }, { tag: "t1data3", value: "t1-2row-data3", }, { tag: "t1data4", value: "t1-2row-data4", }, ], [ { tag: "t1data1", value: "t1-3row-data1", }, { tag: "t1data2", value: "t1-3row-data2", }, { tag: "t1data3", value: "t1-3row-data3", }, { tag: "t1data4", value: "t1-3row-data4", }, ], ], }, ]); doc.render(); const fullText = doc.getFullText(); expect(fullText).to.be.equal( "TABLE1COLUMN1COLUMN2COLUMN3COLUMN4t1-1row-data1t1-1row-data2t1-1row-data3t1-1row-data4t1-2row-data1t1-2row-data2t1-2row-data3t1-2row-data4t1-3row-data1t1-3row-data2t1-3row-data3t1-3row-data4TOTALt1total1-datat1total2-datat1total3-data" ); }); }); const dataNestedLoops = { a: [{ d: "Hello world" }] }; it("should not regress with nested loops sync", function () { const doc = createDoc("regression-complex-loops.docx"); doc.compile(); doc.setData(dataNestedLoops); doc.render(); shouldBeSame({ doc, expectedName: "expected-regression-complex-loops.docx", }); }); it("should not regress when having [Content_Types.xml] contain Default instead of Override", function () { const doc = createDoc("with-default-contenttype.docx"); doc.compile(); doc.setData({}); doc.render(); shouldBeSame({ doc, expectedName: "expected-with-default-contenttype.docx", }); }); it("should not regress with nested loops async", function () { const doc = createDoc("regression-complex-loops.docx"); doc.compile(); return doc.resolveData(dataNestedLoops).then(function () { doc.render(); shouldBeSame({ doc, expectedName: "expected-regression-complex-loops.docx", }); }); }); const regress2Data = { amount_wheels_car_1: "4", amount_wheels_motorcycle_1: "2", amount_wheels_car_2: "6", amount_wheels_motorcycle_2: "3", id: [ { car: "1", motorcycle: "", }, ], }; it("should not regress with multiple loops sync", function () { const doc = createDoc("regression-loops-resolve.docx"); doc.compile(); doc.setData(regress2Data); doc.render(); shouldBeSame({ doc, expectedName: "expected-regression-loops-resolve.docx", }); }); it("should not regress with multiple loops async", function () { const doc = createDoc("regression-loops-resolve.docx"); doc.compile(); return doc.resolveData(regress2Data).then(function () { doc.render(); shouldBeSame({ doc, expectedName: "expected-regression-loops-resolve.docx", }); }); }); });