const { expectToThrow, createDoc, shouldBeSame, isNode12, createDocV4, } = require("./utils"); const Errors = require("../errors.js"); const { expect } = require("chai"); const { xml2str, traits } = require("../doc-utils"); describe("Verify apiversion", function () { it("should work with valid api version", function () { const module = { requiredAPIVersion: "3.23.0", render(part) { return part.value; }, }; const doc = createDoc("loop-valid.docx"); doc.attachModule(module); }); it("should fail with invalid api version", function () { const module = { requiredAPIVersion: "3.92.0", render(part) { return part.value; }, }; const doc = createDoc("loop-valid.docx"); expectToThrow(() => doc.attachModule(module), Errors.XTAPIVersionError, { message: "The minor api version is not uptodate, you probably have to update docxtemplater with npm install --save docxtemplater", name: "APIVersionError", properties: { id: "api_version_error", currentModuleApiVersion: [3, 24, 0], neededVersion: [3, 92, 0], }, }); }); }); describe("Module attachment", function () { it("should not allow to attach the same module twice", function () { const module = { name: "TestModule", requiredAPIVersion: "3.0.0", render(part) { return part.value; }, }; const doc1 = createDoc("loop-valid.docx"); doc1.attachModule(module); const doc2 = createDoc("tag-example.docx"); let errMessage = null; try { doc2.attachModule(module); } catch (e) { errMessage = e.message; } expect(errMessage).to.equal( 'Cannot attach a module that was already attached : "TestModule". Maybe you are instantiating the module at the root level, and using it for multiple instances of Docxtemplater' ); }); }); describe("Module xml parse", function () { it("should not mutate options (regression for issue #526)", function () { const module = { requiredAPIVersion: "3.0.0", optionsTransformer(options, docxtemplater) { const relsFiles = docxtemplater.zip .file(/document.xml.rels/) .map((file) => file.name); options.xmlFileNames = options.xmlFileNames.concat(relsFiles); return options; }, }; const doc = createDoc("tag-example.docx"); const opts = {}; doc.setOptions(opts); doc.attachModule(module); doc.compile(); expect(opts).to.deep.equal({}); }); it("should be possible to parse xml files", function () { let xmlDocuments; const module = { requiredAPIVersion: "3.0.0", optionsTransformer(options, docxtemplater) { const relsFiles = docxtemplater.zip .file(/document.xml.rels/) .map((file) => file.name); options.xmlFileNames = options.xmlFileNames.concat(relsFiles); return options; }, set(options) { if (options.xmlDocuments) { xmlDocuments = options.xmlDocuments; } }, }; const doc = createDoc("tag-example.docx"); doc.attachModule(module); doc.compile(); const xmlKeys = Object.keys(xmlDocuments); expect(xmlKeys).to.deep.equal(["word/_rels/document.xml.rels"]); const rels = xmlDocuments[ "word/_rels/document.xml.rels" ].getElementsByTagName("Relationship"); expect(rels.length).to.equal(10); const str = xml2str(xmlDocuments["word/_rels/document.xml.rels"]); if (isNode12()) { expect(str).to .equal(`\r `); rels[5].setAttribute("Foobar", "Baz"); doc.render(); shouldBeSame({ doc, expectedName: "expected-module-change-rels.docx" }); } }); }); describe("Module unique tags xml", function () { it("should not cause an issue if tagsXmlLexedArray contains duplicates", function () { const module = { requiredAPIVersion: "3.0.0", optionsTransformer(options, docxtemplater) { docxtemplater.fileTypeConfig.tagsXmlLexedArray.push( "w:p", "w:r", "w:p" ); return options; }, }; const doc = createDoc("tag-example.docx"); doc.attachModule(module); doc.setData({ first_name: "Hipp", last_name: "Edgar", phone: "0652455478", description: "New Website", }); doc.compile(); doc.render(); shouldBeSame({ doc, expectedName: "expected-tag-example.docx" }); }); }); describe("Module traits", function () { it("should not cause an issue if using traits.expandTo containing loop", function () { const moduleName = "comment-module"; function getInner({ part, leftParts, rightParts, postparse }) { part.subparsed = postparse([].concat(leftParts).concat(rightParts), { basePart: part, }); return part; } const module = { name: "Test module", requiredAPIVersion: "3.0.0", parse(placeHolderContent) { if (placeHolderContent[0] === "£") { const type = "placeholder"; return { type, value: placeHolderContent.substr(1), module: moduleName, }; } }, postparse(parsed, { postparse }) { parsed = traits.expandToOne(parsed, { moduleName, getInner, expandTo: ["w:p"], postparse, }); return parsed; }, render(part) { if (part.module === moduleName) { return { value: "", }; } }, }; const doc = createDoc("comment-with-loop.docx"); doc.attachModule(module); doc.setData({}).compile().render(); shouldBeSame({ doc, expectedName: "expected-comment-example.docx" }); }); }); describe("Module errors", function () { it("should work", function () { const moduleName = "ErrorModule"; const module = { name: "Error module", requiredAPIVersion: "3.0.0", parse(placeHolderContent) { const type = "placeholder"; return { type, value: placeHolderContent, module: moduleName, }; }, render(part) { if (part.module === moduleName) { return { errors: [new Error(`foobar ${part.value}`)], }; } }, }; let error = null; const doc = createDoc("tag-example.docx"); doc.attachModule(module); doc.setData({}).compile(); try { doc.render(); } catch (e) { error = e; } expect(error).to.be.an("object"); expect(error.message).to.equal("Multi error"); expect(error.properties.errors.length).to.equal(9); expect(error.properties.errors[0].message).to.equal("foobar last_name"); expect(error.properties.errors[1].message).to.equal("foobar first_name"); expect(error.properties.errors[2].message).to.equal("foobar phone"); }); }); describe("Module should pass options to module.parse, module.postparse, module.render, module.postrender", function () { it("should pass filePath and contentType options", function () { const doc = createDoc("tag-example.docx"); const filePaths = []; let renderFP = "", renderCT = "", postrenderFP = "", postrenderCT = "", postparseFP = "", postparseCT = ""; const ct = []; const module = { name: "Test module", requiredAPIVersion: "3.0.0", parse(a, options) { filePaths.push(options.filePath); ct.push(options.contentType); }, postparse(a, options) { postparseFP = options.filePath; postparseCT = options.contentType; return a; }, render(a, options) { renderFP = options.filePath; renderCT = options.contentType; }, postrender(a, options) { postrenderFP = options.filePath; postrenderCT = options.contentType; return a; }, }; doc.attachModule(module); doc.setData({}).compile(); doc.render(); expect(renderFP).to.equal("word/document.xml"); expect(renderCT).to.equal( "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml" ); expect(postparseFP).to.equal("word/document.xml"); expect(postparseCT).to.equal( "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml" ); expect(postrenderFP).to.equal("word/document.xml"); expect(postrenderCT).to.equal( "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml" ); expect(filePaths).to.deep.equal([ // Header appears 4 times because there are 4 tags in the header "word/header1.xml", "word/header1.xml", "word/header1.xml", "word/header1.xml", // Footer appears 3 times because there are 3 tags in the header "word/footer1.xml", "word/footer1.xml", "word/footer1.xml", // Document appears 2 times because there are 2 tags in the header "word/document.xml", "word/document.xml", ]); expect(ct).to.deep.equal([ "application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml", "application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml", "application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml", "application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml", "application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml", "application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml", "application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml", "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml", "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml", ]); }); }); describe("Module detachment", function () { it("should detach the module when the module does not support the document filetype", function () { let isModuleCalled = false; let isDetachedCalled = false; const module = { optionsTransformer(options) { isModuleCalled = true; return options; }, on(eventName) { if (eventName === "detached") { isDetachedCalled = true; } }, supportedFileTypes: ["pptx"], }; createDocV4("tag-example.docx", { modules: [module] }); expect(isDetachedCalled).to.equal(true); expect(isModuleCalled).to.equal(false); }); });