|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 "use strict"; |
|
6 |
|
7 module.metadata = { |
|
8 "stability": "unstable" |
|
9 }; |
|
10 |
|
11 const { Cu } = require("chrome"); |
|
12 const { Task } = Cu.import("resource://gre/modules/Task.jsm", {}); |
|
13 const { defer } = require("sdk/core/promise"); |
|
14 const BaseAssert = require("sdk/test/assert").Assert; |
|
15 const { isFunction, isObject } = require("sdk/lang/type"); |
|
16 |
|
17 exports.Assert = BaseAssert; |
|
18 |
|
19 function extend(target) { |
|
20 let descriptor = {} |
|
21 Array.slice(arguments, 1).forEach(function(source) { |
|
22 Object.getOwnPropertyNames(source).forEach(function onEach(name) { |
|
23 descriptor[name] = Object.getOwnPropertyDescriptor(source, name); |
|
24 }); |
|
25 }); |
|
26 return Object.create(target, descriptor); |
|
27 } |
|
28 |
|
29 /** |
|
30 * Function takes test `suite` object in CommonJS format and defines all of the |
|
31 * tests from that suite and nested suites in a jetpack format on a given |
|
32 * `target` object. Optionally third argument `prefix` can be passed to prefix |
|
33 * all the test names. |
|
34 */ |
|
35 function defineTestSuite(target, suite, prefix) { |
|
36 prefix = prefix || ""; |
|
37 // If suite defines `Assert` that's what `assert` object have to be created |
|
38 // from and passed to a test function (This allows custom assertion functions) |
|
39 // See for details: http://wiki.commonjs.org/wiki/Unit_Testing/1.1 |
|
40 let Assert = suite.Assert || BaseAssert; |
|
41 // Going through each item in the test suite and wrapping it into a |
|
42 // Jetpack test format. |
|
43 Object.keys(suite).forEach(function(key) { |
|
44 // If name starts with test then it's a test function or suite. |
|
45 if (key.indexOf("test") === 0) { |
|
46 let test = suite[key]; |
|
47 |
|
48 // For each test function so we create a wrapper test function in a |
|
49 // jetpack format and copy that to a `target` exports. |
|
50 if (isFunction(test)) { |
|
51 |
|
52 // Since names of the test may match across suites we use full object |
|
53 // path as a name to avoid overriding same function. |
|
54 target[prefix + key] = function(options) { |
|
55 |
|
56 // Creating `assert` functions for this test. |
|
57 let assert = Assert(options); |
|
58 assert.end = () => options.done(); |
|
59 |
|
60 // If test function is a generator use a task JS to allow yield-ing |
|
61 // style test runs. |
|
62 if (test.isGenerator && test.isGenerator()) { |
|
63 options.waitUntilDone(); |
|
64 Task.spawn(test.bind(null, assert)). |
|
65 then(null, assert.fail). |
|
66 then(assert.end); |
|
67 } |
|
68 |
|
69 // If CommonJS test function expects more than one argument |
|
70 // it means that test is async and second argument is a callback |
|
71 // to notify that test is finished. |
|
72 else if (1 < test.length) { |
|
73 |
|
74 // Letting test runner know that test is executed async and |
|
75 // creating a callback function that CommonJS tests will call |
|
76 // once it's done. |
|
77 options.waitUntilDone(); |
|
78 test(assert, function() { |
|
79 options.done(); |
|
80 }); |
|
81 } |
|
82 |
|
83 // Otherwise CommonJS test is synchronous so we call it only with |
|
84 // one argument. |
|
85 else { |
|
86 test(assert); |
|
87 } |
|
88 } |
|
89 } |
|
90 |
|
91 // If it's an object then it's a test suite containing test function |
|
92 // and / or nested test suites. In that case we just extend prefix used |
|
93 // and call this function to copy and wrap tests from nested suite. |
|
94 else if (isObject(test)) { |
|
95 // We need to clone `tests` instead of modifying it, since it's very |
|
96 // likely that it is frozen (usually test suites imported modules). |
|
97 test = extend(Object.prototype, test, { |
|
98 Assert: test.Assert || Assert |
|
99 }); |
|
100 defineTestSuite(target, test, prefix + key + "."); |
|
101 } |
|
102 } |
|
103 }); |
|
104 } |
|
105 |
|
106 /** |
|
107 * This function is a CommonJS test runner function, but since Jetpack test |
|
108 * runner and test format is different from CommonJS this function shims given |
|
109 * `exports` with all its tests into a Jetpack test format so that the built-in |
|
110 * test runner will be able to run CommonJS test without manual changes. |
|
111 */ |
|
112 exports.run = function run(exports) { |
|
113 |
|
114 // We can't leave old properties on exports since those are test in a CommonJS |
|
115 // format that why we move everything to a new `suite` object. |
|
116 let suite = {}; |
|
117 Object.keys(exports).forEach(function(key) { |
|
118 suite[key] = exports[key]; |
|
119 delete exports[key]; |
|
120 }); |
|
121 |
|
122 // Now we wrap all the CommonJS tests to a Jetpack format and define |
|
123 // those to a given `exports` object since that where jetpack test runner |
|
124 // will look for them. |
|
125 defineTestSuite(exports, suite); |
|
126 }; |