marko/test/autotest.js

217 lines
7.0 KiB
JavaScript

"use strict";
const fs = require("fs");
const path = require("path");
const assert = require("assert");
const callerpath = require("caller-path");
const projectRoot = path.join(__dirname, "..");
const updateExpectations = process.env.hasOwnProperty("UPDATE_EXPECTATIONS");
const complain = require("complain");
const formatters = {};
module.exports = function autotest(fixturesName, run) {
const suiteDirectory = path.dirname(callerpath());
const suiteName = path.basename(suiteDirectory);
const fixturesDirectory = path.join(suiteDirectory, fixturesName);
describe(path.join(suiteName, fixturesName), () => {
let modes = [];
if (typeof run === "function") {
modes.push({ name: "", run });
} else {
Object.keys(run).forEach(modeName =>
modes.push({
name: modeName,
run: run[modeName]
})
);
}
fs.readdirSync(fixturesDirectory).forEach(fixtureName => {
let fixtureDirectory = path.join(fixturesDirectory, fixtureName);
let context = {};
if (fixtureName[0] === "~") {
// skip the fixture directory
} else if (modes.length > 1) {
describe(fixtureName, function() {
modes.forEach(mode => {
runFixtureTest(
mode.name,
fixtureDirectory,
mode.run,
mode.name,
context
);
});
});
} else {
runFixtureTest(fixtureName, fixtureDirectory, modes[0].run);
}
});
});
};
function runFixtureTest(name, dir, run, mode, context = {}) {
const resolve = file => path.join(dir, file);
const mainPath = resolve("test.js");
const hasMainFile = fs.existsSync(mainPath);
const expectDeprecation =
/-deprecated|migrate/.test(dir) && !/parser/.test(dir);
let mochaTestFunction = it;
let mochaDetails;
if (hasMainFile) {
try {
complain.log = function(message) {
context.deprecation = context.deprecation || new Error(message);
};
const main = require(mainPath);
const skip = main.skip || main["skip_" + mode];
const fails = main.fails || main["fails_" + mode];
if (skip) {
mochaTestFunction = it.skip;
mochaDetails = skip;
} else if (fails) {
mochaTestFunction = it.fails;
mochaDetails = fails;
}
} catch (err) {
mochaTestFunction(name, () => {
throw err;
});
return;
}
}
const snapshot = (actual, opts) => {
let prefix = opts && opts.name ? opts.name + "-" : "";
let ext =
typeof opts === "string" ? opts : (opts && opts.ext) || ".html";
let actualPath = resolve(prefix + "actual" + ext);
let expectedPath = resolve(prefix + "expected" + ext);
let format = (opts && opts.format) || formatters[ext];
let isObject = typeof actual === "string" ? false : true;
let actualString = isObject ? JSON.stringify(actual, null, 4) : actual;
let expectedString = loadExpected(expectedPath, isObject);
let expected;
fs.writeFileSync(actualPath, actualString, { encoding: "utf8" });
actual = normalize(actualString, isObject, format);
expected = normalize(expectedString, isObject, format);
try {
assert.deepEqual(actual, expected);
} catch (e) {
if (updateExpectations) {
fs.writeFileSync(expectedPath, actualString, {
encoding: "utf8"
});
} else {
e.stack = e.stack.slice(
e.stack.indexOf("\n", e.stack.indexOf("\n") + 1) + 1
);
e.message = `SnapshotError: ${path.relative(
process.cwd(),
actualPath
)}`;
throw e;
}
}
};
const test = fn => {
const withDeprecationAssertion = fn => {
const assertDeprecation = () => {
if (expectDeprecation) {
if (!context.deprecation) {
throw new Error(
"A deprecated test should log a deprecation warning using `complain`"
);
}
} else if (context.deprecation) {
throw context.deprecation;
}
};
if (fn.length > 0) {
return function(done) {
complain.log = function(message) {
if (!/NOTICE/.test(message)) {
context.deprecation =
context.deprecation || new Error(message);
}
};
return fn.call(this, err => {
if (err) {
done(err);
} else {
assertDeprecation();
done();
}
});
};
} else {
return function() {
complain.log = function(message) {
if (!/NOTICE/.test(message)) {
context.deprecation =
context.deprecation || new Error(message);
}
};
return Promise.resolve()
.then(() => fn.call(this))
.then(assertDeprecation);
};
}
};
const test = mochaTestFunction(name, withDeprecationAssertion(fn));
test.details = mochaDetails;
test.file = mainPath;
return test;
};
const skip = reason => {
const test = it.skip(name);
test.details = reason;
test.file = mainPath;
return test;
};
run({
resolve,
test,
skip,
dir,
snapshot,
mode,
context
});
}
function loadExpected(expectedPath, isObject) {
try {
return fs.readFileSync(expectedPath, { encoding: "utf8" });
} catch (e) {
let expected = `${path.basename(expectedPath)} does not exist`;
return isObject ? JSON.stringify(expected) : expected;
}
}
function normalize(content, isObject, format) {
if (isObject) {
content = JSON.parse(content);
} else {
content = replaceAll(content, projectRoot, "PROJECT_ROOT").replace(
/\r?\n$/g,
""
);
}
format = format || (content => content);
return format(content);
}
function replaceAll(str, substr, replacement) {
return str.split(substr).join(replacement);
}