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: "<w:p><w:r><w:t>RAW</w:t></w:r></w:p>" },
			],
		});
		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 = "<w:t>{##tables}{user}{/tables}</w:t>";
		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 =
			"<w:t>{##tables}{user}{/tables}{#tables}{user}{/tables}</w:t>";
		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 = "<w:p><w:t>{!!raw}</w:t></w:p>";
		const scope = {
			raw: "<w:p><w:t>HoHo</w:t></w:p>",
		};
		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",
			});
		});
	});
});
