Commit c3e74ea3fa73c602896779454d2bc8aa741db520

Michael Schmidt 2019-03-24T22:50:22

Tests: Additional checks for Prism functions (#1803) This adds additional checks for `extend` and `insertBefore` under `Prism.languages`.

diff --git a/tests/checks/extend.js b/tests/checks/extend.js
new file mode 100644
index 0000000..b699dd3
--- /dev/null
+++ b/tests/checks/extend.js
@@ -0,0 +1,20 @@
+const { testFunction } = require('./../helper/check-functionality');
+
+function extendTest(id, redef) {
+	const details = `\nextend("${id}", ${redef})`;
+
+	// type checks
+	if (typeof id !== 'string') {
+		throw new TypeError(`The id argument has to be a 'string'.` + details);
+	}
+	if (typeof redef !== 'object') {
+		throw new TypeError(`The redef argument has to be an 'object'.` + details);
+	}
+
+
+	if (!(id in Prism.languages)) {
+		throw new Error(`Cannot extend '${id}' because it is not defined in Prism.languages.`);
+	}
+}
+
+testFunction('extend', Prism.languages, extendTest);
diff --git a/tests/checks/insert-before.js b/tests/checks/insert-before.js
new file mode 100644
index 0000000..8536345
--- /dev/null
+++ b/tests/checks/insert-before.js
@@ -0,0 +1,32 @@
+const { testFunction } = require('./../helper/check-functionality');
+
+function insertBeforeTest(inside, before, insert, root) {
+	const details = `\ninsertBefore("${inside}", "${before}", ${insert}, ${root})`;
+
+	// type checks
+	if (typeof inside !== 'string') {
+		throw new TypeError(`The inside argument has to be a 'string'.` + details);
+	}
+	if (typeof before !== 'string') {
+		throw new TypeError(`The before argument has to be a 'string'.` + details);
+	}
+	if (typeof insert !== 'object') {
+		throw new TypeError(`The insert argument has to be an 'object'.` + details);
+	}
+	if (root && typeof root !== 'object') {
+		throw new TypeError(`The root argument has to be an 'object' if defined.` + details);
+	}
+
+
+	root = root || Prism.languages;
+	var grammar = root[inside];
+
+	if (typeof grammar !== 'object') {
+		throw new Error(`The grammar "${inside}" has to be an 'object' not '${typeof grammar}'.`);
+	}
+	if (!(before in grammar)) {
+		throw new Error(`"${before}" has to be a key of the grammar "${inside}".`);
+	}
+}
+
+testFunction('insertBefore', Prism.languages, insertBeforeTest);
diff --git a/tests/helper/check-functionality.js b/tests/helper/check-functionality.js
new file mode 100644
index 0000000..0192d0c
--- /dev/null
+++ b/tests/helper/check-functionality.js
@@ -0,0 +1,13 @@
+"use strict";
+
+module.exports = {
+	testFunction(name, object, tester) {
+		const func = object[name];
+
+		object[name] = function () {
+			tester.apply(this, arguments);
+			return func.apply(this, arguments);
+		};
+	}
+
+}
diff --git a/tests/helper/prism-loader.js b/tests/helper/prism-loader.js
index cb7780d..8ec06e3 100644
--- a/tests/helper/prism-loader.js
+++ b/tests/helper/prism-loader.js
@@ -2,6 +2,7 @@
 
 const fs = require("fs");
 const vm = require("vm");
+const { getAllFiles } = require("./test-discovery");
 const components = require("../../components");
 const languagesCatalog = components.languages;
 
@@ -70,7 +71,7 @@ module.exports = {
 		}
 
 		// load the language itself
-		const languageSource = this.loadFileSource(language);
+		const languageSource = this.loadComponentSource(language);
 		context.Prism = this.runFileWithContext(languageSource, { Prism: context.Prism }).Prism;
 		context.loadedLanguages.push(language);
 
@@ -85,8 +86,32 @@ module.exports = {
 	 * @returns {Prism}
 	 */
 	createEmptyPrism() {
-		const coreSource = this.loadFileSource("core");
+		const coreSource = this.loadComponentSource("core");
 		const context = this.runFileWithContext(coreSource);
+
+		for (const testSource of this.getChecks().map(src => this.loadFileSource(src))) {
+			context.Prism = this.runFileWithContext(testSource, {
+				Prism: context.Prism,
+				/**
+				 * A pseudo require function for the checks.
+				 *
+				 * This function will behave like the regular `require` in real modules when called form a check file.
+				 *
+				 * @param {string} id The id of relative path to require.
+				 */
+				require(id) {
+					if (id.startsWith('./')) {
+						// We have to rewrite relative paths starting with './'
+						return require('./../checks/' + id.substr(2));
+					} else {
+						// This might be an id like 'mocha' or 'fs' or a relative path starting with '../'.
+						// In both cases we don't have to change anything.
+						return require(id);
+					}
+				}
+			}).Prism;
+		}
+
 		return context.Prism;
 	},
 
@@ -101,14 +126,37 @@ module.exports = {
 
 
 	/**
-	 * Loads the given file source as string
+	 * Loads the given component's file source as string
 	 *
 	 * @private
 	 * @param {string} name
 	 * @returns {string}
 	 */
-	loadFileSource(name) {
-		return this.fileSourceCache[name] = this.fileSourceCache[name] || fs.readFileSync(__dirname + "/../../components/prism-" + name + ".js", "utf8");
+	loadComponentSource(name) {
+		return this.loadFileSource(__dirname + "/../../components/prism-" + name + ".js");
+	},
+
+	/**
+	 * Loads the given file source as string
+	 *
+	 * @private
+	 * @param {string} src
+	 * @returns {string}
+	 */
+	loadFileSource(src) {
+		return this.fileSourceCache[src] = this.fileSourceCache[src] || fs.readFileSync(src, "utf8");
+	},
+
+
+	checkCache: null,
+
+	/**
+	 * Returns a list of files which add additional checks to Prism functions.
+	 *
+	 * @returns {ReadonlyArray<string>}
+	 */
+	getChecks() {
+		return this.checkCache = this.checkCache || getAllFiles(__dirname + "/../checks");
 	},