2021-09-04 10:15:02 +05:00

678 lines
16 KiB
JavaScript

const path = require("path");
const chai = require("chai");
const { expect } = chai;
const PizZip = require("pizzip");
const fs = require("fs");
const { get, unset, omit, uniq } = require("lodash");
const errorLogger = require("../error-logger");
const diff = require("diff");
const AssertionModule = require("./assertion-module.js");
const Docxtemplater = require("../docxtemplater.js");
const { first } = require("../utils.js");
const xmlPrettify = require("./xml-prettify");
let countFiles = 1;
let allStarted = false;
let examplesDirectory;
const documentCache = {};
const imageData = {};
const emptyNamespace = /xmlns:[a-z0-9]+=""/;
function unifiedDiff(actual, expected) {
const indent = " ";
function cleanUp(line) {
const firstChar = first(line);
if (firstChar === "+") {
return indent + line;
}
if (firstChar === "-") {
return indent + line;
}
if (line.match(/@@/)) {
return "--";
}
if (line.match(/\\ No newline/)) {
return null;
}
return indent + line;
}
function notBlank(line) {
return typeof line !== "undefined" && line !== null;
}
const msg = diff.createPatch("string", actual, expected);
const lines = msg.split("\n").splice(5);
return (
"\n " +
"+ expected" +
" " +
"- actual" +
"\n\n" +
lines.map(cleanUp).filter(notBlank).join("\n")
);
}
function isNode12() {
return process && process.version && process.version.indexOf("v12") === 0;
}
function walk(dir) {
let results = [];
const list = fs.readdirSync(dir);
list.forEach(function (file) {
if (file.indexOf(".") === 0) {
return;
}
file = dir + "/" + file;
const stat = fs.statSync(file);
if (stat && stat.isDirectory()) {
results = results.concat(walk(file));
} else {
results.push(file);
}
});
return results;
}
function createXmlTemplaterDocxNoRender(content, options = {}) {
const doc = makeDocx("temporary.docx", content);
doc.setOptions(options);
doc.setData(options.tags);
return doc;
}
function createXmlTemplaterDocx(content, options = {}) {
const doc = makeDocx("temporary.docx", content);
doc.setOptions(options);
doc.setData(options.tags);
doc.render();
return doc;
}
function writeFile(expectedName, zip) {
const writeFile = path.resolve(examplesDirectory, "..", expectedName);
if (fs.writeFileSync) {
fs.writeFileSync(
writeFile,
zip.generate({ type: "nodebuffer", compression: "DEFLATE" })
);
}
if (typeof window !== "undefined" && window.saveAs) {
const out = zip.generate({
type: "blob",
mimeType:
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
compression: "DEFLATE",
});
saveAs(out, expectedName); // comment to see the error
}
}
function unlinkFile(expectedName) {
const writeFile = path.resolve(examplesDirectory, "..", expectedName);
if (fs.unlinkSync) {
try {
fs.unlinkSync(writeFile);
} catch (e) {
if (e.code !== "ENOENT") {
throw e;
}
}
}
}
/* eslint-disable no-console */
function shouldBeSame(options) {
const zip = options.doc.getZip();
const { expectedName } = options;
let expectedZip;
try {
expectedZip = documentCache[expectedName].zip;
} catch (e) {
writeFile(expectedName, zip);
console.log(
JSON.stringify({ msg: "Expected file does not exists", expectedName })
);
throw e;
}
try {
uniq(Object.keys(zip.files).concat(Object.keys(expectedZip.files))).map(
function (filePath) {
const suffix = `for "${filePath}"`;
expect(expectedZip.files[filePath]).to.be.an(
"object",
`The file ${filePath} doesn't exist on ${expectedName}`
);
expect(zip.files[filePath]).to.be.an(
"object",
`The file ${filePath} doesn't exist on generated file`
);
expect(zip.files[filePath].name).to.be.equal(
expectedZip.files[filePath].name,
`Name differs ${suffix}`
);
expect(zip.files[filePath].options.dir).to.be.equal(
expectedZip.files[filePath].options.dir,
`IsDir differs ${suffix}`
);
const text1 = zip.files[filePath].asText().replace(/\n|\t/g, "");
const text2 = expectedZip.files[filePath]
.asText()
.replace(/\n|\t/g, "");
if (endsWith(filePath, "/")) {
return;
}
if (filePath.indexOf(".png") !== -1) {
expect(text1.length).to.be.equal(
text2.length,
`Content differs ${suffix}`
);
expect(text1).to.be.equal(text2, `Content differs ${suffix}`);
} else {
expect(text1).to.not.match(
emptyNamespace,
`The file ${filePath} has empty namespaces`
);
expect(text2).to.not.match(
emptyNamespace,
`The file ${filePath} has empty namespaces`
);
if (text1 === text2) {
return;
}
const pText1 = xmlPrettify(text1, options);
const pText2 = xmlPrettify(text2, options);
if (pText1 !== pText2) {
const pd = unifiedDiff(pText1, pText2);
expect(pText1).to.be.equal(
pText2,
"Content differs \n" + suffix + "\n" + pd
);
}
}
}
);
} catch (e) {
writeFile(expectedName, zip);
console.log(
JSON.stringify({
msg: "Expected file differs from actual file",
expectedName,
})
);
throw e;
}
unlinkFile(expectedName);
}
/* eslint-enable no-console */
function checkLength(e, expectedError, propertyPath) {
const propertyPathLength = propertyPath + "Length";
const property = get(e, propertyPath);
const expectedPropertyLength = get(expectedError, propertyPathLength);
if (property && expectedPropertyLength) {
expect(expectedPropertyLength).to.be.a(
"number",
JSON.stringify(expectedError.properties)
);
expect(expectedPropertyLength).to.equal(property.length);
unset(e, propertyPath);
unset(expectedError, propertyPathLength);
}
}
function cleanRecursive(arr) {
arr.forEach(function (p) {
delete p.lIndex;
delete p.endLindex;
delete p.offset;
delete p.raw;
if (p.subparsed) {
cleanRecursive(p.subparsed);
}
if (p.value && p.value.forEach) {
p.value.forEach(cleanRecursive);
}
if (p.expanded) {
p.expanded.forEach(cleanRecursive);
}
});
}
function cleanError(e, expectedError) {
const message = e.message;
e = omit(e, ["line", "sourceURL", "stack"]);
e.message = message;
if (expectedError.properties && e.properties) {
if (expectedError.properties.explanation != null) {
const e1 = e.properties.explanation;
const e2 = expectedError.properties.explanation;
expect(e1).to.be.deep.equal(
e2,
`Explanations differ '${e1}' != '${e2}': for ${JSON.stringify(
expectedError
)}`
);
}
delete e.properties.explanation;
delete expectedError.properties.explanation;
if (e.properties.postparsed) {
e.properties.postparsed.forEach(function (p) {
delete p.lIndex;
delete p.endLindex;
delete p.offset;
});
}
if (e.properties.rootError) {
expect(
e.properties.rootError,
JSON.stringify(e.properties)
).to.be.instanceOf(Error);
expect(
expectedError.properties.rootError,
JSON.stringify(expectedError.properties)
).to.be.instanceOf(Object, "expectedError doesn't have a rootError");
if (expectedError) {
expect(e.properties.rootError.message).to.equal(
expectedError.properties.rootError.message,
"rootError.message"
);
}
delete e.properties.rootError;
delete expectedError.properties.rootError;
}
if (expectedError.properties.offset != null) {
const o1 = e.properties.offset;
const o2 = expectedError.properties.offset;
// offset can be arrays, so deep compare
expect(o1).to.be.deep.equal(
o2,
`Offset differ ${o1} != ${o2}: for ${JSON.stringify(expectedError)}`
);
}
delete expectedError.properties.offset;
delete e.properties.offset;
checkLength(e, expectedError, "properties.paragraphParts");
checkLength(e, expectedError, "properties.postparsed");
checkLength(e, expectedError, "properties.parsed");
}
if (e.stack && expectedError) {
expect(e.stack).to.contain("Error: " + expectedError.message);
}
delete e.stack;
return e;
}
function wrapMultiError(error) {
const type = Object.prototype.toString.call(error);
let errors;
if (type === "[object Array]") {
errors = error;
} else {
errors = [error];
}
return {
name: "TemplateError",
message: "Multi error",
properties: {
id: "multi_error",
errors,
},
};
}
function jsonifyError(e) {
return JSON.parse(
JSON.stringify(e, function (key, value) {
if (value instanceof Promise) {
return {};
}
return value;
})
);
}
function errorVerifier(e, type, expectedError) {
expect(e, "No error has been thrown").not.to.be.equal(null);
const toShowOnFail = e.stack;
expect(e, toShowOnFail).to.be.instanceOf(Error);
expect(e, toShowOnFail).to.be.instanceOf(type);
expect(e, toShowOnFail).to.be.an("object");
expect(e, toShowOnFail).to.have.property("properties");
expect(e.properties, toShowOnFail).to.be.an("object");
if (type.name && type.name !== "XTInternalError") {
expect(e.properties, toShowOnFail).to.have.property("explanation");
expect(e.properties.explanation, toShowOnFail).to.be.a("string");
expect(e.properties.explanation, toShowOnFail).to.be.a("string");
}
expect(e.properties, toShowOnFail).to.have.property("id");
expect(e.properties.id, toShowOnFail).to.be.a("string");
e = cleanError(e, expectedError);
if (e.properties.errors) {
const msg =
"expected : \n" +
JSON.stringify(expectedError.properties.errors) +
"\nactual : \n" +
JSON.stringify(e.properties.errors);
expect(expectedError.properties.errors).to.be.an("array", msg);
const l1 = e.properties.errors.length;
const l2 = expectedError.properties.errors.length;
expect(l1).to.equal(
l2,
`Expected to have the same amount of e.properties.errors ${l1} !== ${l2} ` +
msg
);
e.properties.errors = e.properties.errors.map(function (suberror, i) {
const cleaned = cleanError(suberror, expectedError.properties.errors[i]);
const jsonified = jsonifyError(cleaned);
return jsonified;
});
}
const realError = jsonifyError(e);
expect(realError).to.be.deep.equal(expectedError);
}
function expectToThrowAsync(fn, type, expectedError) {
return Promise.resolve(null)
.then(function () {
const r = fn();
return r.then(function () {
return null;
});
})
.catch(function (error) {
return error;
})
.then(function (e) {
return errorVerifier(e, type, expectedError);
});
}
function expectToThrow(fn, type, expectedError) {
let err = null;
try {
fn();
} catch (e) {
err = e;
}
errorVerifier(err, type, expectedError);
return err;
}
function load(name, content, obj) {
const zip = new PizZip(content);
obj[name] = new Docxtemplater();
obj[name].loadZip(zip);
obj[name].loadedName = name;
obj[name].loadedContent = content;
return obj[name];
}
function loadDocument(name, content) {
return load(name, content, documentCache);
}
function cacheDocument(name, content) {
const zip = new PizZip(content);
documentCache[name] = { loadedName: name, loadedContent: content, zip };
return documentCache[name];
}
function loadImage(name, content) {
imageData[name] = content;
}
function loadFile(name, callback) {
if (fs.readFileSync) {
const path = require("path");
const buffer = fs.readFileSync(
path.join(examplesDirectory, name),
"binary"
);
return callback(null, name, buffer);
}
return PizZipUtils.getBinaryContent("../examples/" + name, function (
err,
data
) {
if (err) {
return callback(err);
}
return callback(null, name, data);
});
}
function unhandledRejectionHandler(reason) {
throw reason;
}
let startFunction;
function setStartFunction(sf) {
allStarted = false;
countFiles = 1;
startFunction = sf;
if (typeof window !== "undefined" && window.addEventListener) {
window.addEventListener("unhandledrejection", unhandledRejectionHandler);
} else {
process.on("unhandledRejection", unhandledRejectionHandler);
}
}
function endLoadFile(change) {
change = change || 0;
countFiles += change;
if (countFiles === 0 && allStarted === true) {
const result = startFunction();
if (typeof window !== "undefined") {
return window.mocha.run(() => {
const elemDiv = window.document.getElementById("status");
elemDiv.textContent = "FINISHED";
document.body.appendChild(elemDiv);
});
}
return result;
}
}
function endsWith(str, suffix) {
return str.indexOf(suffix, str.length - suffix.length) !== -1;
}
function endsWithOne(str, suffixes) {
return suffixes.some(function (suffix) {
return endsWith(str, suffix);
});
}
function startsWith(str, suffix) {
return str.indexOf(suffix) === 0;
}
/* eslint-disable no-console */
function start() {
afterEach(function () {
if (
this.currentTest.state === "failed" &&
this.currentTest.err.properties
) {
errorLogger(this.currentTest.err);
}
});
/* eslint-disable import/no-unresolved */
const fileNames = require("./filenames.js");
/* eslint-enable import/no-unresolved */
fileNames.forEach(function (fullFileName) {
const fileName = fullFileName.replace(examplesDirectory + "/", "");
let callback;
if (startsWith(fileName, ".") || startsWith(fileName, "~")) {
return;
}
if (
endsWithOne(fileName, [
".dotx",
".dotm",
".docx",
".docm",
".pptm",
".pptx",
".xlsx",
])
) {
callback = cacheDocument;
}
if (!callback) {
callback = loadImage;
}
countFiles++;
loadFile(fileName, (e, name, buffer) => {
if (e) {
console.log(e);
throw e;
}
endLoadFile(-1);
callback(name, buffer);
});
});
allStarted = true;
endLoadFile(-1);
}
/* eslint-disable no-console */
function setExamplesDirectory(ed) {
examplesDirectory = ed;
if (fs && fs.writeFileSync) {
const fileNames = walk(examplesDirectory).map(function (f) {
return f.replace(examplesDirectory + "/", "");
});
fs.writeFileSync(
path.resolve(__dirname, "filenames.js"),
"module.exports=" + JSON.stringify(fileNames)
);
}
}
function removeSpaces(text) {
return text.replace(/\n|\t/g, "");
}
const contentTypeContent = `<?xml version="1.0" encoding="utf-8"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
<Default Extension="xml" ContentType="application/xml"/>
<Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>
</Types>`;
function makeDocx(name, content) {
const zip = new PizZip();
zip.file("word/document.xml", content, { createFolders: true });
zip.file("[Content_Types].xml", contentTypeContent);
return load(name, zip.generate({ type: "string" }), documentCache);
}
function createDoc(name) {
const doc = loadDocument(name, documentCache[name].loadedContent);
/* eslint-disable-next-line no-process-env */
if (!process.env.FAST) {
doc.attachModule(new AssertionModule());
}
return doc;
}
function createDocV4(name, options) {
const zip = getZip(name);
/* eslint-disable-next-line no-process-env */
if (!process.env.FAST) {
options = options || {};
if (!options.modules || options.modules instanceof Array) {
options.modules = options.modules || [];
options.modules.push(new AssertionModule());
}
}
return new Docxtemplater(zip, options);
}
function getZip(name) {
return new PizZip(documentCache[name].loadedContent);
}
function getLoadedContent(name) {
return documentCache[name].loadedContent;
}
function getContent(doc) {
return doc.getZip().files["word/document.xml"].asText();
}
function resolveSoon(data) {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(data);
}, 1);
});
}
function rejectSoon(data) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
reject(data);
}, 1);
});
}
function getParameterByName(name) {
if (typeof window === "undefined") {
return null;
}
const url = window.location.href;
name = name.replace(/[\[\]]/g, "\\$&");
const regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
results = regex.exec(url);
if (!results) {
return null;
}
if (!results[2]) {
return "";
}
return decodeURIComponent(results[2].replace(/\+/g, " "));
}
function browserMatches(regex) {
const currentBrowser = getParameterByName("browser");
if (currentBrowser === null) {
return false;
}
return regex.test(currentBrowser);
}
module.exports = {
chai,
cleanError,
cleanRecursive,
createDoc,
getLoadedContent,
createXmlTemplaterDocx,
createXmlTemplaterDocxNoRender,
expect,
expectToThrow,
expectToThrowAsync,
getContent,
imageData,
loadDocument,
loadFile,
loadImage,
makeDocx,
removeSpaces,
setExamplesDirectory,
setStartFunction,
shouldBeSame,
resolveSoon,
rejectSoon,
start,
wrapMultiError,
isNode12,
createDocV4,
getZip,
getParameterByName,
browserMatches,
};