testem 3.20.1 → 3.21.0
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 +77 -7
- 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 +65 -26
- package/lib/utils/jasmine1_deprecation.js +27 -0
- package/lib/utils/mustache_test_page_deprecation.js +27 -0
- package/lib/utils/report-file.js +7 -13
- package/lib/utils/runner_framework_assets.js +154 -0
- package/package.json +14 -24
- package/public/testem/jasmine_adapter.js +7 -1
- package/public/testem/testem_client.js +1 -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
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ Features
|
|
|
33
33
|
|
|
34
34
|
Installation
|
|
35
35
|
------------
|
|
36
|
-
Testem needs a supported **[Node.js](https://nodejs.org/)** runtime. The required range is defined in [`package.json`](package.json) under `engines` (currently **^20.19.0**, **^22.12.0**, or **>=
|
|
36
|
+
Testem needs a supported **[Node.js](https://nodejs.org/)** runtime. The required range is defined in [`package.json`](package.json) under `engines` (currently **^20.19.0**, **^22.12.0**, **^24.0.0**, or **>= 26.0.0**).
|
|
37
37
|
|
|
38
38
|
**Recommended:** install Testem **both** as a **dev dependency** (so your project pins a version) **and** **globally** (so the `testem` command is always available on your `PATH`):
|
|
39
39
|
|
|
@@ -278,7 +278,7 @@ This calls for the `testem.json` configuration file (you can also alternatively
|
|
|
278
278
|
|
|
279
279
|
```json
|
|
280
280
|
{
|
|
281
|
-
"framework": "
|
|
281
|
+
"framework": "jasmine2",
|
|
282
282
|
"src_files": [
|
|
283
283
|
"hello.js",
|
|
284
284
|
"hello_spec.js"
|
|
@@ -286,6 +286,8 @@ This calls for the `testem.json` configuration file (you can also alternatively
|
|
|
286
286
|
}
|
|
287
287
|
```
|
|
288
288
|
|
|
289
|
+
The default `framework` is still `"jasmine"` (Jasmine 1.x). That default is **deprecated** and will be removed in the next version of Testem. New projects should use `"jasmine2"` with `jasmine-core` (see [Browser framework dependencies](#browser-framework-dependencies)).
|
|
290
|
+
|
|
289
291
|
The `src_files` can also be unix glob patterns.
|
|
290
292
|
|
|
291
293
|
```json
|
|
@@ -312,6 +314,48 @@ You can also ignore certain files using `src_files_ignore`.
|
|
|
312
314
|
|
|
313
315
|
Read [more details](docs/config_file.md) about the config options.
|
|
314
316
|
|
|
317
|
+
Browser framework dependencies
|
|
318
|
+
------------------------------
|
|
319
|
+
|
|
320
|
+
Built-in `mocha`, `mocha+chai`, `qunit`, and `jasmine2` runners **prefer** files from `/node_modules/` in the project `cwd`. If those packages are not installed, Testem still loads the previous CDN URLs (Mocha 2.3.4, Chai 3.4.1, QUnit 1.20.0, Jasmine 2.4.1). Installing the packages is optional on this version and required in the next major version.
|
|
321
|
+
|
|
322
|
+
| `framework` | Install for modern versions |
|
|
323
|
+
|---|---|
|
|
324
|
+
| `jasmine2` | `jasmine-core` |
|
|
325
|
+
| `qunit` | `qunit` |
|
|
326
|
+
| `mocha` | `mocha` |
|
|
327
|
+
| `mocha+chai` | `mocha` and `chai` |
|
|
328
|
+
|
|
329
|
+
Run `npm install` in the project directory. In a monorepo, or when `cwd` is not the install root, map the path with `routes`:
|
|
330
|
+
|
|
331
|
+
```json
|
|
332
|
+
{
|
|
333
|
+
"routes": {
|
|
334
|
+
"/node_modules": "../node_modules"
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
The `mocha+chai` runner loads local Chai when Mocha is also local. Chai 4 uses the UMD build `chai/chai.js` as a classic script. Chai 5+ (`"type": "module"`) is imported as an ES module, then your spec files load as classic scripts (`var expect = chai.expect` still works).
|
|
340
|
+
|
|
341
|
+
`jasmine2` uses `jasmine-core` 5 `boot0.js`/`boot1.js` when those files are present, and `boot.js` for jasmine-core 3/4. Incomplete installs fall back to the CDN pin.
|
|
342
|
+
|
|
343
|
+
### Jasmine 1.x deprecation
|
|
344
|
+
|
|
345
|
+
`framework: "jasmine"` (the default) still runs Jasmine 1.3.1 from CDN. Testem logs a deprecation warning on the server, and the Jasmine 1 adapter logs a warning in the browser (including custom `test_page`s that still load Jasmine 1).
|
|
346
|
+
|
|
347
|
+
**The next version of Testem will not support Jasmine 1.** Migrate with:
|
|
348
|
+
|
|
349
|
+
```json
|
|
350
|
+
{
|
|
351
|
+
"framework": "jasmine2"
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
```bash
|
|
356
|
+
npm install --save-dev jasmine-core
|
|
357
|
+
```
|
|
358
|
+
|
|
315
359
|
Custom Test Pages
|
|
316
360
|
-----------------
|
|
317
361
|
|
|
@@ -330,7 +374,7 @@ Next, the test page you use needs to have the adapter code installed on them, as
|
|
|
330
374
|
|
|
331
375
|
### Include Snippet
|
|
332
376
|
|
|
333
|
-
Include this snippet directly after your `jasmine.js`, `qunit.js` or `mocha.js` scripts to enable *Testem* with your test page.
|
|
377
|
+
Include this snippet directly after your `jasmine.js`, `qunit.js` or `mocha.js` scripts to enable *Testem* with your test page. Prefer loading those frameworks from `/node_modules/...` (see [examples/qunit_lazy](examples/qunit_lazy) and [examples/vite](examples/vite)); CDN URLs still work.
|
|
334
378
|
|
|
335
379
|
```html
|
|
336
380
|
<script src="/testem.js"></script>
|
|
@@ -340,9 +384,11 @@ Or if you are using require.js or another loader, just make sure you load `/test
|
|
|
340
384
|
|
|
341
385
|
'/testem.js' here is dynamically generated to be used client-side and it should not be confused with server-side 'testem.js'.
|
|
342
386
|
|
|
343
|
-
### Dynamic Substitution
|
|
387
|
+
### Dynamic Substitution (deprecated)
|
|
388
|
+
|
|
389
|
+
Naming `test_page` with a `.mustache` extension to interpolate `serve_files` / `css_files` is **deprecated** and will be removed in Testem 4. Prefer a static HTML page and include `<script src="/testem.js"></script>` as shown above.
|
|
344
390
|
|
|
345
|
-
|
|
391
|
+
On Testem 3.x the old form still works:
|
|
346
392
|
|
|
347
393
|
1. name your test page using `.mustache` as the extension
|
|
348
394
|
2. use `{{#serve_files}}` to loop over the set of JavaScript files to be served, and then reference its `src` property to access their path (or `{{#css_files}}` for stylesheets)
|
|
@@ -357,6 +403,28 @@ Example:
|
|
|
357
403
|
<link rel="stylesheet" href="{{src}}">
|
|
358
404
|
{{/css_files}}
|
|
359
405
|
|
|
406
|
+
To migrate, rename the page to `.html`, list the same scripts and styles as tags, and point `test_page` at that file. `serve_files` can stay in config for watching or compilation; it no longer needs to be injected.
|
|
407
|
+
|
|
408
|
+
Before:
|
|
409
|
+
|
|
410
|
+
```html
|
|
411
|
+
{{#serve_files}}
|
|
412
|
+
<script src="{{src}}"></script>
|
|
413
|
+
{{/serve_files}}
|
|
414
|
+
{{#css_files}}
|
|
415
|
+
<link rel="stylesheet" href="{{src}}">
|
|
416
|
+
{{/css_files}}
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
After, if config has `"serve_files": ["test-bundle.js"]` and `"css_files": ["app.css"]`:
|
|
420
|
+
|
|
421
|
+
```html
|
|
422
|
+
<script src="test-bundle.js"></script>
|
|
423
|
+
<link rel="stylesheet" href="app.css">
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
Keep `/testem.js` and your test framework scripts in the same order as a normal custom page. If you interpolated other config keys (`{{port}}`, custom options), bake those values into the HTML or generate the page in `before_tests`.
|
|
427
|
+
|
|
360
428
|
### Multiple Test Pages
|
|
361
429
|
|
|
362
430
|
You can also specify multiple test pages to run by passing an array to the `test_page` option.
|
|
@@ -576,7 +644,7 @@ Simply add a `proxies` section to the `testem.json` configuration file.
|
|
|
576
644
|
```
|
|
577
645
|
|
|
578
646
|
This functionality is implemented as a *transparent proxy*, hence a request to
|
|
579
|
-
`http://localhost:7357/api/posts.json` will be proxied to `http://localhost:4200/api/posts.json` without removing the `/api` prefix. Setting the `secure` option to `false` as in the above `/xmlapi` configuration block will ignore TLS certificate validation and allow tests to successfully reach that URL even if testem was launched over HTTP. Other available options can be found here: https://github.com/
|
|
647
|
+
`http://localhost:7357/api/posts.json` will be proxied to `http://localhost:4200/api/posts.json` without removing the `/api` prefix. Setting the `secure` option to `false` as in the above `/xmlapi` configuration block will ignore TLS certificate validation and allow tests to successfully reach that URL even if testem was launched over HTTP. Other available options can be found here: https://github.com/unjs/httpxy#options
|
|
580
648
|
|
|
581
649
|
To limit the functionality to only certain content types, use "onlyContentTypes".
|
|
582
650
|
|
|
@@ -585,7 +653,9 @@ Preprocessors (Babel, TypeScript, LESS, Sass, Browserify, CoffeeScript, etc)
|
|
|
585
653
|
|
|
586
654
|
If you need to run a preprocessor (or indeed any shell command before the start of the tests) use the `before_tests` option, such as
|
|
587
655
|
|
|
588
|
-
"before_tests": "coffee -c
|
|
656
|
+
"before_tests": "coffee -c hello.coffee tests.coffee"
|
|
657
|
+
|
|
658
|
+
On Windows, list files explicitly in string hooks like this—cmd.exe does not expand `*` for external programs (see [Available hooks](docs/config_file.md#available-hooks) and the [coffeescript example](examples/coffeescript)). Testem's own `src_files` / `watch_files` globs are expanded by Testem, not by the hook shell.
|
|
589
659
|
|
|
590
660
|
or, with Babel (see the [Babel example](https://github.com/testem/testem/tree/master/examples/babel)):
|
|
591
661
|
|
package/lib/api.js
CHANGED
|
@@ -25,7 +25,7 @@ const Server = require('./server');
|
|
|
25
25
|
launch_in_dev: [Array] list of launchers to use for dev runs
|
|
26
26
|
launch_in_ci: [Array] list of launchers to use for CI runs
|
|
27
27
|
timeout: [Number] timeout for a browser
|
|
28
|
-
framework: [String] test framework to use
|
|
28
|
+
framework: [String] test framework to use (jasmine [deprecated Jasmine 1.x], jasmine2, qunit, mocha, mocha+chai, custom, tap)
|
|
29
29
|
url: [String] url server runs at (http://{host}:{port}/)
|
|
30
30
|
src_files: [Array] list of files or file patterns to use
|
|
31
31
|
src_files_ignore: [Array] list of files or file patterns to exclude from usage
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
|
+
const { inspect } = require('util');
|
|
3
4
|
const indent = require('../utils/strutils').indent;
|
|
4
|
-
const printf = require('printf');
|
|
5
5
|
const displayutils = require('../utils/displayutils');
|
|
6
6
|
|
|
7
7
|
module.exports = class DotReporter {
|
|
@@ -78,20 +78,20 @@ module.exports = class DotReporter {
|
|
|
78
78
|
return;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
this.out.write(`${String(idx + 1).padStart(3)}) [${data.launcher}] ${result.name}\n`);
|
|
82
82
|
|
|
83
83
|
if (error.message) {
|
|
84
|
-
|
|
84
|
+
this.out.write(` ${error.message}\n`);
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
if ('expected' in error && 'actual' in error) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
this.out.write('\n' +
|
|
89
|
+
` expected: ${error.negative ? 'NOT ' : ''}${inspect(error.expected)}\n` +
|
|
90
|
+
` actual: ${inspect(error.actual)}\n`);
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
if (error.stack) {
|
|
94
|
-
|
|
94
|
+
this.out.write(`\n${indent(error.stack, 5)}`);
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
this.out.write('\n\n');
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const { scriptTags, styleTags } = require('./html');
|
|
2
|
+
|
|
3
|
+
module.exports = function renderCustomRunner({
|
|
4
|
+
scripts, styles, footer_scripts
|
|
5
|
+
}) {
|
|
6
|
+
return `<!doctype html>
|
|
7
|
+
<html>
|
|
8
|
+
<head>
|
|
9
|
+
<title>Test'em</title>
|
|
10
|
+
<script src="/testem.js"></script>
|
|
11
|
+
${scriptTags(scripts)}
|
|
12
|
+
${styleTags(styles)}
|
|
13
|
+
</head>
|
|
14
|
+
<body>
|
|
15
|
+
</body>
|
|
16
|
+
${scriptTags(footer_scripts)}
|
|
17
|
+
</html>`;
|
|
18
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const { escapeHtml } = require('./html');
|
|
2
|
+
|
|
3
|
+
module.exports = function renderDirectoryListing({ files }) {
|
|
4
|
+
const items = (files || []).map(name => {
|
|
5
|
+
const safe = escapeHtml(name);
|
|
6
|
+
return `<li><a href="${safe}">${safe}</a></li>`;
|
|
7
|
+
}).join('');
|
|
8
|
+
return `<h1>Files In This Directory</h1>
|
|
9
|
+
<ul>
|
|
10
|
+
${items}
|
|
11
|
+
</ul>`;
|
|
12
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
function escapeHtml(value) {
|
|
2
|
+
return String(value)
|
|
3
|
+
.replace(/&/g, '&')
|
|
4
|
+
.replace(/</g, '<')
|
|
5
|
+
.replace(/>/g, '>')
|
|
6
|
+
.replace(/"/g, '"');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function attrSuffix(attrs) {
|
|
10
|
+
return (attrs || []).map(attr => ` ${attr}`).join('');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function scriptTags(files) {
|
|
14
|
+
return (files || [])
|
|
15
|
+
.map(file => `<script src="${file.src}"${attrSuffix(file.attrs)}></script>`)
|
|
16
|
+
.join('');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function asHrefList(hrefs) {
|
|
20
|
+
if (hrefs === undefined || hrefs === null || hrefs === '') {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
return Array.isArray(hrefs) ? hrefs : [hrefs];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function styleTags(hrefs) {
|
|
27
|
+
return asHrefList(hrefs)
|
|
28
|
+
.map(href => `<link rel="stylesheet" href="${escapeHtml(href)}">`)
|
|
29
|
+
.join('');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function loadScriptCalls(files) {
|
|
33
|
+
return (files || []).map(file => {
|
|
34
|
+
const extra = (file.attrs || []).map(attr => `, '${attr}'`).join('');
|
|
35
|
+
return `await loadScript('${file.src}'${extra});`;
|
|
36
|
+
}).join('\n');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = {
|
|
40
|
+
escapeHtml,
|
|
41
|
+
attrSuffix,
|
|
42
|
+
scriptTags,
|
|
43
|
+
styleTags,
|
|
44
|
+
loadScriptCalls
|
|
45
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const pages = {
|
|
2
|
+
jasmine: require('./jasmine'),
|
|
3
|
+
jasmine2: require('./jasmine2'),
|
|
4
|
+
qunit: require('./qunit'),
|
|
5
|
+
mocha: require('./mocha'),
|
|
6
|
+
'mocha+chai': require('./mocha_chai'),
|
|
7
|
+
custom: require('./custom'),
|
|
8
|
+
tap: require('./tap')
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
function renderRunner(framework, data) {
|
|
12
|
+
const render = pages[framework];
|
|
13
|
+
return render ? render(data) : null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
module.exports = {
|
|
17
|
+
renderRunner,
|
|
18
|
+
renderDirectoryListing: require('./directory')
|
|
19
|
+
};
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
const { scriptTags, styleTags } = require('./html');
|
|
2
|
+
|
|
3
|
+
module.exports = function renderJasmineRunner({
|
|
4
|
+
scripts, styles, footer_scripts
|
|
5
|
+
}) {
|
|
6
|
+
return `<!doctype html>
|
|
2
7
|
<html>
|
|
3
8
|
<head>
|
|
4
9
|
<title>Test'em</title>
|
|
@@ -16,14 +21,15 @@
|
|
|
16
21
|
})();
|
|
17
22
|
</script>
|
|
18
23
|
|
|
19
|
-
{
|
|
24
|
+
${scriptTags(scripts)}
|
|
20
25
|
|
|
21
26
|
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/jasmine/1.3.1/jasmine.css">
|
|
22
27
|
|
|
23
|
-
{
|
|
28
|
+
${styleTags(styles)}
|
|
24
29
|
</head>
|
|
25
30
|
<body>
|
|
26
31
|
<div id="jasmine_content"></div>
|
|
27
32
|
</body>
|
|
28
|
-
{
|
|
29
|
-
</html
|
|
33
|
+
${scriptTags(footer_scripts)}
|
|
34
|
+
</html>`;
|
|
35
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const { scriptTags, styleTags } = require('./html');
|
|
2
|
+
|
|
3
|
+
module.exports = function renderJasmine2Runner({
|
|
4
|
+
jasmineJs,
|
|
5
|
+
jasmineHtml,
|
|
6
|
+
jasmineCss,
|
|
7
|
+
jasmineBoot,
|
|
8
|
+
jasmineCoreV5,
|
|
9
|
+
scripts,
|
|
10
|
+
styles,
|
|
11
|
+
footer_scripts
|
|
12
|
+
}) {
|
|
13
|
+
const boot = jasmineCoreV5
|
|
14
|
+
? `<script src="/node_modules/jasmine-core/lib/jasmine-core/boot0.js"></script>
|
|
15
|
+
<script src="/node_modules/jasmine-core/lib/jasmine-core/boot1.js"></script>`
|
|
16
|
+
: `<script src="${jasmineBoot || ''}"></script>`;
|
|
17
|
+
|
|
18
|
+
return `<!doctype html>
|
|
19
|
+
<html>
|
|
20
|
+
<head>
|
|
21
|
+
<title>Test'em</title>
|
|
22
|
+
<script src="${jasmineJs}"></script>
|
|
23
|
+
<script src="${jasmineHtml}"></script>
|
|
24
|
+
${boot}
|
|
25
|
+
<script src="/testem.js"></script>
|
|
26
|
+
|
|
27
|
+
${scriptTags(scripts)}
|
|
28
|
+
|
|
29
|
+
<link rel="stylesheet" href="${jasmineCss}">
|
|
30
|
+
|
|
31
|
+
${styleTags(styles)}
|
|
32
|
+
</head>
|
|
33
|
+
<body>
|
|
34
|
+
<div id="jasmine_content"></div>
|
|
35
|
+
</body>
|
|
36
|
+
${scriptTags(footer_scripts)}
|
|
37
|
+
</html>`;
|
|
38
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const { scriptTags, styleTags } = require('./html');
|
|
2
|
+
|
|
3
|
+
module.exports = function renderMochaRunner({
|
|
4
|
+
mochaCss, mochaJs, scripts, styles, footer_scripts
|
|
5
|
+
}) {
|
|
6
|
+
return `<!doctype html>
|
|
7
|
+
<html>
|
|
8
|
+
<head>
|
|
9
|
+
<title>Test'em</title>
|
|
10
|
+
<link rel="stylesheet" href="${mochaCss}">
|
|
11
|
+
<script src="${mochaJs}"></script>
|
|
12
|
+
<script src="/testem.js"></script>
|
|
13
|
+
<script>mocha.setup('bdd')</script>
|
|
14
|
+
${scriptTags(scripts)}
|
|
15
|
+
${styleTags(styles)}
|
|
16
|
+
</head>
|
|
17
|
+
<body>
|
|
18
|
+
<div id="mocha"></div>
|
|
19
|
+
${scriptTags(footer_scripts)}
|
|
20
|
+
<script>
|
|
21
|
+
mocha.run()
|
|
22
|
+
</script>
|
|
23
|
+
</body>
|
|
24
|
+
</html>`;
|
|
25
|
+
};
|
|
@@ -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!
|
|
@@ -227,13 +246,22 @@ class Server extends EventEmitter {
|
|
|
227
246
|
return false;
|
|
228
247
|
}
|
|
229
248
|
|
|
249
|
+
sendProxyError(res, err) {
|
|
250
|
+
const message = (err && (err.message || err.code)) || 'Proxy error';
|
|
251
|
+
log.error(message);
|
|
252
|
+
// WebSocket upgrades pass a raw socket with no Express res.
|
|
253
|
+
if (res && res.status && !res.headersSent) {
|
|
254
|
+
res.status(500).json({ error: message });
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
230
258
|
configureProxy(app) {
|
|
231
259
|
var proxies = this.config.get('proxies');
|
|
232
260
|
if (proxies) {
|
|
233
|
-
this.proxy =
|
|
261
|
+
this.proxy = this.createProxyServer({ changeOrigin: true });
|
|
234
262
|
|
|
235
263
|
this.proxy.on('error', (err, req, res) => {
|
|
236
|
-
res
|
|
264
|
+
this.sendProxyError(res, err);
|
|
237
265
|
});
|
|
238
266
|
|
|
239
267
|
Object.keys(proxies).forEach((url) => {
|
|
@@ -241,7 +269,9 @@ class Server extends EventEmitter {
|
|
|
241
269
|
// set up proxy for WebSocket
|
|
242
270
|
this.server.on('upgrade', (req, socket, head) => {
|
|
243
271
|
if (req.url.startsWith(url)) {
|
|
244
|
-
|
|
272
|
+
var wsOpts = Object.assign({}, proxies[url]);
|
|
273
|
+
delete wsOpts.onlyContentTypes;
|
|
274
|
+
this.proxy.ws(req, socket, wsOpts, head).catch(() => {});
|
|
245
275
|
}
|
|
246
276
|
});
|
|
247
277
|
return;
|
|
@@ -252,15 +282,21 @@ class Server extends EventEmitter {
|
|
|
252
282
|
let wIdx = 0;
|
|
253
283
|
const proxyPath =
|
|
254
284
|
url.replace(/\*/g, () => `:_proxyW${wIdx++}`).replace(/\/$/, '') + '{/*_proxyRest}';
|
|
255
|
-
app.all(proxyPath, (req, res, next) => {
|
|
285
|
+
app.all(proxyPath, async (req, res, next) => {
|
|
256
286
|
var opts = proxies[url];
|
|
257
287
|
if (this.shouldProxy(req, opts)) {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
delete
|
|
288
|
+
var proxyOpts = Object.assign({}, opts);
|
|
289
|
+
if (proxyOpts.host) {
|
|
290
|
+
proxyOpts.target = `http://${proxyOpts.host}:${proxyOpts.port}`;
|
|
291
|
+
delete proxyOpts.host;
|
|
292
|
+
delete proxyOpts.port;
|
|
293
|
+
}
|
|
294
|
+
delete proxyOpts.onlyContentTypes;
|
|
295
|
+
try {
|
|
296
|
+
await this.proxy.web(req, res, proxyOpts);
|
|
297
|
+
} catch (err) {
|
|
298
|
+
this.sendProxyError(res, err);
|
|
262
299
|
}
|
|
263
|
-
this.proxy.web(req, res, opts);
|
|
264
300
|
} else {
|
|
265
301
|
next();
|
|
266
302
|
}
|
|
@@ -273,20 +309,23 @@ class Server extends EventEmitter {
|
|
|
273
309
|
var config = this.config;
|
|
274
310
|
var framework = config.get('framework') || 'jasmine';
|
|
275
311
|
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,
|
|
312
|
+
if (framework === 'jasmine') {
|
|
313
|
+
warnJasmine1Framework();
|
|
314
|
+
}
|
|
315
|
+
var frameworkAssets = resolveRunnerFrameworkAssets(
|
|
316
|
+
config.cwd(),
|
|
317
|
+
config.get('routes') || config.get('route')
|
|
318
|
+
);
|
|
319
|
+
var html = renderRunner(framework, Object.assign({
|
|
320
|
+
scripts: (files || []).map(browserScriptEntry),
|
|
287
321
|
styles: css_files,
|
|
288
|
-
footer_scripts: footerScripts
|
|
289
|
-
});
|
|
322
|
+
footer_scripts: (footerScripts || []).map(browserScriptEntry)
|
|
323
|
+
}, frameworkAssets));
|
|
324
|
+
if (html === null) {
|
|
325
|
+
res.status(500).send('Failed to lookup view "' + framework + '"');
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
res.type('html').send(html);
|
|
290
329
|
}
|
|
291
330
|
|
|
292
331
|
renderDefaultTestPage(req, res) {
|
|
@@ -394,6 +433,7 @@ class Server extends EventEmitter {
|
|
|
394
433
|
res.write('403 Forbidden');
|
|
395
434
|
res.end();
|
|
396
435
|
} else if (ext === '.mustache') {
|
|
436
|
+
warnMustacheTestPage();
|
|
397
437
|
config.getTemplateData((err, data) => {
|
|
398
438
|
res.render(filePath, data);
|
|
399
439
|
this.emit('file-requested', filePath);
|
|
@@ -410,8 +450,7 @@ class Server extends EventEmitter {
|
|
|
410
450
|
this.emit('file-requested', filePath);
|
|
411
451
|
if (stat.isDirectory()) {
|
|
412
452
|
return fs.promises.readdir(filePath).then(files => {
|
|
413
|
-
|
|
414
|
-
res.render(dirListingPage, { files: files });
|
|
453
|
+
res.type('html').send(renderDirectoryListing({ files: files }));
|
|
415
454
|
});
|
|
416
455
|
}
|
|
417
456
|
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/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
|
+
};
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"unittest",
|
|
8
8
|
"browser"
|
|
9
9
|
],
|
|
10
|
-
"version": "3.
|
|
10
|
+
"version": "3.21.0",
|
|
11
11
|
"author": "Toby Ho <airportyh@gmail.com>",
|
|
12
12
|
"repository": {
|
|
13
13
|
"type": "git",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"url": "https://github.com/testem/testem/issues"
|
|
18
18
|
},
|
|
19
19
|
"engines": {
|
|
20
|
-
"node": "^20.19.0 || ^22.12.0 ||
|
|
20
|
+
"node": "^20.19.0 || ^22.12.0 || ^24.0.0 || >= 26.0.0"
|
|
21
21
|
},
|
|
22
22
|
"license": "MIT",
|
|
23
23
|
"dependencies": {
|
|
@@ -31,20 +31,18 @@
|
|
|
31
31
|
"execa": "^9.6.1",
|
|
32
32
|
"express": "^5.2.1",
|
|
33
33
|
"glob": "^13.0.6",
|
|
34
|
-
"
|
|
35
|
-
"js-yaml": "^
|
|
34
|
+
"httpxy": "^0.5.5",
|
|
35
|
+
"js-yaml": "^5.3.0",
|
|
36
36
|
"lodash": "^4.18.1",
|
|
37
37
|
"minimatch": "^10.2.5",
|
|
38
|
-
"mkdirp": "^3.0.1",
|
|
39
38
|
"mustache": "^4.2.0",
|
|
40
|
-
"toasted-notifier": "^10.1.0",
|
|
41
|
-
"printf": "^0.6.1",
|
|
42
39
|
"proc-log": "^6.1.0",
|
|
43
40
|
"rimraf": "^6.1.3",
|
|
44
41
|
"socket.io": "^4.8.3",
|
|
45
42
|
"spawn-args": "^0.2.0",
|
|
46
43
|
"styled_string": "0.0.1",
|
|
47
|
-
"tap-parser": "^18.3.0"
|
|
44
|
+
"tap-parser": "^18.3.0",
|
|
45
|
+
"toasted-notifier": "^10.1.0"
|
|
48
46
|
},
|
|
49
47
|
"files": [
|
|
50
48
|
"lib",
|
|
@@ -52,8 +50,7 @@
|
|
|
52
50
|
"README.md",
|
|
53
51
|
"testem.js",
|
|
54
52
|
"assets",
|
|
55
|
-
"package.json"
|
|
56
|
-
"views"
|
|
53
|
+
"package.json"
|
|
57
54
|
],
|
|
58
55
|
"scripts": {
|
|
59
56
|
"test": "mocha tests/*_tests.js tests/**/*_tests.js",
|
|
@@ -68,30 +65,23 @@
|
|
|
68
65
|
"@eslint/eslintrc": "^3.3.5",
|
|
69
66
|
"@eslint/js": "^10.0.1",
|
|
70
67
|
"chai": "^6.2.2",
|
|
71
|
-
"chai-files": "^1.4.0",
|
|
72
|
-
"chai-shallow-deep-equal": "^1.4.6",
|
|
73
68
|
"cheerio": "^1.2.0",
|
|
74
69
|
"dirty-chai": "^3.0.0",
|
|
75
|
-
"esbuild": "^0.
|
|
76
|
-
"esbuild-plugins-node-modules-polyfill": "^1.8.
|
|
70
|
+
"esbuild": "^0.28.2",
|
|
71
|
+
"esbuild-plugins-node-modules-polyfill": "^1.8.3",
|
|
77
72
|
"eslint": "^10.2.0",
|
|
78
73
|
"eslint-config-prettier": "^10.1.8",
|
|
79
74
|
"eslint-plugin-chai-expect": "^4.0.0",
|
|
80
|
-
"eslint-plugin-mocha": "^
|
|
75
|
+
"eslint-plugin-mocha": "^12.0.2",
|
|
81
76
|
"fresh-tape": "^5.9.1",
|
|
82
|
-
"
|
|
83
|
-
"
|
|
77
|
+
"jasmine-core": "^5.1.0",
|
|
78
|
+
"mocha": "^12.0.0",
|
|
79
|
+
"qunit": "^2.26.0",
|
|
80
|
+
"saucie": "^4.1.0",
|
|
84
81
|
"sinon": "^21.0.3",
|
|
85
82
|
"sinon-chai": "^4.0.1",
|
|
86
83
|
"socket.io-client": "^4.8.3"
|
|
87
84
|
},
|
|
88
|
-
"overrides": {
|
|
89
|
-
"mocha": {
|
|
90
|
-
"diff": "^8.0.3",
|
|
91
|
-
"minimatch": "^9.0.7",
|
|
92
|
-
"serialize-javascript": "^7.0.5"
|
|
93
|
-
}
|
|
94
|
-
},
|
|
95
85
|
"bin": {
|
|
96
86
|
"testem": "./testem.js"
|
|
97
87
|
},
|
|
@@ -7,11 +7,17 @@ Testem's adapter for Jasmine. It works by adding a custom reporter.
|
|
|
7
7
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
/* globals emit, jasmine */
|
|
10
|
+
/* globals emit, jasmine, console */
|
|
11
11
|
/* exported jasmineAdapter */
|
|
12
12
|
'use strict';
|
|
13
13
|
|
|
14
14
|
function jasmineAdapter() {
|
|
15
|
+
if (!jasmineAdapter._deprecationWarned) {
|
|
16
|
+
jasmineAdapter._deprecationWarned = true;
|
|
17
|
+
if (typeof console !== 'undefined' && console.warn) {
|
|
18
|
+
console.warn('Testem: the Jasmine 1.x adapter is deprecated and will be removed in the next version of Testem. Upgrade to Jasmine 2+ (jasmine-core) so Testem can use the jasmine2 adapter.');
|
|
19
|
+
}
|
|
20
|
+
}
|
|
15
21
|
|
|
16
22
|
var results = {
|
|
17
23
|
failed: 0,
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html>
|
|
3
|
-
<head>
|
|
4
|
-
<title>Test'em</title>
|
|
5
|
-
<script src="/testem.js"></script>
|
|
6
|
-
{{#scripts}}<script src="{{{src}}}"{{#attrs}} {{&.}}{{/attrs}}></script>{{/scripts}}
|
|
7
|
-
{{#styles}}<link rel="stylesheet" href="{{.}}">{{/styles}}
|
|
8
|
-
</head>
|
|
9
|
-
<body>
|
|
10
|
-
</body>
|
|
11
|
-
{{#footer_scripts}}<script src="{{{src}}}"{{#attrs}} {{&.}}{{/attrs}}></script>{{/footer_scripts}}
|
|
12
|
-
</html>
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html>
|
|
3
|
-
<head>
|
|
4
|
-
<title>Test'em</title>
|
|
5
|
-
<script src="//cdnjs.cloudflare.com/ajax/libs/jasmine/2.4.1/jasmine.js"></script>
|
|
6
|
-
<script src="//cdnjs.cloudflare.com/ajax/libs/jasmine/2.4.1/jasmine-html.js"></script>
|
|
7
|
-
<script src="//cdnjs.cloudflare.com/ajax/libs/jasmine/2.4.1/boot.js"></script>
|
|
8
|
-
<script src="/testem.js"></script>
|
|
9
|
-
|
|
10
|
-
{{#scripts}}<script src="{{{src}}}"{{#attrs}} {{&.}}{{/attrs}}></script>{{/scripts}}
|
|
11
|
-
|
|
12
|
-
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/jasmine/2.4.1/jasmine.css">
|
|
13
|
-
|
|
14
|
-
{{#styles}}<link rel="stylesheet" href="{{.}}">{{/styles}}
|
|
15
|
-
</head>
|
|
16
|
-
<body>
|
|
17
|
-
<div id="jasmine_content"></div>
|
|
18
|
-
</body>
|
|
19
|
-
{{#footer_scripts}}<script src="{{{src}}}"{{#attrs}} {{&.}}{{/attrs}}></script>{{/footer_scripts}}
|
|
20
|
-
</html>
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html>
|
|
3
|
-
<head>
|
|
4
|
-
<title>Test'em</title>
|
|
5
|
-
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/mocha/2.3.4/mocha.css">
|
|
6
|
-
<script src="//cdnjs.cloudflare.com/ajax/libs/mocha/2.3.4/mocha.js"></script>
|
|
7
|
-
<script src="//cdnjs.cloudflare.com/ajax/libs/chai/3.4.1/chai.js"></script>
|
|
8
|
-
<script src="/testem.js"></script>
|
|
9
|
-
<script>mocha.setup('bdd')</script>
|
|
10
|
-
{{#scripts}}<script src="{{{src}}}"{{#attrs}} {{&.}}{{/attrs}}></script>{{/scripts}}
|
|
11
|
-
</head>
|
|
12
|
-
<body>
|
|
13
|
-
<div id="mocha"></div>
|
|
14
|
-
{{#footer_scripts}}<script src="{{{src}}}"{{#attrs}} {{&.}}{{/attrs}}></script>{{/footer_scripts}}
|
|
15
|
-
<script>
|
|
16
|
-
mocha.run()
|
|
17
|
-
</script>
|
|
18
|
-
</body>
|
|
19
|
-
</html>
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html>
|
|
3
|
-
<head>
|
|
4
|
-
<title>Test'em</title>
|
|
5
|
-
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/mocha/2.3.4/mocha.css">
|
|
6
|
-
<script src="//cdnjs.cloudflare.com/ajax/libs/mocha/2.3.4/mocha.js"></script>
|
|
7
|
-
<script src="/testem.js"></script>
|
|
8
|
-
<script>mocha.setup('bdd')</script>
|
|
9
|
-
{{#scripts}}<script src="{{{src}}}"{{#attrs}} {{&.}}{{/attrs}}></script>{{/scripts}}
|
|
10
|
-
{{#styles}}<link rel="stylesheet" href="{{.}}">{{/styles}}
|
|
11
|
-
</head>
|
|
12
|
-
<body>
|
|
13
|
-
<div id="mocha"></div>
|
|
14
|
-
{{#footer_scripts}}<script src="{{{src}}}"{{#attrs}} {{&.}}{{/attrs}}></script>{{/footer_scripts}}
|
|
15
|
-
<script>
|
|
16
|
-
mocha.run()
|
|
17
|
-
</script>
|
|
18
|
-
</body>
|
|
19
|
-
</html>
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html>
|
|
3
|
-
<head>
|
|
4
|
-
<title>Test'em</title>
|
|
5
|
-
<script src="//code.jquery.com/qunit/qunit-1.20.0.js"></script>
|
|
6
|
-
<script src="/testem.js"></script>
|
|
7
|
-
{{#scripts}}<script src="{{{src}}}"{{#attrs}} {{&.}}{{/attrs}}></script>{{/scripts}}
|
|
8
|
-
<link rel="stylesheet" href="//code.jquery.com/qunit/qunit-1.20.0.css"/>
|
|
9
|
-
{{#styles}}<link rel="stylesheet" href="{{.}}">{{/styles}}
|
|
10
|
-
</head>
|
|
11
|
-
<body>
|
|
12
|
-
<div id="qunit"></div>
|
|
13
|
-
<div id="qunit-fixture"></div>
|
|
14
|
-
</body>
|
|
15
|
-
{{#footer_scripts}}<script src="{{{src}}}"{{#attrs}} {{&.}}{{/attrs}}></script>{{/footer_scripts}}
|
|
16
|
-
</html>
|