testem 3.20.2 → 3.21.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +128 -43
- package/lib/api.js +1 -1
- package/lib/reporters/dot_reporter.js +7 -7
- package/lib/runner_pages/custom.js +18 -0
- package/lib/runner_pages/directory.js +12 -0
- package/lib/runner_pages/html.js +45 -0
- package/lib/runner_pages/index.js +19 -0
- package/{views/jasminerunner.mustache → lib/runner_pages/jasmine.js} +11 -5
- package/lib/runner_pages/jasmine2.js +38 -0
- package/lib/runner_pages/mocha.js +25 -0
- package/lib/runner_pages/mocha_chai.js +83 -0
- package/lib/runner_pages/qunit.js +22 -0
- package/{views/taprunner.mustache → lib/runner_pages/tap.js} +9 -5
- package/lib/server/index.js +74 -38
- package/lib/utils/jasmine1_deprecation.js +27 -0
- package/lib/utils/mustache_test_page_deprecation.js +27 -0
- package/lib/utils/promises.js +6 -1
- package/lib/utils/report-file.js +7 -13
- package/lib/utils/runner_framework_assets.js +154 -0
- package/package.json +15 -23
- package/public/testem/jasmine_adapter.js +7 -1
- package/views/customrunner.mustache +0 -12
- package/views/directorylisting.mustache +0 -4
- package/views/jasmine2runner.mustache +0 -20
- package/views/mochachairunner.mustache +0 -19
- package/views/mocharunner.mustache +0 -19
- package/views/qunitrunner.mustache +0 -16
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
const { scriptTags, styleTags, loadScriptCalls } = require('./html');
|
|
2
|
+
|
|
3
|
+
const LOAD_SCRIPT_HELPER = `function applyScriptAttr(el, attr) {
|
|
4
|
+
attr = String(attr).trim();
|
|
5
|
+
var eq = attr.indexOf('=');
|
|
6
|
+
if (eq === -1) {
|
|
7
|
+
el.setAttribute(attr, '');
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
var name = attr.slice(0, eq);
|
|
11
|
+
var value = attr.slice(eq + 1).replace(/^["']|["']$/g, '');
|
|
12
|
+
el.setAttribute(name, value);
|
|
13
|
+
}
|
|
14
|
+
function loadScript(src) {
|
|
15
|
+
var attrs = Array.prototype.slice.call(arguments, 1);
|
|
16
|
+
return new Promise(function(resolve, reject) {
|
|
17
|
+
var s = document.createElement('script');
|
|
18
|
+
s.src = src;
|
|
19
|
+
attrs.forEach(function(attr) { applyScriptAttr(s, attr); });
|
|
20
|
+
s.onload = resolve;
|
|
21
|
+
s.onerror = reject;
|
|
22
|
+
document.body.appendChild(s);
|
|
23
|
+
});
|
|
24
|
+
}`;
|
|
25
|
+
|
|
26
|
+
function renderUmd({
|
|
27
|
+
mochaCss, mochaJs, chaiJs, scripts, styles, footer_scripts
|
|
28
|
+
}) {
|
|
29
|
+
return `<!doctype html>
|
|
30
|
+
<html>
|
|
31
|
+
<head>
|
|
32
|
+
<title>Test'em</title>
|
|
33
|
+
<link rel="stylesheet" href="${mochaCss}">
|
|
34
|
+
<script src="${mochaJs}"></script>
|
|
35
|
+
<script src="${chaiJs}"></script>
|
|
36
|
+
<script src="/testem.js"></script>
|
|
37
|
+
<script>mocha.setup('bdd')</script>
|
|
38
|
+
${scriptTags(scripts)}
|
|
39
|
+
${styleTags(styles)}
|
|
40
|
+
</head>
|
|
41
|
+
<body>
|
|
42
|
+
<div id="mocha"></div>
|
|
43
|
+
${scriptTags(footer_scripts)}
|
|
44
|
+
<script>
|
|
45
|
+
mocha.run()
|
|
46
|
+
</script>
|
|
47
|
+
</body>
|
|
48
|
+
</html>`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function renderEsm({
|
|
52
|
+
mochaCss, mochaJs, chaiJs, scripts, styles, footer_scripts
|
|
53
|
+
}) {
|
|
54
|
+
const awaits = [loadScriptCalls(scripts), loadScriptCalls(footer_scripts)]
|
|
55
|
+
.filter(Boolean)
|
|
56
|
+
.join('\n');
|
|
57
|
+
|
|
58
|
+
return `<!doctype html>
|
|
59
|
+
<html>
|
|
60
|
+
<head>
|
|
61
|
+
<title>Test'em</title>
|
|
62
|
+
<link rel="stylesheet" href="${mochaCss}">
|
|
63
|
+
<script src="${mochaJs}"></script>
|
|
64
|
+
<script src="/testem.js"></script>
|
|
65
|
+
<script>mocha.setup('bdd')</script>
|
|
66
|
+
${styleTags(styles)}
|
|
67
|
+
</head>
|
|
68
|
+
<body>
|
|
69
|
+
<div id="mocha"></div>
|
|
70
|
+
<script type="module">
|
|
71
|
+
import * as chai from '${chaiJs}';
|
|
72
|
+
globalThis.chai = chai;
|
|
73
|
+
${LOAD_SCRIPT_HELPER}
|
|
74
|
+
${awaits}
|
|
75
|
+
globalThis.mocha.run();
|
|
76
|
+
</script>
|
|
77
|
+
</body>
|
|
78
|
+
</html>`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
module.exports = function renderMochaChaiRunner(data) {
|
|
82
|
+
return data.chaiLocal ? renderEsm(data) : renderUmd(data);
|
|
83
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const { scriptTags, styleTags } = require('./html');
|
|
2
|
+
|
|
3
|
+
module.exports = function renderQunitRunner({
|
|
4
|
+
qunitJs, qunitCss, scripts, styles, footer_scripts
|
|
5
|
+
}) {
|
|
6
|
+
return `<!doctype html>
|
|
7
|
+
<html>
|
|
8
|
+
<head>
|
|
9
|
+
<title>Test'em</title>
|
|
10
|
+
<script src="${qunitJs}"></script>
|
|
11
|
+
<script src="/testem.js"></script>
|
|
12
|
+
${scriptTags(scripts)}
|
|
13
|
+
<link rel="stylesheet" href="${qunitCss}"/>
|
|
14
|
+
${styleTags(styles)}
|
|
15
|
+
</head>
|
|
16
|
+
<body>
|
|
17
|
+
<div id="qunit"></div>
|
|
18
|
+
<div id="qunit-fixture"></div>
|
|
19
|
+
</body>
|
|
20
|
+
${scriptTags(footer_scripts)}
|
|
21
|
+
</html>`;
|
|
22
|
+
};
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
const { scriptTags } = require('./html');
|
|
2
|
+
|
|
3
|
+
module.exports = function renderTapRunner({ scripts, footer_scripts }) {
|
|
4
|
+
return `<!doctype html>
|
|
2
5
|
<html>
|
|
3
6
|
<head>
|
|
4
7
|
<title>Test'em</title>
|
|
@@ -11,11 +14,12 @@
|
|
|
11
14
|
var log = document.getElementById('log')
|
|
12
15
|
Testem.handleConsoleMessage = function(msg){
|
|
13
16
|
Testem.emit('tap', msg)
|
|
14
|
-
log.appendChild(document.createTextNode(msg + '
|
|
17
|
+
log.appendChild(document.createTextNode(msg + '\\n'))
|
|
15
18
|
return false
|
|
16
19
|
}
|
|
17
20
|
</script>
|
|
18
|
-
{
|
|
19
|
-
{
|
|
21
|
+
${scriptTags(scripts)}
|
|
22
|
+
${scriptTags(footer_scripts)}
|
|
20
23
|
</body>
|
|
21
|
-
</html
|
|
24
|
+
</html>`;
|
|
25
|
+
};
|
package/lib/server/index.js
CHANGED
|
@@ -19,8 +19,22 @@ var EventEmitter = require('events').EventEmitter;
|
|
|
19
19
|
var Mustache = require('consolidate').mustache;
|
|
20
20
|
var http = require('http');
|
|
21
21
|
var https = require('https');
|
|
22
|
-
var httpProxy = require('http-proxy');
|
|
23
22
|
const { fromCallback, each } = require('../utils/promises');
|
|
23
|
+
const { resolveRunnerFrameworkAssets } = require('../utils/runner_framework_assets');
|
|
24
|
+
const { warnJasmine1Framework } = require('../utils/jasmine1_deprecation');
|
|
25
|
+
const { warnMustacheTestPage } = require('../utils/mustache_test_page_deprecation');
|
|
26
|
+
const { convertToPosix } = require('../utils/posix');
|
|
27
|
+
const { renderRunner, renderDirectoryListing } = require('../runner_pages');
|
|
28
|
+
|
|
29
|
+
function browserScriptEntry(file) {
|
|
30
|
+
if (!file || !file.src) {
|
|
31
|
+
return file;
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
src: convertToPosix(file.src),
|
|
35
|
+
attrs: file.attrs
|
|
36
|
+
};
|
|
37
|
+
}
|
|
24
38
|
|
|
25
39
|
class Server extends EventEmitter {
|
|
26
40
|
constructor(config) {
|
|
@@ -35,7 +49,12 @@ class Server extends EventEmitter {
|
|
|
35
49
|
this.nextSocketId = 0;
|
|
36
50
|
}
|
|
37
51
|
|
|
38
|
-
start() {
|
|
52
|
+
async start() {
|
|
53
|
+
if (!this.createProxyServer) {
|
|
54
|
+
const { createProxyServer } = await import('httpxy');
|
|
55
|
+
this.createProxyServer = createProxyServer;
|
|
56
|
+
}
|
|
57
|
+
|
|
39
58
|
this.createExpress();
|
|
40
59
|
|
|
41
60
|
// Start the server!
|
|
@@ -206,34 +225,40 @@ class Server extends EventEmitter {
|
|
|
206
225
|
}
|
|
207
226
|
|
|
208
227
|
shouldProxy(req, opts) {
|
|
209
|
-
|
|
228
|
+
//Only apply filtering logic if 'onlyContentTypes' key is present
|
|
229
|
+
if (!('onlyContentTypes' in opts)) {
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
var onlyContentTypes = [].concat(opts.onlyContentTypes);
|
|
210
234
|
var acceptCheck = [
|
|
211
235
|
'html',
|
|
212
236
|
'css',
|
|
213
237
|
'javascript'
|
|
214
|
-
];
|
|
238
|
+
].concat(onlyContentTypes, 'text');
|
|
215
239
|
|
|
216
|
-
//
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
240
|
+
// req.accepts returns the single best matching type as a string, or false
|
|
241
|
+
// when the request accepts none of them.
|
|
242
|
+
var accepts = req.accepts(acceptCheck);
|
|
243
|
+
return accepts !== false && onlyContentTypes.indexOf(accepts) !== -1;
|
|
244
|
+
}
|
|
220
245
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
246
|
+
sendProxyError(res, err) {
|
|
247
|
+
const message = (err && (err.message || err.code)) || 'Proxy error';
|
|
248
|
+
log.error(message);
|
|
249
|
+
// WebSocket upgrades pass a raw socket with no Express res.
|
|
250
|
+
if (res && res.status && !res.headersSent) {
|
|
251
|
+
res.status(500).json({ error: message });
|
|
226
252
|
}
|
|
227
|
-
return false;
|
|
228
253
|
}
|
|
229
254
|
|
|
230
255
|
configureProxy(app) {
|
|
231
256
|
var proxies = this.config.get('proxies');
|
|
232
257
|
if (proxies) {
|
|
233
|
-
this.proxy =
|
|
258
|
+
this.proxy = this.createProxyServer({ changeOrigin: true });
|
|
234
259
|
|
|
235
260
|
this.proxy.on('error', (err, req, res) => {
|
|
236
|
-
res
|
|
261
|
+
this.sendProxyError(res, err);
|
|
237
262
|
});
|
|
238
263
|
|
|
239
264
|
Object.keys(proxies).forEach((url) => {
|
|
@@ -241,7 +266,9 @@ class Server extends EventEmitter {
|
|
|
241
266
|
// set up proxy for WebSocket
|
|
242
267
|
this.server.on('upgrade', (req, socket, head) => {
|
|
243
268
|
if (req.url.startsWith(url)) {
|
|
244
|
-
|
|
269
|
+
var wsOpts = Object.assign({}, proxies[url]);
|
|
270
|
+
delete wsOpts.onlyContentTypes;
|
|
271
|
+
this.proxy.ws(req, socket, wsOpts, head).catch(() => {});
|
|
245
272
|
}
|
|
246
273
|
});
|
|
247
274
|
return;
|
|
@@ -252,15 +279,21 @@ class Server extends EventEmitter {
|
|
|
252
279
|
let wIdx = 0;
|
|
253
280
|
const proxyPath =
|
|
254
281
|
url.replace(/\*/g, () => `:_proxyW${wIdx++}`).replace(/\/$/, '') + '{/*_proxyRest}';
|
|
255
|
-
app.all(proxyPath, (req, res, next) => {
|
|
282
|
+
app.all(proxyPath, async (req, res, next) => {
|
|
256
283
|
var opts = proxies[url];
|
|
257
284
|
if (this.shouldProxy(req, opts)) {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
delete
|
|
285
|
+
var proxyOpts = Object.assign({}, opts);
|
|
286
|
+
if (proxyOpts.host) {
|
|
287
|
+
proxyOpts.target = `http://${proxyOpts.host}:${proxyOpts.port}`;
|
|
288
|
+
delete proxyOpts.host;
|
|
289
|
+
delete proxyOpts.port;
|
|
290
|
+
}
|
|
291
|
+
delete proxyOpts.onlyContentTypes;
|
|
292
|
+
try {
|
|
293
|
+
await this.proxy.web(req, res, proxyOpts);
|
|
294
|
+
} catch (err) {
|
|
295
|
+
this.sendProxyError(res, err);
|
|
262
296
|
}
|
|
263
|
-
this.proxy.web(req, res, opts);
|
|
264
297
|
} else {
|
|
265
298
|
next();
|
|
266
299
|
}
|
|
@@ -273,20 +306,23 @@ class Server extends EventEmitter {
|
|
|
273
306
|
var config = this.config;
|
|
274
307
|
var framework = config.get('framework') || 'jasmine';
|
|
275
308
|
var css_files = config.get('css_files');
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
'
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
res.render(`${__dirname}/../../views/${templateFile}`, {
|
|
286
|
-
scripts: files,
|
|
309
|
+
if (framework === 'jasmine') {
|
|
310
|
+
warnJasmine1Framework();
|
|
311
|
+
}
|
|
312
|
+
var frameworkAssets = resolveRunnerFrameworkAssets(
|
|
313
|
+
config.cwd(),
|
|
314
|
+
config.get('routes') || config.get('route')
|
|
315
|
+
);
|
|
316
|
+
var html = renderRunner(framework, Object.assign({
|
|
317
|
+
scripts: (files || []).map(browserScriptEntry),
|
|
287
318
|
styles: css_files,
|
|
288
|
-
footer_scripts: footerScripts
|
|
289
|
-
});
|
|
319
|
+
footer_scripts: (footerScripts || []).map(browserScriptEntry)
|
|
320
|
+
}, frameworkAssets));
|
|
321
|
+
if (html === null) {
|
|
322
|
+
res.status(500).send('Failed to lookup view "' + framework + '"');
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
res.type('html').send(html);
|
|
290
326
|
}
|
|
291
327
|
|
|
292
328
|
renderDefaultTestPage(req, res) {
|
|
@@ -394,6 +430,7 @@ class Server extends EventEmitter {
|
|
|
394
430
|
res.write('403 Forbidden');
|
|
395
431
|
res.end();
|
|
396
432
|
} else if (ext === '.mustache') {
|
|
433
|
+
warnMustacheTestPage();
|
|
397
434
|
config.getTemplateData((err, data) => {
|
|
398
435
|
res.render(filePath, data);
|
|
399
436
|
this.emit('file-requested', filePath);
|
|
@@ -410,8 +447,7 @@ class Server extends EventEmitter {
|
|
|
410
447
|
this.emit('file-requested', filePath);
|
|
411
448
|
if (stat.isDirectory()) {
|
|
412
449
|
return fs.promises.readdir(filePath).then(files => {
|
|
413
|
-
|
|
414
|
-
res.render(dirListingPage, { files: files });
|
|
450
|
+
res.type('html').send(renderDirectoryListing({ files: files }));
|
|
415
451
|
});
|
|
416
452
|
}
|
|
417
453
|
res.sendFile(filePath, sendOptions);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const log = require('../log');
|
|
2
|
+
|
|
3
|
+
const JASMINE1_FRAMEWORK_WARNING =
|
|
4
|
+
'Testem: framework "jasmine" (Jasmine 1.x) is deprecated and will be removed in the next version of Testem. Use "framework": "jasmine2" with the jasmine-core package, or load Jasmine 2+ on a custom test_page.';
|
|
5
|
+
|
|
6
|
+
let warned = false;
|
|
7
|
+
|
|
8
|
+
function warnJasmine1Framework() {
|
|
9
|
+
if (warned) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
warned = true;
|
|
13
|
+
log.warn(JASMINE1_FRAMEWORK_WARNING);
|
|
14
|
+
process.emitWarning(JASMINE1_FRAMEWORK_WARNING, {
|
|
15
|
+
code: 'TESTEM_JASMINE1_DEPRECATED'
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function resetJasmine1FrameworkWarning() {
|
|
20
|
+
warned = false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = {
|
|
24
|
+
JASMINE1_FRAMEWORK_WARNING,
|
|
25
|
+
warnJasmine1Framework,
|
|
26
|
+
resetJasmine1FrameworkWarning
|
|
27
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const log = require('../log');
|
|
2
|
+
|
|
3
|
+
const MUSTACHE_TEST_PAGE_WARNING =
|
|
4
|
+
'Testem: naming test_page with a .mustache extension is deprecated and will be removed in Testem 4. Use a static HTML page and include <script src="/testem.js"></script>.';
|
|
5
|
+
|
|
6
|
+
let warned = false;
|
|
7
|
+
|
|
8
|
+
function warnMustacheTestPage() {
|
|
9
|
+
if (warned) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
warned = true;
|
|
13
|
+
log.warn(MUSTACHE_TEST_PAGE_WARNING);
|
|
14
|
+
process.emitWarning(MUSTACHE_TEST_PAGE_WARNING, {
|
|
15
|
+
code: 'TESTEM_MUSTACHE_TEST_PAGE_DEPRECATED'
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function resetMustacheTestPageWarning() {
|
|
20
|
+
warned = false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = {
|
|
24
|
+
MUSTACHE_TEST_PAGE_WARNING,
|
|
25
|
+
warnMustacheTestPage,
|
|
26
|
+
resetMustacheTestPageWarning
|
|
27
|
+
};
|
package/lib/utils/promises.js
CHANGED
|
@@ -123,13 +123,18 @@ function mapLimit(arr, concurrency, fn) {
|
|
|
123
123
|
return Promise.all(workers).then(() => results);
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
async function retry(fn, { max_tries = 3 } = {}) {
|
|
126
|
+
async function retry(fn, { max_tries = 3, interval = 0, backoff = 1 } = {}) {
|
|
127
127
|
let lastErr;
|
|
128
|
+
let wait = interval;
|
|
128
129
|
for (let i = 0; i < max_tries; i++) {
|
|
129
130
|
try {
|
|
130
131
|
return await fn();
|
|
131
132
|
} catch (e) {
|
|
132
133
|
lastErr = e;
|
|
134
|
+
if (i < max_tries - 1 && wait > 0) {
|
|
135
|
+
await delay(wait);
|
|
136
|
+
wait *= backoff;
|
|
137
|
+
}
|
|
133
138
|
}
|
|
134
139
|
}
|
|
135
140
|
throw lastErr;
|
package/lib/utils/report-file.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
|
-
const mkdirp = require('mkdirp');
|
|
6
5
|
const PassThrough = require('stream').PassThrough;
|
|
7
6
|
module.exports = class ReportFile {
|
|
8
7
|
constructor(reportFile) {
|
|
@@ -10,25 +9,20 @@ module.exports = class ReportFile {
|
|
|
10
9
|
|
|
11
10
|
this.outputStream = new PassThrough();
|
|
12
11
|
|
|
13
|
-
|
|
12
|
+
fs.mkdirSync(path.dirname(path.resolve(reportFile)), { recursive: true });
|
|
14
13
|
|
|
15
14
|
this.outputStream = fs.createWriteStream(reportFile, { flags: 'w+' });
|
|
16
15
|
|
|
17
|
-
let alreadyEnded = false;
|
|
18
|
-
function finish(data) {
|
|
19
|
-
if (!alreadyEnded) {
|
|
20
|
-
alreadyEnded = true;
|
|
21
|
-
this.outputStream.end(data);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
this.outputStream.on('end', finish);
|
|
26
|
-
this.outputStream.on('error', finish);
|
|
27
|
-
|
|
28
16
|
this.closePromise = new Promise((resolve, reject) => {
|
|
29
17
|
this.outputStream.on('finish', resolve);
|
|
30
18
|
this.outputStream.on('error', reject);
|
|
31
19
|
});
|
|
20
|
+
|
|
21
|
+
// The stream can fail long before anything calls close() - opening an
|
|
22
|
+
// unwritable report file rejects on the next tick. Attach a no-op handler
|
|
23
|
+
// so that rejection is not reported as unhandled; close() still returns
|
|
24
|
+
// closePromise, so the error is surfaced to the caller.
|
|
25
|
+
this.closePromise.catch(() => {});
|
|
32
26
|
}
|
|
33
27
|
|
|
34
28
|
close() {
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const CDN = {
|
|
5
|
+
mochaJs: '//cdnjs.cloudflare.com/ajax/libs/mocha/2.3.4/mocha.js',
|
|
6
|
+
mochaCss: '//cdnjs.cloudflare.com/ajax/libs/mocha/2.3.4/mocha.css',
|
|
7
|
+
chaiJs: '//cdnjs.cloudflare.com/ajax/libs/chai/3.4.1/chai.js',
|
|
8
|
+
jasmineJs: '//cdnjs.cloudflare.com/ajax/libs/jasmine/2.4.1/jasmine.js',
|
|
9
|
+
jasmineHtml: '//cdnjs.cloudflare.com/ajax/libs/jasmine/2.4.1/jasmine-html.js',
|
|
10
|
+
jasmineBoot: '//cdnjs.cloudflare.com/ajax/libs/jasmine/2.4.1/boot.js',
|
|
11
|
+
jasmineCss: '//cdnjs.cloudflare.com/ajax/libs/jasmine/2.4.1/jasmine.css',
|
|
12
|
+
qunitJs: '//code.jquery.com/qunit/qunit-1.20.0.js',
|
|
13
|
+
qunitCss: '//code.jquery.com/qunit/qunit-1.20.0.css'
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const JASMINE_JS = 'jasmine-core/lib/jasmine-core/jasmine.js';
|
|
17
|
+
const JASMINE_HTML = 'jasmine-core/lib/jasmine-core/jasmine-html.js';
|
|
18
|
+
const JASMINE_CSS = 'jasmine-core/lib/jasmine-core/jasmine.css';
|
|
19
|
+
const JASMINE_BOOT0 = 'jasmine-core/lib/jasmine-core/boot0.js';
|
|
20
|
+
const JASMINE_BOOT1 = 'jasmine-core/lib/jasmine-core/boot1.js';
|
|
21
|
+
const JASMINE_BOOT = 'jasmine-core/lib/jasmine-core/boot.js';
|
|
22
|
+
|
|
23
|
+
function nodeModulesUrl(relPath) {
|
|
24
|
+
return '/node_modules/' + relPath.replace(/\\/g, '/');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function nodeModulesDirs(cwd, routes) {
|
|
28
|
+
const dirs = [path.resolve(cwd, 'node_modules')];
|
|
29
|
+
const routesObj = routes || {};
|
|
30
|
+
['/node_modules', '/node_modules/'].forEach(prefix => {
|
|
31
|
+
if (!(prefix in routesObj)) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
[].concat(routesObj[prefix]).forEach(target => {
|
|
35
|
+
dirs.push(path.resolve(cwd, target));
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
return dirs;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function existsInNodeModules(dirs, relPath) {
|
|
42
|
+
return dirs.some(dir => fs.existsSync(path.join(dir, relPath)));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function findInNodeModules(dirs, relPath) {
|
|
46
|
+
for (let i = 0; i < dirs.length; i++) {
|
|
47
|
+
const full = path.join(dirs[i], relPath);
|
|
48
|
+
if (fs.existsSync(full)) {
|
|
49
|
+
return full;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function resolveAsset(dirs, relPath, fallbackUrl) {
|
|
56
|
+
if (existsInNodeModules(dirs, relPath)) {
|
|
57
|
+
return nodeModulesUrl(relPath);
|
|
58
|
+
}
|
|
59
|
+
return fallbackUrl;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function isChaiEsm(dirs) {
|
|
63
|
+
const pkgPath = findInNodeModules(dirs, 'chai/package.json');
|
|
64
|
+
if (!pkgPath) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
69
|
+
return pkg.type === 'module';
|
|
70
|
+
} catch {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function resolveChai(dirs, mochaLocal) {
|
|
76
|
+
if (!mochaLocal) {
|
|
77
|
+
return { chaiLocal: false, chaiJs: CDN.chaiJs };
|
|
78
|
+
}
|
|
79
|
+
if (existsInNodeModules(dirs, 'chai/chai.js')) {
|
|
80
|
+
return { chaiLocal: false, chaiJs: nodeModulesUrl('chai/chai.js') };
|
|
81
|
+
}
|
|
82
|
+
if (existsInNodeModules(dirs, 'chai/index.js') && isChaiEsm(dirs)) {
|
|
83
|
+
return { chaiLocal: true, chaiJs: nodeModulesUrl('chai/index.js') };
|
|
84
|
+
}
|
|
85
|
+
return { chaiLocal: false, chaiJs: CDN.chaiJs };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function resolveJasmine(dirs) {
|
|
89
|
+
const hasCore = existsInNodeModules(dirs, JASMINE_JS) &&
|
|
90
|
+
existsInNodeModules(dirs, JASMINE_HTML) &&
|
|
91
|
+
existsInNodeModules(dirs, JASMINE_CSS);
|
|
92
|
+
const jasmineCoreV5 = hasCore &&
|
|
93
|
+
existsInNodeModules(dirs, JASMINE_BOOT0) &&
|
|
94
|
+
existsInNodeModules(dirs, JASMINE_BOOT1);
|
|
95
|
+
const jasmineLegacy = hasCore &&
|
|
96
|
+
existsInNodeModules(dirs, JASMINE_BOOT) &&
|
|
97
|
+
!jasmineCoreV5;
|
|
98
|
+
|
|
99
|
+
if (jasmineCoreV5) {
|
|
100
|
+
return {
|
|
101
|
+
jasmineCoreV5: true,
|
|
102
|
+
jasmineJs: nodeModulesUrl(JASMINE_JS),
|
|
103
|
+
jasmineHtml: nodeModulesUrl(JASMINE_HTML),
|
|
104
|
+
jasmineCss: nodeModulesUrl(JASMINE_CSS),
|
|
105
|
+
jasmineBoot: null
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
if (jasmineLegacy) {
|
|
109
|
+
return {
|
|
110
|
+
jasmineCoreV5: false,
|
|
111
|
+
jasmineJs: nodeModulesUrl(JASMINE_JS),
|
|
112
|
+
jasmineHtml: nodeModulesUrl(JASMINE_HTML),
|
|
113
|
+
jasmineCss: nodeModulesUrl(JASMINE_CSS),
|
|
114
|
+
jasmineBoot: nodeModulesUrl(JASMINE_BOOT)
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
jasmineCoreV5: false,
|
|
119
|
+
jasmineJs: CDN.jasmineJs,
|
|
120
|
+
jasmineHtml: CDN.jasmineHtml,
|
|
121
|
+
jasmineCss: CDN.jasmineCss,
|
|
122
|
+
jasmineBoot: CDN.jasmineBoot
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function resolveRunnerFrameworkAssets(cwd, routes) {
|
|
127
|
+
cwd = cwd || process.cwd();
|
|
128
|
+
const dirs = nodeModulesDirs(cwd, routes);
|
|
129
|
+
|
|
130
|
+
const mochaJs = resolveAsset(dirs, 'mocha/mocha.js', CDN.mochaJs);
|
|
131
|
+
const mochaCss = resolveAsset(dirs, 'mocha/mocha.css', CDN.mochaCss);
|
|
132
|
+
const mochaLocal = mochaJs.indexOf('/node_modules/') === 0;
|
|
133
|
+
const chai = resolveChai(dirs, mochaLocal);
|
|
134
|
+
const jasmine = resolveJasmine(dirs);
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
mochaJs,
|
|
138
|
+
mochaCss,
|
|
139
|
+
chaiLocal: chai.chaiLocal,
|
|
140
|
+
chaiJs: chai.chaiJs,
|
|
141
|
+
jasmineCoreV5: jasmine.jasmineCoreV5,
|
|
142
|
+
jasmineJs: jasmine.jasmineJs,
|
|
143
|
+
jasmineHtml: jasmine.jasmineHtml,
|
|
144
|
+
jasmineBoot: jasmine.jasmineBoot,
|
|
145
|
+
jasmineCss: jasmine.jasmineCss,
|
|
146
|
+
qunitJs: resolveAsset(dirs, 'qunit/qunit/qunit.js', CDN.qunitJs),
|
|
147
|
+
qunitCss: resolveAsset(dirs, 'qunit/qunit/qunit.css', CDN.qunitCss)
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
module.exports = {
|
|
152
|
+
CDN,
|
|
153
|
+
resolveRunnerFrameworkAssets
|
|
154
|
+
};
|