jasmine-browser-runner 0.6.0 → 0.10.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 CHANGED
@@ -8,13 +8,13 @@ using either headless Chrome or Saucelabs.
8
8
  # Getting started
9
9
 
10
10
  ```bash
11
- npm install --save-dev jasmine-browser-runner
11
+ npm install --save-dev jasmine-browser-runner jasmine-core
12
12
  ```
13
13
 
14
14
  or
15
15
 
16
16
  ```bash
17
- yarn add -D jasmine-browser-runner
17
+ yarn add -D jasmine-browser-runner jasmine-core
18
18
  ```
19
19
 
20
20
  Add a `spec/support/jasmine-browser.json`. For example:
@@ -67,15 +67,97 @@ Its value can be `"internet explorer"`, `"firefox"`, `"safari"`,
67
67
 
68
68
  If a source, spec, or helper file's name ends in `.mjs`, it will be loaded as
69
69
  an ES module rather than a regular script. Note that ES modules are not
70
- available in all browsers supported by jasmine-browser. Currently,
71
- jasmine-browser does not try to determine whether the browser supports ES
70
+ available in all browsers supported by jasmine-browser-runner. Currently,
71
+ jasmine-browser-runner does not try to determine whether the browser supports ES
72
72
  modules. ES modules will silently fail to load in browsers that don't
73
73
  support them. Other kinds of load-time errors are detected and reported as suite
74
74
  errors.
75
75
 
76
+ To allow spec files to import source files via relative paths, set the `specDir`
77
+ config field to something that's high enough up to include both spec and source
78
+ files, and set `srcFiles` to `[]`. You can autogenerate such a configuration by
79
+ running `npx jasmine-browser-runner init --esm`.
80
+
81
+ ## Use with Rails
82
+
83
+ You can use jasmine-browser-runner to test your Rails application's JavaScript,
84
+ whether you use the Asset Pipeline or Webpacker.
85
+
86
+ ### Webpacker
87
+
88
+ 1. Run `yarn add --dev jasmine-browser-runner`.
89
+ 2. Run `npx jasmine-browser-runner init`.
90
+ 3. Edit `spec/support/jasmine-browser.json` as follows:
91
+ ```
92
+ {
93
+ "srcDir": ".",
94
+ "srcFiles": [],
95
+ "specDir": "public/packs/js",
96
+ "specFiles": [
97
+ "specs-*.js"
98
+ ],
99
+ "helpers": [],
100
+ // ...
101
+ }
102
+ ```
103
+ 4. Create `app/javascript/packs/specs.js` (or `app/javascript/packs/specs.jsx`
104
+ if you use JSX) as follows:
105
+ ```
106
+ (function() {
107
+ 'use strict';
108
+
109
+ function requireAll(context) {
110
+ context.keys().forEach(context);
111
+ }
112
+
113
+ requireAll(require.context('spec/javascript/helpers/', true, /\.js/));
114
+ requireAll(require.context('spec/javascript/', true, /[sS]pec\.js/));
115
+ })();
116
+ ```
117
+ 5. Add `'spec/javascript'` to the `additional_paths` array in `config/webpacker.yml`.
118
+ 6. Put your spec files in `spec/javascript`.
119
+
120
+ To run the specs:
121
+
122
+ 1. Run `bin/webpack --watch`.
123
+ 2. Run `npx jasmine-browser-runner`.
124
+ 3. visit <http://localhost:8888>.
125
+
126
+ ### Asset Pipeline
127
+
128
+ 1. Run `yarn init` if there isn't already `package.json` file in the root of
129
+ the Rails application.
130
+ 2. Run `yarn add --dev jasmine-browser-runner`.
131
+ 3. Run `npx jasmine-browser-runner init`.
132
+ 5. Edit `spec/support/jasmine-browser.json` as follows:
133
+ ```
134
+ {
135
+ "srcDir": "public/assets",
136
+ "srcFiles": [
137
+ "application-*.js"
138
+ ],
139
+ "specDir": "spec/javascript",
140
+ "specFiles": [
141
+ "**/*[sS]pec.?(m)js"
142
+ ],
143
+ "helpers": [
144
+ "helpers/**/*.?(m)js"
145
+ ],
146
+ // ...
147
+ }
148
+ ```
149
+ 6. Put your spec files in `spec/javascript`.
150
+
151
+ To run the specs:
152
+
153
+ 1. Either run `bundle exec rake assets:precompile` or start the Rails
154
+ application in an environment that's configured to precompile assets.
155
+ 2. Run `npx jasmine-browser-runner`.
156
+ 3. Visit <http://localhost:8888>.
157
+
76
158
  ## Saucelabs support
77
159
 
