678 lines
16 KiB
JavaScript
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,
|
|
};
|