UNPKG

infusion

Version:

Infusion is an application framework for developing flexible stuff with JavaScript

369 lines (312 loc) 13.4 kB
/* Copyright The Infusion copyright holders See the AUTHORS.md file at the top-level directory of this distribution and at https://github.com/fluid-project/infusion/raw/main/AUTHORS.md. Licensed under the Educational Community License (ECL), Version 2.0 or the New BSD license. You may not use this file except in compliance with one these Licenses. You may obtain a copy of the ECL 2.0 License and BSD License at https://github.com/fluid-project/infusion/raw/main/Infusion-LICENSE.txt */ /* global QUnit */ "use strict"; var jqUnit = jqUnit || {}; (function ($) { var QUnitPassthroughs = ["module", "asyncTest", "throws", "raises", "start", "stop", "expect"]; QUnit.config.reorder = false; // defeat this QUnit feature which frequently just causes confusion for (var i = 0; i < QUnitPassthroughs.length; ++i) { var method = QUnitPassthroughs[i]; jqUnit[method] = QUnit[method]; window[method] = undefined; // work around IE8 bug http://stackoverflow.com/questions/1073414/deleting-a-window-property-in-ie } jqUnit.failureHandler = function (args/*, activity*/) { if (QUnit.config.current) { QUnit.ok(false, "Assertion failure (see console.log for expanded message): ".concat(args)); } }; fluid.failureEvent.addListener(jqUnit.failureHandler, "jqUnit", "before:fail"); // Helpful utility for creating multiple test target components compactly fluid.makeComponents = function (components) { fluid.each(components, function (value, key) { var options = { gradeNames: fluid.makeArray(value) }; fluid.defaults(key, options); }); }; /* * Keeps track of the order of function invocations. The transcript contains information about * each invocation, including its name and the arguments that were supplied to it. */ jqUnit.invocationTracker = function (options) { var that = {}; that.runTestsOnFunctionNamed = options ? options.runTestsOnFunctionNamed : undefined; that.testBody = options ? options.testBody : undefined; /** * An array containing an ordered list of details about each function invocation. */ that.transcript = []; /** * Called to listen for a function's invocation and record its details in the transcript. * * @param {Object} fnName - the function name to listen for * @param {Object} onObject - the object on which to invoke the method */ that.intercept = function (fnName, onObject) { onObject = onObject || window; var wrappedFn = onObject[fnName]; onObject[fnName] = function () { that.transcript.push({ name: fnName, args: arguments }); wrappedFn.apply(onObject, arguments); if (fnName === that.runTestsOnFunctionNamed) { that.testBody(that.transcript); } }; }; /** * Intercepts all the functions on the specified object. * * @param {Object} obj - The object whose functions should be intercepted. */ that.interceptAll = function (obj) { for (var fnName in obj) { that.intercept(fnName, obj); } }; that.clearTranscript = function () { that.transcript = []; }; return that; }; var messageSuffix = ""; var processMessage = function (message) { return message + messageSuffix; }; var pok = function (condition, message) { QUnit.ok(condition, processMessage(message)); }; // unsupported, NON-API function jqUnit.okWithPrefix = pok; // unsupported, NON-API function jqUnit.setMessageSuffix = function (suffix) { messageSuffix = suffix; }; /*********************** * xUnit Compatibility * ***********************/ var jsUnitCompat = { fail: function (msg) { pok(false, msg); }, assert: function (msg) { pok(true, msg); }, assertEquals: function (msg, expected, actual) { QUnit.strictEqual(actual, expected, processMessage(msg)); }, assertNotEquals: function (msg, unexpected, actual) { QUnit.notStrictEqual(actual, unexpected, msg); }, assertTrue: function (msg, value) { pok(value, msg); }, assertFalse: function (msg, value) { pok(!value, msg); }, assertUndefined: function (msg, value) { pok(value === undefined, msg); }, assertNotUndefined: function (msg, value) { pok(value !== undefined, msg); }, assertValue: function (msg, value) { pok(value !== null && value !== undefined, msg); }, assertNoValue: function (msg, value) { pok(value === null || value === undefined, msg); }, assertNull: function (msg, value) { QUnit.equal(value, null, processMessage(msg)); }, assertNotNull: function (msg, value) { pok(value !== null, msg); }, assertDeepEq: function (msg, expected, actual) { if (fluid.isPrimitive(expected) || fluid.isPrimitive(actual)) { jqUnit.assertEquals(msg, expected, actual); } else { QUnit.propEqual(actual, expected, processMessage(msg)); } }, assertDeepNeq: function (msg, unexpected, actual) { if (fluid.isPrimitive(unexpected) || fluid.isPrimitive(actual)) { jqUnit.assertNotEquals(msg, unexpected, actual); } else { QUnit.notPropEqual(actual, unexpected, processMessage(msg)); } }, // This version of "expect" offers the cumulative semantic we desire expect: function (number) { var oldExpect = QUnit.expect(); QUnit.expect(number + oldExpect); } }; // Mix these compatibility functions into the jqUnit namespace. $.extend(jqUnit, jsUnitCompat); // Implement promise wrapper for FLUID-6577 - this is actually consistent with QUnit's behaviour since 1.16.0 jqUnit.test = function (name, func) { QUnit.asyncTest(name, function () { var promise = func(); if (promise) { promise.then(jqUnit.start, function (err) { jqUnit.fail(err); jqUnit.start(); }); } else { jqUnit.start(); } }); }; /** Sort an old renderer component tree into canonical order, to facilitate comparison with * deepEq */ jqUnit.sortOldRendererComponentTree = function (tree) { function comparator(ela, elb) { var ida = ela.ID || ""; var idb = elb.ID || ""; var cola = ida.indexOf(":") === -1; var colb = idb.indexOf(":") === -1; if (cola && colb) { // if neither has a colon, compare by IDs if they have IDs return ida.localeCompare(idb); } else { return cola - colb; } } if (fluid.isArrayable(tree)) { tree.sort(comparator); } fluid.each(tree, function (value) { if (!fluid.isPrimitive(value)) { jqUnit.sortOldRendererComponentTree(value); } }); }; /** The effect of jqUnit.flattenMergedSubcomponentOptions at the next level of nesting - assumes * that it is given an options block with `components` at the root */ jqUnit.flattenMergedSubcomponents = function (components) { return fluid.transform(components, function (component) { return component[0]; }); }; /** Undoes the effect of the new mergePolicy implemented post FLUID-5614, and flattens the array of * merged subcomponents to match the structure that is written in plain options. E.g. a structure * ``` * { * someOption: 4, * components: { * myComponent: [ * { * type: "fluid.component" * } * ] * } * ``` * will be flattened into * ``` * someOption: 4, * components: { * myComponent: { * type: "fluid.component" * } * } * ``` * @param {Object} tree - A tree of component options * @return {Object} A deep-cloned tree with arrays held in subcomponent entries flattened to hold their 0th entries */ jqUnit.flattenMergedSubcomponentOptions = function (tree) { return fluid.transform(tree, function (value, key) { if (key !== "components") { return value; } else { return jqUnit.flattenMergedSubcomponents(value); } }); }; /** A canonicalisation function which will cause any functions encountered in the tree to compare as equal, * as sent to `jqUnit.assertCanoniseEqual`. This returns an deep cloned object tree where every function * encountered in the tree is replaced by `fluid.identity` * @param {Object} tree - The object tree to be canonicalised * @return {Object} A deep-cloned version of `tree` with every function handle replaced by `fluid.identity` */ jqUnit.canonicaliseFunctions = function (tree) { return fluid.transform(tree, function (value) { if (fluid.isPrimitive(value)) { if (typeof(value) === "function") { return fluid.identity; } else { return value; } } else { return jqUnit.canonicaliseFunctions(value); } }); }; /** Assert that two trees are equal after applying a "canonicalisation function". This can be used in * cases where the criterion for equivalence is looser than exact object equivalence - for example, * when using renderer trees, "jqUnit.sortOldRendererComponentTree" can be used for canonFunc", or in the case * of a resourceSpec, "jqUnit.canonicaliseFunctions". **/ jqUnit.assertCanoniseEqual = function (message, expected, actual, canonFunc) { var expected2 = canonFunc(expected); var actual2 = canonFunc(actual); jqUnit.assertDeepEq(message, expected2, actual2); }; /** Assert that the actual value object is a superset (considered in terms of shallow key coincidence) of the * expected value object (this method is the one that will be most often used in practice). "Left hand" (expected) is a subset of actual. **/ jqUnit.assertLeftHand = function (message, expected, actual) { jqUnit.assertDeepEq(message, expected, fluid.filterKeys(actual, fluid.keys(expected))); }; /** Assert that the actual value object is a subset of the expected value object **/ jqUnit.assertRightHand = function (message, expected, actual) { jqUnit.assertDeepEq(message, fluid.filterKeys(expected, fluid.keys(actual)), actual); }; /** Assert that the supplied callback will produce a framework diagnostic, containing the supplied text * somewhere in its error message - that is, the framework will invoke fluid.fail with a message containing * <code>errorText</code>. * @param {String} message - The message prefix to be supplied for all the assertions this function issues * @param {Function} toInvoke - A no-arg function holding the code to be tested for emission of the diagnostic * @param {String} errorTexts - or {Array of String} Either a single string or array of strings which the <code>message</code> field * of the thrown exception will be tested against - each string must appear as a substring in the text */ jqUnit.expectFrameworkDiagnostic = function (message, toInvoke, errorTexts) { errorTexts = fluid.makeArray(errorTexts); var gotFailure; var capturedActivity; var captureActivity = function (args, activity) { capturedActivity = fluid.renderActivity(activity).map(fluid.prettyPrintJSON).join(""); }; try { fluid.failureEvent.addListener(fluid.identity, "jqUnit"); fluid.failureEvent.addListener(captureActivity, "captureActivity", "before:fail"); jqUnit.expect(errorTexts.length); toInvoke(); } catch (e) { gotFailure = true; if (!(e instanceof fluid.FluidError)) { jqUnit.fail(message + " - received non-framework exception"); throw e; } var fullText = e.message + capturedActivity; fluid.each(errorTexts, function (errorText) { jqUnit.assertTrue(message + " - message text must contain " + errorText, fullText.indexOf(errorText) >= 0); }); } finally { if (!gotFailure) { jqUnit.fail("No failure received for test " + message); } fluid.failureEvent.removeListener("jqUnit"); fluid.failureEvent.removeListener("captureActivity"); } }; })(jQuery);