78
- jasmine-browser can run your Jasmine specs on [Saucelabs](https://saucelabs.com/).
160
+ jasmine-browser-runner can run your Jasmine specs on [Saucelabs](https://saucelabs.com/).
79
161
  To use Saucelabs, set `browser.name`, `browser.useSauce`, and `browser.sauce`
80
162
  in your config file as follows:
81
163
 
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  var path = require('path'),
4
- jasmineCore = require('jasmine-core'),
4
+ jasmineCore = require('../lib/jasmineCore'),
5
5
  Command = require('../lib/command'),
6
6
  jasmineBrowser = require('../index.js');
7
7
 
package/config.js.ejs ADDED
@@ -0,0 +1 @@
1
+ jasmine.getEnv().configure(<%- JSON.stringify(envConfig) %>);
package/index.js CHANGED
@@ -1,39 +1,47 @@
1
- const ConsoleReporter = require('jasmine').ConsoleReporter,
1
+ const ConsoleReporter = require('./lib/console_reporter'),
2
2
  webdriverModule = require('./lib/webdriver'),
3
- util = require('util'),
4
3
  Server = require('./lib/server'),
5
- Runner = require('./lib/runner');
4
+ Runner = require('./lib/runner'),
5
+ ModuleLoader = require('./lib/moduleLoader');
6
6
 
7
- function createReporters(options) {
8
- if (!options.reporters) {
9
- return createDefaultReporter(options);
7
+ async function createReporters(options) {
8
+ const result = [];
9
+
10
+ if (options.useConsoleReporter !== false) {
11
+ const consoleReporter = new ConsoleReporter();
12
+ consoleReporter.setOptions({ color: options.color });
13
+ result.push(consoleReporter);
10
14
  }
11
15
 
12
- const result = [];
16
+ if (options.reporters) {
17
+ // Resolve relative paths relative to the cwd, rather than the default
18
+ // which is the directory containing the moduleLoader module.
19
+ const loader = new ModuleLoader(process.cwd());
13
20
 
14
- for (const reporterName of options.reporters) {
15
- try {
16
- const Reporter = require(reporterName);
17
- result.push(new Reporter());
18
- } catch (e) {
19
- throw new Error(
20
- `Failed to register reporter ${reporterName}: ${e.message}`
21
- );
21
+ for (const reporterOrModuleName of options.reporters) {
22
+ if (typeof reporterOrModuleName === 'object') {
23
+ result.push(reporterOrModuleName);
24
+ } else {
25
+ try {
26
+ const Reporter = await loader.load(reporterOrModuleName);
27
+ result.push(new Reporter());
28
+ } catch (e) {
29
+ throw new Error(
30
+ `Failed to register reporter ${reporterOrModuleName}: ${e.message}`
31
+ );
32
+ }
33
+ }
22
34
  }
23
35
  }
24
36
 
25
- return result;
26
- }
37
+ if (result.length === 0) {
38
+ throw new Error(
39
+ 'No reporters were specified. Either add a reporter or remove ' +
40
+ '`useConsoleReporter: false`.'
41
+ );
42
+ }
27
43
 
28
- function createDefaultReporter(options) {
29
- const reporter = new ConsoleReporter();
30
- reporter.setOptions({
31
- print: function() {
32
- process.stdout.write(util.format.apply(this, arguments));
33
- },
34
- showColors: options.color === 'undefined' ? true : options.color,
35
- });
36
- return [reporter];
44
+ return result;
37
45
  }
38
46
 
39
47
  /**
@@ -53,7 +61,7 @@ module.exports = {
53
61
  },
54
62
  /**
55
63
  * Runs the specs.
56
- * @param {RunSpecsOptions} options
64
+ * @param {Configuration} options
57
65
  * @return {Promise<JasmineDoneInfo>} A promise that resolves to the {@link https://jasmine.github.io/api/edge/global.html#JasmineDoneInfo|overall result} when the suite has finished running.
58
66
  */
59
67
  runSpecs: async function(options, deps) {
@@ -71,41 +79,46 @@ module.exports = {
71
79
  deps.buildWebdriver || webdriverModule.buildWebdriver;
72
80
  const setExitCode = deps.setExitCode || (code => (process.exitCode = code));
73
81
  const server = new ServerClass(options);
74
- const webdriver = buildWebdriver(options.browser);
75
82
 
76
- const reporters = createReporters(options);
83
+ const reporters = await createReporters(options);
77
84
  const useSauce = options.browser && options.browser.useSauce;
78
85
  const portRequest = useSauce ? 5555 : 0;
79
86
  await server.start({ port: portRequest });
80
- const host = `http://localhost:${server.port()}`;
81
- const runner = new RunnerClass({ webdriver, reporters, host });
82
-
83
- console.log('Running tests in the browser...');
84
87
 
85
88
  try {
86
- const details = await runner.run(options);
89
+ const webdriver = buildWebdriver(options.browser);
87
90
 
88
- if (details.overallStatus === 'passed') {
89
- setExitCode(0);
90
- } else if (details.overallStatus === 'incomplete') {
91
- setExitCode(2);
92
- } else {
93
- setExitCode(1);
94
- }
91
+ try {
92
+ const host = `http://localhost:${server.port()}`;
93
+ const runner = new RunnerClass({ webdriver, reporters, host });
95
94
 
96
- return details;
97
- } finally {
98
- await server.stop();
95
+ console.log('Running tests in the browser...');
99
96
 
100
- if (useSauce) {
101
- await webdriver.executeScript(
102
- `sauce:job-result=${process.exitCode === 0}`
103
- );
104
- }
97
+ const details = await runner.run(options);
105
98
 
106
- await webdriver.close();
99
+ if (details.overallStatus === 'passed') {
100
+ setExitCode(0);
101
+ } else if (details.overallStatus === 'incomplete') {
102
+ setExitCode(2);
103
+ } else {
104
+ setExitCode(1);
105
+ }
106
+
107
+ return details;
108
+ } finally {
109
+ if (useSauce) {
110
+ await webdriver.executeScript(
111
+ `sauce:job-result=${process.exitCode === 0}`
112
+ );
113
+ }
114
+
115
+ await webdriver.close();
116
+ }
117
+ } finally {
118
+ await server.stop();
107
119
  }
108
120
  },
109
121
  Server,
110
122
  Runner,
123
+ ConsoleReporter,
111
124
  };
package/lib/command.js CHANGED
@@ -1,14 +1,14 @@
1
1
  const path = require('path');
2
2
  const fs = require('fs');
3
- const defaultConfig = require('./default_config');
3
+ const {
4
+ loadConfig,
5
+ validateConfig,
6
+ defaultConfig,
7
+ defaultEsmConfig,
8
+ } = require('./config');
4
9
 
5
10
  const commonOptions = [
6
11
  { name: 'config', type: 'string', description: 'path to the config file' },
7
- {
8
- name: 'clear-reporters',
9
- type: 'bool',
10
- description: 'clear default reporters in the HTML page',
11
- },
12
12
  { name: 'port', type: 'number', description: 'port to run the server on' },
13
13
  ];
14
14
 
@@ -22,6 +22,14 @@ const subCommands = [
22
22
  {
23
23
  name: 'init',
24
24
  description: 'initialize a new Jasmine project',
25
+ options: [
26
+ {
27
+ name: 'esm',
28
+ type: 'bool',
29
+ description:
30
+ 'configure for use with ES modules (<script type="module">)',
31
+ },
32
+ ],
25
33
  },
26
34
  {
27
35
  name: 'serve',
@@ -35,6 +43,7 @@ const subCommands = [
35
43
  {
36
44
  name: 'color',
37
45
  type: 'bool',
46
+ reversable: true,
38
47
  description: 'turn on or off color output',
39
48
  },
40
49
  {
@@ -43,19 +52,15 @@ const subCommands = [
43
52
  description:
44
53
  'filter specs to run only those that match the given string',
45
54
  },
46
- {
47
- name: 'stop-on-failure',
48
- type: 'bool',
49
- description: 'stop spec execution on expectation failure',
50
- },
51
55
  {
52
56
  name: 'fail-fast',
53
57
  type: 'bool',
54
- description: 'stop Jasmine execution on spec failure',
58
+ description: 'stop Jasmine execution on the first failure',
55
59
  },
56
60
  {
57
61
  name: 'random',
58
62
  type: 'bool',
63
+ reversable: true,
59
64
  description: 'turn on or off randomization',
60
65
  },
61
66
  {
@@ -141,7 +146,7 @@ class Command {
141
146
  this._logger.log('jasmine-core v' + this._config.jasmineCore.version());
142
147
  }
143
148
 
144
- init() {
149
+ init(options) {
145
150
  const dest = 'spec/support/jasmine-browser.json';
146
151
 
147
152
  if (fs.existsSync(dest)) {
@@ -150,33 +155,40 @@ class Command {
150
155
  }
151
156
 
152
157
  fs.mkdirSync(path.dirname(dest), { recursive: true });
153
- fs.writeFileSync(dest, defaultConfig);
158
+
159
+ if (options.esm) {
160
+ fs.writeFileSync(dest, defaultEsmConfig());
161
+ } else {
162
+ fs.writeFileSync(dest, defaultConfig());
163
+ }
164
+
154
165
  this._logger.log(`Wrote configuration to ${dest}.`);
155
166
  }
156
167
 
157
168
  async serve(options) {
158
- await this._config.jasmineBrowser.startServer(this._loadConfig(options));
169
+ await this._config.jasmineBrowser.startServer(
170
+ await this._loadConfig(options)
171
+ );
159
172
  }
160
173
 
161
174
  async runSpecs(options) {
162
- await this._config.jasmineBrowser.runSpecs(this._loadConfig(options));
175
+ const config = await this._loadConfig(options);
176
+
177
+ if (options['fail-fast']) {
178
+ config.env = {
179
+ ...config.env,
180
+ stopSpecOnExpectationFailure: true,
181
+ stopOnSpecFailure: true,
182
+ };
183
+ }
184
+
185
+ await this._config.jasmineBrowser.runSpecs(config);
163
186
  }
164
187
 
165
- _loadConfig(options) {
166
- const configFile = options.config || 'spec/support/jasmine-browser.json';
167
- delete options.config;
168
- delete options.unknown;
169
- Object.keys(options).forEach(function(opt) {
170
- const camelCase = opt.replace(/-./g, function(input) {
171
- return input[1].toUpperCase();
172
- });
173
- if (camelCase !== opt) {
174
- options[camelCase] = options[opt];
175
- delete options[opt];
176
- }
177
- });
178
- const fullPath = path.resolve(this._config.baseDir, configFile);
179
- return Object.assign({}, require(fullPath), options);
188
+ async _loadConfig(options) {
189
+ const config = await loadConfig(this._config.baseDir, options);
190
+ validateConfig(config);
191
+ return config;
180
192
  }
181
193
  }
182
194
 
@@ -219,9 +231,13 @@ function commandText(command) {
219
231
  }
220
232
 
221
233
  function optionText(option) {
222
- return option.type === 'bool'
223
- ? `--[no-]${option.name}`
224
- : `--${option.name}=<value>`;
234
+ if (option.type !== 'bool') {
235
+ return `--${option.name}=<value>`;
236
+ } else if (option.reversable) {
237
+ return `--[no-]${option.name}`;
238
+ } else {
239
+ return `--${option.name}`;
240
+ }
225
241
  }
226
242
 
227
243
  function wrapDescription(indentLevel, prefixWidth, maxWidth, text) {
package/lib/config.js ADDED
@@ -0,0 +1,75 @@
1
+ const path = require('path');
2
+ const fs = require('fs');
3
+ const ModuleLoader = require('./moduleLoader');
4
+
5
+ async function loadConfig(baseDir, cliOptions) {
6
+ const options = { ...cliOptions };
7
+ const specifiedConfigFile = options.config;
8
+ delete options.config;
9
+ delete options.unknown;
10
+
11
+ Object.keys(options).forEach(function(opt) {
12
+ const camelCase = opt.replace(/-./g, function(input) {
13
+ return input[1].toUpperCase();
14
+ });
15
+ if (camelCase !== opt) {
16
+ options[camelCase] = options[opt];
17
+ delete options[opt];
18
+ }
19
+ });
20
+
21
+ const candidates = (specifiedConfigFile
22
+ ? [specifiedConfigFile]
23
+ : ['spec/support/jasmine-browser.js', 'spec/support/jasmine-browser.json']
24
+ )
25
+ .filter(name => !!name)
26
+ .map(name => path.resolve(baseDir, name));
27
+
28
+ const fullPath = candidates.find(p => fs.existsSync(p));
29
+
30
+ if (!fullPath) {
31
+ const msg =
32
+ 'Could not find configuration file.\nTried:\n' +
33
+ candidates.map(p => `* ${p}`).join('\n');
34
+ throw new Error(msg);
35
+ }
36
+
37
+ const moduleLoader = new ModuleLoader();
38
+ return Object.assign({}, await moduleLoader.load(fullPath), options);
39
+ }
40
+
41
+ function validateConfig(config) {
42
+ for (const k of ['specDir', 'srcDir']) {
43
+ if (!config[k]) {
44
+ throw new Error('Configuration is missing ' + k);
45
+ }
46
+ }
47
+
48
+ for (const k of ['specFiles', 'srcFiles', 'helpers']) {
49
+ if (config[k] && !Array.isArray(config[k])) {
50
+ throw new Error(`Configuration's ${k} property is not an array`);
51
+ }
52
+ }
53
+ }
54
+
55
+ function defaultConfig() {
56
+ return fs.readFileSync(require.resolve('./examples/default_config.json'), {
57
+ encoding: 'utf8',
58
+ });
59
+ }
60
+
61
+ function defaultEsmConfig() {
62
+ return fs.readFileSync(
63
+ require.resolve('./examples/default_esm_config.json'),
64
+ {
65
+ encoding: 'utf8',
66
+ }
67
+ );
68
+ }
69
+
70
+ module.exports = {
71
+ loadConfig,
72
+ validateConfig,
73
+ defaultConfig,
74
+ defaultEsmConfig,
75
+ };
@@ -0,0 +1,307 @@
1
+ const util = require('util');
2
+ module.exports = exports = ConsoleReporter;
3
+
4
+ /**
5
+ * @classdesc A reporter that prints spec and suite results to the console.
6
+ * A ConsoleReporter is installed unless {@link Configuration#useConsoleReporter}
7
+ * is set to false.
8
+ *
9
+ * @constructor
10
+ * @example
11
+ * const {ConsoleReporter} = require('jasmine');
12
+ * const reporter = new ConsoleReporter();
13
+ */
14
+ function ConsoleReporter() {
15
+ let print = function() {
16
+ process.stdout.write(util.format.apply(this, arguments));
17
+ },
18
+ showColors = true,
19
+ jasmineCorePath = null,
20
+ specCount,
21
+ executableSpecCount,
22
+ failureCount,
23
+ failedSpecs = [],
24
+ pendingSpecs = [],
25
+ ansi = {
26
+ green: '\x1B[32m',
27
+ red: '\x1B[31m',
28
+ yellow: '\x1B[33m',
29
+ none: '\x1B[0m',
30
+ },
31
+ failedSuites = [],
32
+ stackFilter = defaultStackFilter;
33
+
34
+ /**
35
+ * Configures the reporter.
36
+ * @function
37
+ * @name ConsoleReporter#setOptions
38
+ * @param {ConsoleReporterOptions} options
39
+ */
40
+ this.setOptions = function(options) {
41
+ if (options.print) {
42
+ print = options.print;
43
+ }
44
+
45
+ /**
46
+ * @interface ConsoleReporterOptions
47
+ */
48
+ /**
49
+ * Whether to colorize the output
50
+ * @name ConsoleReporterOptions#color
51
+ * @type Boolean|undefined
52
+ * @default true
53
+ */
54
+ if (options.color !== undefined) {
55
+ showColors = options.color;
56
+ }
57
+
58
+ if (options.jasmineCorePath) {
59
+ jasmineCorePath = options.jasmineCorePath;
60
+ }
61
+ if (options.stackFilter) {
62
+ stackFilter = options.stackFilter;
63
+ }
64
+ };
65
+
66
+ this.jasmineStarted = function(options) {
67
+ specCount = 0;
68
+ executableSpecCount = 0;
69
+ failureCount = 0;
70
+ if (options && options.order && options.order.random) {
71
+ print('Randomized with seed ' + options.order.seed);
72
+ printNewline();
73
+ }
74
+ print('Started');
75
+ printNewline();
76
+ };
77
+
78
+ this.jasmineDone = function(result) {
79
+ printNewline();
80
+ printNewline();
81
+ if (failedSpecs.length > 0) {
82
+ print('Failures:');
83
+ }
84
+ for (let i = 0; i < failedSpecs.length; i++) {
85
+ specFailureDetails(failedSpecs[i], i + 1);
86
+ }
87
+
88
+ for (let i = 0; i < failedSuites.length; i++) {
89
+ suiteFailureDetails(failedSuites[i]);
90
+ }
91
+
92
+ if (
93
+ result &&
94
+ result.failedExpectations &&
95
+ result.failedExpectations.length > 0
96
+ ) {
97
+ suiteFailureDetails(result);
98
+ }
99
+
100
+ if (pendingSpecs.length > 0) {
101
+ print('Pending:');
102
+ }
103
+ for (let i = 0; i < pendingSpecs.length; i++) {
104
+ pendingSpecDetails(pendingSpecs[i], i + 1);
105
+ }
106
+
107
+ if (specCount > 0) {
108
+ printNewline();
109
+
110
+ if (executableSpecCount !== specCount) {
111
+ print(
112
+ 'Ran ' +
113
+ executableSpecCount +
114
+ ' of ' +
115
+ specCount +
116
+ plural(' spec', specCount)
117
+ );
118
+ printNewline();
119
+ }
120
+ let specCounts =
121
+ executableSpecCount +
122
+ ' ' +
123
+ plural('spec', executableSpecCount) +
124
+ ', ' +
125
+ failureCount +
126
+ ' ' +
127
+ plural('failure', failureCount);
128
+
129
+ if (pendingSpecs.length) {
130
+ specCounts +=
131
+ ', ' +
132
+ pendingSpecs.length +
133
+ ' pending ' +
134
+ plural('spec', pendingSpecs.length);
135
+ }
136
+
137
+ print(specCounts);
138
+ } else {
139
+ print('No specs found');
140
+ }
141
+
142
+ printNewline();
143
+ const seconds = result ? result.totalTime / 1000 : 0;
144
+ print('Finished in ' + seconds + ' ' + plural('second', seconds));
145
+ printNewline();
146
+
147
+ if (result && result.overallStatus === 'incomplete') {
148
+ print('Incomplete: ' + result.incompleteReason);
149
+ printNewline();
150
+ }
151
+
152
+ if (result && result.order && result.order.random) {
153
+ print('Randomized with seed ' + result.order.seed);
154
+ print(
155
+ ' (jasmine-browser-runner runSpecs --seed=' + result.order.seed + ')'
156
+ );
157
+ printNewline();
158
+ }
159
+ };
160
+
161
+ this.specDone = function(result) {
162
+ specCount++;
163
+
164
+ if (result.status == 'pending') {
165
+ pendingSpecs.push(result);
166
+ executableSpecCount++;
167
+ print(colored('yellow', '*'));
168
+ return;
169
+ }
170
+
171
+ if (result.status == 'passed') {
172
+ executableSpecCount++;
173
+ print(colored('green', '.'));
174
+ return;
175
+ }
176
+
177
+ if (result.status == 'failed') {
178
+ failureCount++;
179
+ failedSpecs.push(result);
180
+ executableSpecCount++;
181
+ print(colored('red', 'F'));
182
+ }
183
+ };
184
+
185
+ this.suiteDone = function(result) {
186
+ if (result.failedExpectations && result.failedExpectations.length > 0) {
187
+ failureCount++;
188
+ failedSuites.push(result);
189
+ }
190
+ };
191
+
192
+ return this;
193
+
194
+ function printNewline() {
195
+ print('\n');
196
+ }
197
+
198
+ function colored(color, str) {
199
+ return showColors ? ansi[color] + str + ansi.none : str;
200
+ }
201
+
202
+ function plural(str, count) {
203
+ return count == 1 ? str : str + 's';
204
+ }
205
+
206
+ function repeat(thing, times) {
207
+ const arr = [];
208
+ for (let i = 0; i < times; i++) {
209
+ arr.push(thing);
210
+ }
211
+ return arr;
212
+ }
213
+
214
+ function indent(str, spaces) {
215
+ const lines = (str || '').split('\n');
216
+ const newArr = [];
217
+ for (let i = 0; i < lines.length; i++) {
218
+ newArr.push(repeat(' ', spaces).join('') + lines[i]);
219
+ }
220
+ return newArr.join('\n');
221
+ }
222
+
223
+ function defaultStackFilter(stack) {
224
+ if (!stack) {
225
+ return '';
226
+ }
227
+
228
+ const filteredStack = stack
229
+ .split('\n')
230
+ .filter(function(stackLine) {
231
+ return stackLine.indexOf(jasmineCorePath) === -1;
232
+ })
233
+ .join('\n');
234
+ return filteredStack;
235
+ }
236
+
237
+ function specFailureDetails(result, failedSpecNumber) {
238
+ printNewline();
239
+ print(failedSpecNumber + ') ');
240
+ print(result.fullName);
241
+ printFailedExpectations(result);
242
+
243
+ if (result.trace) {
244
+ printNewline();
245
+ print(indent('Trace:', 2));
246
+ printNewline();
247
+
248
+ for (const entry of result.trace) {
249
+ print(indent(`${entry.timestamp}ms: ${entry.message}`, 4));
250
+ printNewline();
251
+ }
252
+ }
253
+ }
254
+
255
+ function suiteFailureDetails(result) {
256
+ printNewline();
257
+ print('Suite error: ' + result.fullName);
258
+ printFailedExpectations(result);
259
+ }
260
+
261
+ function printFailedExpectations(result) {
262
+ for (let i = 0; i < result.failedExpectations.length; i++) {
263
+ const failedExpectation = result.failedExpectations[i];
264
+ printNewline();
265
+ print(indent('Message:', 2));
266
+ printNewline();
267
+ print(colored('red', indent(failedExpectation.message, 4)));
268
+ printNewline();
269
+ print(indent('Stack:', 2));
270
+ printNewline();
271
+ print(indent(stackFilter(failedExpectation.stack), 4));
272
+ }
273
+
274
+ // When failSpecWithNoExpectations = true and a spec fails because of no expectations found,
275
+ // jasmine-core reports it as a failure with no message.
276
+ //
277
+ // Therefore we assume that when there are no failed or passed expectations,
278
+ // the failure was because of our failSpecWithNoExpectations setting.
279
+ //
280
+ // Same logic is used by jasmine.HtmlReporter, see https://github.com/jasmine/jasmine/blob/main/src/html/HtmlReporter.js
281
+ if (
282
+ result.failedExpectations.length === 0 &&
283
+ result.passedExpectations.length === 0
284
+ ) {
285
+ printNewline();
286
+ print(indent('Message:', 2));
287
+ printNewline();
288
+ print(colored('red', indent('Spec has no expectations', 4)));
289
+ }
290
+
291
+ printNewline();
292
+ }
293
+
294
+ function pendingSpecDetails(result, pendingSpecNumber) {
295
+ printNewline();
296
+ printNewline();
297
+ print(pendingSpecNumber + ') ');
298
+ print(result.fullName);
299
+ printNewline();
300
+ let pendingReason = 'No reason given';
301
+ if (result.pendingReason && result.pendingReason !== '') {
302
+ pendingReason = result.pendingReason;
303
+ }
304
+ print(indent(colored('yellow', pendingReason), 2));
305
+ printNewline();
306
+ }
307
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "srcDir": "src",
3
+ "srcFiles": [
4
+ "**/*.js"
5
+ ],
6
+ "specDir": "spec",
7
+ "specFiles": [
8
+ "**/*[sS]pec.js"
9
+ ],
10
+ "helpers": [
11
+ "helpers/**/*.js"
12
+ ],
13
+ "env": {
14
+ "stopSpecOnExpectationFailure": false,
15
+ "stopOnSpecFailure": false,
16
+ "random": true
17
+ },
18
+ "browser": {
19
+ "name": "firefox"
20
+ }
21
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "srcDir": "src",
3
+ "srcFiles": [],
4
+ "specDir": ".",
5
+ "specFiles": [
6
+ "spec/**/*[sS]pec.?(m)js"
7
+ ],
8
+ "helpers": [
9
+ "spec/helpers/**/*.?(m)js"
10
+ ],
11
+ "env": {
12
+ "stopSpecOnExpectationFailure": false,
13
+ "stopOnSpecFailure": false,
14
+ "random": true
15
+ },
16
+ "browser": {
17
+ "name": "firefox"
18
+ }
19
+ }
@@ -0,0 +1,11 @@
1
+ try {
2
+ module.exports = require('jasmine-core');
3
+ } catch (e) {
4
+ if (e.code === 'MODULE_NOT_FOUND') {
5
+ throw new Error(
6
+ "Could not load jasmine-core. Please make sure that it's installed."
7
+ );
8
+ } else {
9
+ throw e;
10
+ }
11
+ }
@@ -0,0 +1,38 @@
1
+ const path = require('path');
2
+ const url = require('url');
3
+
4
+ class ModuleLoader {
5
+ constructor(parentDir, deps) {
6
+ deps = deps || {};
7
+ this.require_ = deps.requireShim || requireShim;
8
+ this.import_ = deps.importShim || importShim;
9
+ this.parentDir_ = parentDir;
10
+ }
11
+
12
+ async load(filePath) {
13
+ if (filePath.endsWith('.json')) {
14
+ return this.require_(filePath);
15
+ } else {
16
+ // The ES module spec requires absolute import paths to be valid URLs. As
17
+ // of v16, Node enforces this on Windows but not on other OSes.
18
+ if (path.isAbsolute(filePath)) {
19
+ filePath = url.pathToFileURL(filePath);
20
+ } else if (filePath.startsWith('./') || filePath.startsWith('../')) {
21
+ filePath = url.pathToFileURL(path.resolve(this.parentDir_, filePath));
22
+ }
23
+
24
+ const module = await this.import_(filePath);
25
+ return module.default;
26
+ }
27
+ }
28
+ }
29
+
30
+ function requireShim(path) {
31
+ return require(path);
32
+ }
33
+
34
+ function importShim(path) {
35
+ return import(path);
36
+ }
37
+
38
+ module.exports = ModuleLoader;
package/lib/runner.js CHANGED
@@ -91,8 +91,6 @@ function urlParams(runOptions) {
91
91
  filterUndefined({
92
92
  random: runOptions.random,
93
93
  seed: runOptions.seed,
94
- failFast: runOptions.failFast,
95
- throwFailures: runOptions.stopOnFailure,
96
94
  spec: runOptions.filter,
97
95
  })
98
96
  )
package/lib/server.js CHANGED
@@ -15,11 +15,12 @@ class Server {
15
15
  */
16
16
  constructor(options) {
17
17
  this.options = options;
18
- this.clearReporters = options.clearReporters || false;
18
+ this.useHtmlReporter =
19
+ options.useHtmlReporter === undefined ? true : options.useHtmlReporter;
19
20
  this.batchReporter = options.batchReporter || false;
20
21
  this.jsonDomReporter = options.jsonDomReporter || false;
21
22
  this.projectBaseDir = options.projectBaseDir || path.resolve();
22
- this.jasmineCore = options.jasmineCore || require('jasmine-core');
23
+ this.jasmineCore = options.jasmineCore || require('./jasmineCore');
23
24
  this.jasmineCssFiles = this.jasmineCore.files.cssFiles.map(function(
24
25
  fileName
25
26
  ) {
@@ -29,11 +30,15 @@ class Server {
29
30
  .map(function(fileName) {
30
31
  return unWindows(path.join('/__jasmine__', fileName));
31
32
  })
32
- .concat(
33
- this.jasmineCore.files.bootFiles.map(function(fileName) {
34
- return unWindows(path.join('/__boot__', fileName));
35
- })
36
- );
33
+ .concat(this.bootFiles());
34
+ }
35
+
36
+ bootFiles() {
37
+ const bootFiles = this.jasmineCore.files.bootFiles.map(function(fileName) {
38
+ return unWindows(path.join('/__boot__', fileName));
39
+ });
40
+ bootFiles.splice(1, 0, '/__config__/config.js');
41
+ return bootFiles;
37
42
  }
38
43
 
39
44
  allCss() {
@@ -48,14 +53,14 @@ class Server {
48
53
  getUrls(baseDir, globs, urlRoot) {
49
54
  return findFiles(path.join(this.projectBaseDir, baseDir), globs || []).map(
50
55
  function(p) {
51
- return unWindows(path.join(urlRoot, p));
56
+ return isUrl(p) ? p : unWindows(path.join(urlRoot, p));
52
57
  }
53
58
  );
54
59
  }
55
60
 
56
61
  getSupportFiles() {
57
62
  var result = ['/__support__/loadEsModule.js'];
58
- if (this.clearReporters) {
63
+ if (!this.useHtmlReporter) {
59
64
  result.push('/__support__/clearReporters.js');
60
65
  }
61
66
 
@@ -118,15 +123,18 @@ class Server {
118
123
  express.static(path.join(this.projectBaseDir, this.options.srcDir))
119
124
  );
120
125
 
121
- var template = ejs.compile(
126
+ var indexTemplate = ejs.compile(
122
127
  fs.readFileSync(path.resolve(__dirname, '../run.html.ejs')).toString()
123
128
  );
129
+ var configTemplate = ejs.compile(
130
+ fs.readFileSync(path.resolve(__dirname, '../config.js.ejs')).toString()
131
+ );
124
132
 
125
133
  var self = this;
126
134
  app.get('/', function(req, res) {
127
135
  try {
128
136
  res.send(
129
- template({
137
+ indexTemplate({
130
138
  cssFiles: self.allCss(),
131
139
  jasmineJsFiles: self.jasmineJs(),
132
140
  userJsFiles: self.userJs(),
@@ -137,6 +145,19 @@ class Server {
137
145
  console.error(error);
138
146
  }
139
147
  });
148
+ app.get('/__config__/config.js', function(req, res) {
149
+ try {
150
+ res.append('Content-type', 'application/javascript');
151
+ res.send(
152
+ configTemplate({
153
+ envConfig: self.options.env || {},
154
+ })
155
+ );
156
+ } catch (error) {
157
+ res.status(500).send('An error occurred');
158
+ console.error(error);
159
+ }
160
+ });
140
161
 
141
162
  var port = findPort(serverOptions.port, this.options.port);
142
163
  return new Promise(resolve => {
@@ -200,6 +221,10 @@ function findPort(serverPort, optionsPort) {
200
221
  return 8888;
201
222
  }
202
223
 
224
+ function isUrl(s) {
225
+ return s.startsWith('http://') || s.startsWith('https://');
226
+ }
227
+
203
228
  function findFiles(baseDir, globs) {
204
229
  const { includeGlobs, excludeGlobs } = globs.reduce(
205
230
  function(ongoing, g) {
@@ -220,12 +245,16 @@ function findFiles(baseDir, globs) {
220
245
  const result = [];
221
246
 
222
247
  for (const g of includeGlobs) {
223
- const files = glob.sync(g, { ignore: excludeGlobs, cwd: baseDir });
248
+ if (isUrl(g)) {
249
+ result.push(g);
250
+ } else {
251
+ const files = glob.sync(g, { ignore: excludeGlobs, cwd: baseDir });
224
252
 
225
- for (const f of files) {
226
- // De-duplicate
227
- if (result.indexOf(f) === -1) {
228
- result.push(f);
253
+ for (const f of files) {
254
+ // De-duplicate
255
+ if (result.indexOf(f) === -1) {
256
+ result.push(f);
257
+ }
229
258
  }
230
259
  }
231
260
  }
package/lib/types.js CHANGED
@@ -9,9 +9,10 @@
9
9
  * @see Server
10
10
  */
11
11
  /**
12
- * Whether to remove Jasmine's default reporters before executing the suite.
13
- * @name ServerCtorOptions#clearReporters
12
+ * Whether to use Jasmine's default HTML reporter.
13
+ * @name ServerCtorOptions#useHtmlReporter
14
14
  * @type boolean | undefined
15
+ * @default true
15
16
  */
16
17
  /**
17
18
  * An array of CSS file paths or {@link https://github.com/isaacs/node-glob#glob-primer|globs}
@@ -71,26 +72,37 @@
71
72
  */
72
73
 
73
74
  /**
74
- * @interface RunSpecsOptions
75
+ * Specifies the properties of the configuration file, as well as
76
+ * the argument to runSpecs.
77
+ *
78
+ * @interface Configuration
75
79
  * @augments ServerCtorOptions
76
- * @augments RunnerRunOptions
77
80
  */
78
81
  /**
79
82
  * The browser to run the specs in.
80
- * @name RunSpecsOptions#browser
83
+ * @name Configuration#browser
81
84
  * @type string | BrowserInfo | undefined
82
85
  */
83
86
  /**
84
- * Whether to use color in the console output. Defaults to true.
85
- * @name RunSpecsOptions#color
87
+ * Whether to use color in the console output.
88
+ * @name Configuration#color
86
89
  * @type boolean | undefined
90
+ * @default true
91
+ */
92
+ /**
93
+ * An array of {@link https://jasmine.github.io/api/edge/Reporter.html|reporters}
94
+ * or names of modules defining reporters. If an entry is a string, it should be
95
+ * the {@link https://nodejs.org/api/esm.html#esm_import_specifiers|import specifier}
96
+ * for a module that default exports a reporter constructor. The constructor will
97
+ * be called with no arguments.
98
+ * @name Configuration#reporters
99
+ * @type Array<string | Reporter>
87
100
  */
88
101
  /**
89
- * The name of a module to get a reporter from. The module will be loaded
90
- * using `require` and its default export must be a constructor that creates a
91
- * {@link https://jasmine.github.io/api/edge/Reporter.html|reporter}.
92
- * @name RunSpecsOptions#color
102
+ * Whether to use the built-in {@link ConsoleReporter}.
103
+ * @name Configuration#useConsoleReporter
93
104
  * @type boolean | undefined
105
+ * @default true
94
106
  */
95
107
 
96
108
  /**
package/package.json CHANGED
@@ -1,27 +1,30 @@
1
1
  {
2
2
  "name": "jasmine-browser-runner",
3
- "version": "0.6.0",
3
+ "version": "0.10.0",
4
4
  "description": "Serve and run your Jasmine specs in a browser",
5
- "main": "index.js",
6
- "bin": "bin/jasmine-browser",
5
+ "bin": "bin/jasmine-browser-runner",
6
+ "exports": "./index.js",
7
7
  "files": [
8
8
  "MIT.LICENSE",
9
9
  "README.md",
10
10
  "package.json",
11
11
  "index.js",
12
+ "config.js.ejs",
12
13
  "run.html.ejs",
13
14
  "bin/*.js",
14
- "lib/**/*.js"
15
+ "lib/**/*.js",
16
+ "lib/examples/default_config.json",
17
+ "lib/examples/default_esm_config.json"
15
18
  ],
16
19
  "scripts": {
17
- "posttest": "eslint bin/* lib/*.js lib/**/*.js spec/**/*.js scripts/*.js index.js --ignore-path=.styleIgnore && prettier --check --ignore-path=.styleIgnore lib/*.js lib/**/*.js spec/**/*.js scripts/*.js index.js",
20
+ "posttest": "eslint bin/* lib spec scripts index.js --ignore-path=.styleIgnore && prettier --check --ignore-path=.styleIgnore lib/*.js lib/**/*.js spec/**/*.js scripts/*.js index.js",
18
21
  "test": "jasmine",
19
22
  "cleanup": "prettier --write --ignore-path=.styleIgnore lib/*.js lib/**/*.js spec/**/*.js scripts/*.js index.js",
20
23
  "release": "node scripts/release.js"
21
24
  },
22
25
  "repository": {
23
26
  "type": "git",
24
- "url": "git+https://github.com/jasmine/jasmine-browser.git"
27
+ "url": "git+https://github.com/jasmine/jasmine-browser-runner.git"
25
28
  },
26
29
  "keywords": [
27
30
  "jasmine",
@@ -29,25 +32,28 @@
29
32
  "tdd"
30
33
  ],
31
34
  "engines": {
32
- "node": ">= 10.0"
35
+ "node": ">=14"
33
36
  },
34
37
  "author": "Slackersoft",
35
38
  "license": "MIT",
36
39
  "bugs": {
37
- "url": "https://github.com/jasmine/jasmine-browser/issues"
40
+ "url": "https://github.com/jasmine/jasmine-browser-runner/issues"
38
41
  },
39
- "homepage": "https://github.com/jasmine/jasmine-browser#readme",
42
+ "homepage": "https://github.com/jasmine/jasmine-browser-runner#readme",
40
43
  "dependencies": {
41
- "ejs": "^2.6.1",
44
+ "ejs": "^3.1.6",
42
45
  "express": "^4.16.4",
43
46
  "glob": "^7.1.7",
44
- "jasmine": "^3.6.0",
45
- "jasmine-core": "^3.6.0",
46
- "selenium-webdriver": "^4.0.0-alpha.1"
47
+ "selenium-webdriver": "^4.1.0"
48
+ },
49
+ "peerDependencies": {
50
+ "jasmine-core": "^3.9.0"
47
51
  },
48
52
  "devDependencies": {
49
- "eslint": "^5.16.0",
50
- "eslint-plugin-jasmine": "^2.10.1",
53
+ "eslint": "^8.3.0",
54
+ "eslint-plugin-jasmine": "^4.1.3",
55
+ "jasmine": "^3.9.0",
56
+ "jasmine-core": "^3.9.0",
51
57
  "prettier": "^1.17.1",
52
58
  "shelljs": "^0.8.3",
53
59
  "temp": "^0.9.4"
@@ -69,7 +75,7 @@
69
75
  "expectAsync": "readonly"
70
76
  },
71
77
  "parserOptions": {
72
- "ecmaVersion": 2018
78
+ "ecmaVersion": 11
73
79
  },
74
80
  "plugins": [
75
81
  "jasmine"
@@ -91,7 +97,13 @@
91
97
  "block-spacing": "error",
92
98
  "comma-dangle": [
93
99
  "error",
94
- "always-multiline"
100
+ {
101
+ "arrays": "always-multiline",
102
+ "objects": "always-multiline",
103
+ "imports": "always-multiline",
104
+ "exports": "always-multiline",
105
+ "functions": "never"
106
+ }
95
107
  ],
96
108
  "func-call-spacing": [
97
109
  "error",
@@ -108,11 +120,13 @@
108
120
  "space-before-blocks": "error",
109
121
  "no-console": "off"
110
122
  },
111
- "overrides": {
112
- "files": "spec/**/*.js",
113
- "env": {
114
- "jasmine": true
123
+ "overrides": [
124
+ {
125
+ "files": "spec/**/*.js",
126
+ "env": {
127
+ "jasmine": true
128
+ }
115
129
  }
116
- }
130
+ ]
117
131
  }
118
132
  }
package/run.html.ejs CHANGED
@@ -8,6 +8,8 @@
8
8
  <% cssFiles.forEach(function(cssFile) { %>
9
9
  <link rel="stylesheet" href="<%= cssFile %>" type="text/css" media="screen"/>
10
10
  <% }) %>
11
+ </head>
12
+ <body>
11
13
  <% jasmineJsFiles.forEach(function(jsFile) { %>
12
14
  <script src="<%= jsFile %>" type="text/javascript"></script>
13
15
  <% }) %>
@@ -19,8 +21,6 @@
19
21
  <% } %>
20
22
  <% }) %>
21
23
 
22
- </head>
23
- <body>
24
24
  <div id="jasmine_content"></div>
25
25
  </body>
26
26
  </html>
@@ -1,19 +0,0 @@
1
- module.exports =
2
- '{\n' +
3
- ' "srcDir": "src",\n' +
4
- ' "srcFiles": [\n' +
5
- ' "**/*.?(m)js"\n' +
6
- ' ],\n' +
7
- ' "specDir": "spec",\n' +
8
- ' "specFiles": [\n' +
9
- ' "**/*[sS]pec.?(m)js"\n' +
10
- ' ],\n' +
11
- ' "helpers": [\n' +
12
- ' "helpers/**/*.?(m)js"\n' +
13
- ' ],\n' +
14
- ' "random": true,\n' +
15
- ' "stopSpecOnExpectationFailure": false,\n' +
16
- ' "browser": {\n' +
17
- ' "name": "firefox"\n' +
18
- ' }\n' +
19
- '}';