jasmine-browser-runner 0.7.0 → 1.0.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:
@@ -60,18 +60,95 @@ To use a browser other than Firefox, add a `browser` field to
60
60
  }
61
61
  ```
62
62
 
63
- Its value can be `"internet explorer"`, `"firefox"`, `"safari"`,
63
+ Its value can be `"firefox"`, `"safari"`,
64
64
  `"MicrosoftEdge"`, `"chrome"`, or `"headlessChrome"`.
65
65
 
66
66
  ## ES module support
67
67
 
68
68
  If a source, spec, or helper file's name ends in `.mjs`, it will be loaded as
69
- an ES module rather than a regular script. Note that ES modules are not
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
- modules. ES modules will silently fail to load in browsers that don't
73
- support them. Other kinds of load-time errors are detected and reported as suite
74
- errors.
69
+ an ES module rather than a regular script.
70
+
71
+ To allow spec files to import source files via relative paths, set the `specDir`
72
+ config field to something that's high enough up to include both spec and source
73
+ files, and set `srcFiles` to `[]`. You can autogenerate such a configuration by
74
+ running `npx jasmine-browser-runner init --esm`.
75
+
76
+ ## Use with Rails
77
+
78
+ You can use jasmine-browser-runner to test your Rails application's JavaScript,
79
+ whether you use the Asset Pipeline or Webpacker.
80
+
81
+ ### Webpacker
82
+
83
+ 1. Run `yarn add --dev jasmine-browser-runner`.
84
+ 2. Run `npx jasmine-browser-runner init`.
85
+ 3. Edit `spec/support/jasmine-browser.json` as follows:
86
+ ```
87
+ {
88
+ "srcDir": ".",
89
+ "srcFiles": [],
90
+ "specDir": "public/packs/js",
91
+ "specFiles": [
92
+ "specs-*.js"
93
+ ],
94
+ "helpers": [],
95
+ // ...
96
+ }
97
+ ```
98
+ 4. Create `app/javascript/packs/specs.js` (or `app/javascript/packs/specs.jsx`
99
+ if you use JSX) as follows:
100
+ ```
101
+ (function() {
102
+ 'use strict';
103
+
104
+ function requireAll(context) {
105
+ context.keys().forEach(context);
106
+ }
107
+
108
+ requireAll(require.context('spec/javascript/helpers/', true, /\.js/));
109
+ requireAll(require.context('spec/javascript/', true, /[sS]pec\.js/));
110
+ })();
111
+ ```
112
+ 5. Add `'spec/javascript'` to the `additional_paths` array in `config/webpacker.yml`.
113
+ 6. Put your spec files in `spec/javascript`.
114
+
115
+ To run the specs:
116
+
117
+ 1. Run `bin/webpack --watch`.
118
+ 2. Run `npx jasmine-browser-runner`.
119
+ 3. visit <http://localhost:8888>.
120
+
121
+ ### Asset Pipeline
122
+
123
+ 1. Run `yarn init` if there isn't already `package.json` file in the root of
124
+ the Rails application.
125
+ 2. Run `yarn add --dev jasmine-browser-runner`.
126
+ 3. Run `npx jasmine-browser-runner init`.
127
+ 5. Edit `spec/support/jasmine-browser.json` as follows:
128
+ ```
129
+ {
130
+ "srcDir": "public/assets",
131
+ "srcFiles": [
132
+ "application-*.js"
133
+ ],
134
+ "specDir": "spec/javascript",
135
+ "specFiles": [
136
+ "**/*[sS]pec.?(m)js"
137
+ ],
138
+ "helpers": [
139
+ "helpers/**/*.?(m)js"
140
+ ],
141
+ // ...
142
+ }
143
+ ```
144
+ 6. Put your spec files in `spec/javascript`.
145
+
146
+ To run the specs:
147
+
148
+ 1. Either run `bundle exec rake assets:precompile` or start the Rails
149
+ application in an environment that's configured to precompile assets.
150
+ 2. Run `npx jasmine-browser-runner`.
151
+ 3. Visit <http://localhost:8888>.
75
152
 
76
153
  ## Saucelabs support
77
154
 
@@ -1,9 +1,10 @@
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
+ const UsageError = require('../lib/usage_error');
7
8
 
8
9
  var command = new Command({
9
10
  baseDir: path.resolve(),
@@ -13,6 +14,9 @@ var command = new Command({
13
14
  });
14
15
 
15
16
  command.run(process.argv.slice(2)).catch(function(error) {
16
- console.error(error);
17
+ if (!(error instanceof UsageError)) {
18
+ console.error(error);
19
+ }
20
+
17
21
  process.exitCode = 1;
18
22
  });
package/config.js.ejs ADDED
@@ -0,0 +1 @@
1
+ jasmine.getEnv().configure(<%- JSON.stringify(envConfig) %>);
package/index.js CHANGED
@@ -1,34 +1,46 @@
1
- const DefaultReporter = require('./lib/default_reporter'),
1
+ const ConsoleReporter = require('./lib/console_reporter'),
2
2
  webdriverModule = require('./lib/webdriver'),
3
3
  Server = require('./lib/server'),
4
4
  Runner = require('./lib/runner'),
5
5
  ModuleLoader = require('./lib/moduleLoader');
6
6
 
7
7
  async function createReporters(options) {
8
- if (!options.reporters) {
9
- return [new DefaultReporter(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 = [];
13
- // Resolve relative paths relative to the cwd, rather than the default
14
- // which is the directory containing the moduleLoader module.
15
- const loader = new ModuleLoader(process.cwd());
16
-
17
- for (const reporterOrModuleName of options.reporters) {
18
- if (typeof reporterOrModuleName === 'object') {
19
- result.push(reporterOrModuleName);
20
- } else {
21
- try {
22
- const Reporter = await loader.load(reporterOrModuleName);
23
- result.push(new Reporter());
24
- } catch (e) {
25
- throw new Error(
26
- `Failed to register reporter ${reporterOrModuleName}: ${e.message}`
27
- );
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());
20
+
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
+ }
28
33
  }
29
34
  }
30
35
  }
31
36
 
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
+ }
43
+
32
44
  return result;
33
45
  }
34
46
 
@@ -49,17 +61,11 @@ module.exports = {
49
61
  },
50
62
  /**
51
63
  * Runs the specs.
52
- * @param {RunSpecsOptions} options
64
+ * @param {Configuration} options
53
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.
54
66
  */
55
67
  runSpecs: async function(options, deps) {
56
68
  options = options || {};
57
- if (options.browser && options.browser.name === 'internet explorer') {
58
- options.jsonDomReporter = true;
59
- } else {
60
- options.batchReporter = true;
61
- }
62
-
63
69
  deps = deps || {};
64
70
  const ServerClass = deps.Server || Server;
65
71
  const RunnerClass = deps.Runner || Runner;
@@ -84,12 +90,19 @@ module.exports = {
84
90
 
85
91
  const details = await runner.run(options);
86
92
 
93
+ // Use 0 only for complete success
94
+ // Avoid 1 because Node uses that for unhandled exceptions etc., and
95
+ // some users have CI systems that want to distinguish between spec
96
+ // failures and crashes.
87
97
  if (details.overallStatus === 'passed') {
88
98
  setExitCode(0);
99
+ } else if (anyLoadErrors(details)) {
100
+ // Use node's general failure code
101
+ setExitCode(1);
89
102
  } else if (details.overallStatus === 'incomplete') {
90
103
  setExitCode(2);
91
104
  } else {
92
- setExitCode(1);
105
+ setExitCode(3);
93
106
  }
94
107
 
95
108
  return details;
@@ -108,5 +121,14 @@ module.exports = {
108
121
  },
109
122
  Server,
110
123
  Runner,
111
- DefaultReporter,
124
+ ConsoleReporter,
112
125
  };
126
+
127
+ function anyLoadErrors(jasmineDoneInfo) {
128
+ const failures = jasmineDoneInfo.failedExpectations || [];
129
+ const loadError = failures.find(function(fe) {
130
+ return fe.globalErrorType === 'load';
131
+ });
132
+
133
+ return !!loadError;
134
+ }
package/lib/command.js CHANGED
@@ -1,14 +1,15 @@
1
1
  const path = require('path');
2
2
  const fs = require('fs');
3
- const { loadConfig, validateConfig, defaultConfig } = require('./config');
3
+ const {
4
+ loadConfig,
5
+ validateConfig,
6
+ defaultConfig,
7
+ defaultEsmConfig,
8
+ } = require('./config');
9
+ const UsageError = require('./usage_error');
4
10
 
5
11
  const commonOptions = [
6
12
  { 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
13
  { name: 'port', type: 'number', description: 'port to run the server on' },
13
14
  ];
14
15
 
@@ -22,6 +23,14 @@ const subCommands = [
22
23
  {
23
24
  name: 'init',
24
25
  description: 'initialize a new Jasmine project',
26
+ options: [
27
+ {
28
+ name: 'esm',
29
+ type: 'bool',
30
+ description:
31
+ 'configure for use with ES modules (<script type="module">)',
32
+ },
33
+ ],
25
34
  },
26
35
  {
27
36
  name: 'serve',
@@ -35,6 +44,7 @@ const subCommands = [
35
44
  {
36
45
  name: 'color',
37
46
  type: 'bool',
47
+ reversable: true,
38
48
  description: 'turn on or off color output',
39
49
  },
40
50
  {
@@ -43,19 +53,15 @@ const subCommands = [
43
53
  description:
44
54
  'filter specs to run only those that match the given string',
45
55
  },
46
- {
47
- name: 'stop-on-failure',
48
- type: 'bool',
49
- description: 'stop spec execution on expectation failure',
50
- },
51
56
  {
52
57
  name: 'fail-fast',
53
58
  type: 'bool',
54
- description: 'stop Jasmine execution on spec failure',
59
+ description: 'stop Jasmine execution on the first failure',
55
60
  },
56
61
  {
57
62
  name: 'random',
58
63
  type: 'bool',
64
+ reversable: true,
59
65
  description: 'turn on or off randomization',
60
66
  },
61
67
  {
@@ -91,7 +97,8 @@ class Command {
91
97
  });
92
98
 
93
99
  if (!toRun) {
94
- return this.help();
100
+ this.help();
101
+ throw new UsageError();
95
102
  } else if (toRun.options) {
96
103
  return this[toRun.name](parseOptions(toRun, args.slice(1)));
97
104
  } else {
@@ -141,7 +148,7 @@ class Command {
141
148
  this._logger.log('jasmine-core v' + this._config.jasmineCore.version());
142
149
  }
143
150
 
144
- init() {
151
+ init(options) {
145
152
  const dest = 'spec/support/jasmine-browser.json';
146
153
 
147
154
  if (fs.existsSync(dest)) {
@@ -150,7 +157,13 @@ class Command {
150
157
  }
151
158
 
152
159
  fs.mkdirSync(path.dirname(dest), { recursive: true });
153
- fs.writeFileSync(dest, defaultConfig);
160
+
161
+ if (options.esm) {
162
+ fs.writeFileSync(dest, defaultEsmConfig());
163
+ } else {
164
+ fs.writeFileSync(dest, defaultConfig());
165
+ }
166
+
154
167
  this._logger.log(`Wrote configuration to ${dest}.`);
155
168
  }
156
169
 
@@ -161,7 +174,17 @@ class Command {
161
174
  }
162
175
 
163
176
  async runSpecs(options) {
164
- await this._config.jasmineBrowser.runSpecs(await this._loadConfig(options));
177
+ const config = await this._loadConfig(options);
178
+
179
+ if (options['fail-fast']) {
180
+ config.env = {
181
+ ...config.env,
182
+ stopSpecOnExpectationFailure: true,
183
+ stopOnSpecFailure: true,
184
+ };
185
+ }
186
+
187
+ await this._config.jasmineBrowser.runSpecs(config);
165
188
  }
166
189
 
167
190
  async _loadConfig(options) {
@@ -210,9 +233,13 @@ function commandText(command) {
210
233
  }
211
234
 
212
235
  function optionText(option) {
213
- return option.type === 'bool'
214
- ? `--[no-]${option.name}`
215
- : `--${option.name}=<value>`;
236
+ if (option.type !== 'bool') {
237
+ return `--${option.name}=<value>`;
238
+ } else if (option.reversable) {
239
+ return `--[no-]${option.name}`;
240
+ } else {
241
+ return `--${option.name}`;
242
+ }
216
243
  }
217
244
 
218
245
  function wrapDescription(indentLevel, prefixWidth, maxWidth, text) {
package/lib/config.js CHANGED
@@ -52,24 +52,24 @@ function validateConfig(config) {
52
52
  }
53
53
  }
54
54
 
55
- const defaultConfig =
56
- '{\n' +
57
- ' "srcDir": "src",\n' +
58
- ' "srcFiles": [\n' +
59
- ' "**/*.?(m)js"\n' +
60
- ' ],\n' +
61
- ' "specDir": "spec",\n' +
62
- ' "specFiles": [\n' +
63
- ' "**/*[sS]pec.?(m)js"\n' +
64
- ' ],\n' +
65
- ' "helpers": [\n' +
66
- ' "helpers/**/*.?(m)js"\n' +
67
- ' ],\n' +
68
- ' "random": true,\n' +
69
- ' "stopSpecOnExpectationFailure": false,\n' +
70
- ' "browser": {\n' +
71
- ' "name": "firefox"\n' +
72
- ' }\n' +
73
- '}';
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
+ }
74
69
 
75
- module.exports = { loadConfig, validateConfig, defaultConfig };
70
+ module.exports = {
71
+ loadConfig,
72
+ validateConfig,
73
+ defaultConfig,
74
+ defaultEsmConfig,
75
+ };
@@ -0,0 +1,311 @@
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
+ if (result.failedExpectations) {
80
+ failureCount += result.failedExpectations.length;
81
+ }
82
+
83
+ printNewline();
84
+ printNewline();
85
+ if (failedSpecs.length > 0) {
86
+ print('Failures:');
87
+ }
88
+ for (let i = 0; i < failedSpecs.length; i++) {
89
+ specFailureDetails(failedSpecs[i], i + 1);
90
+ }
91
+
92
+ for (let i = 0; i < failedSuites.length; i++) {
93
+ suiteFailureDetails(failedSuites[i]);
94
+ }
95
+
96
+ if (
97
+ result &&
98
+ result.failedExpectations &&
99
+ result.failedExpectations.length > 0
100
+ ) {
101
+ suiteFailureDetails(result);
102
+ }
103
+
104
+ if (pendingSpecs.length > 0) {
105
+ print('Pending:');
106
+ }
107
+ for (let i = 0; i < pendingSpecs.length; i++) {
108
+ pendingSpecDetails(pendingSpecs[i], i + 1);
109
+ }
110
+
111
+ if (specCount > 0) {
112
+ printNewline();
113
+
114
+ if (executableSpecCount !== specCount) {
115
+ print(
116
+ 'Ran ' +
117
+ executableSpecCount +
118
+ ' of ' +
119
+ specCount +
120
+ plural(' spec', specCount)
121
+ );
122
+ printNewline();
123
+ }
124
+ let specCounts =
125
+ executableSpecCount +
126
+ ' ' +
127
+ plural('spec', executableSpecCount) +
128
+ ', ' +
129
+ failureCount +
130
+ ' ' +
131
+ plural('failure', failureCount);
132
+
133
+ if (pendingSpecs.length) {
134
+ specCounts +=
135
+ ', ' +
136
+ pendingSpecs.length +
137
+ ' pending ' +
138
+ plural('spec', pendingSpecs.length);
139
+ }
140
+
141
+ print(specCounts);
142
+ } else {
143
+ print('No specs found');
144
+ }
145
+
146
+ printNewline();
147
+ const seconds = result ? result.totalTime / 1000 : 0;
148
+ print('Finished in ' + seconds + ' ' + plural('second', seconds));
149
+ printNewline();
150
+
151
+ if (result && result.overallStatus === 'incomplete') {
152
+ print('Incomplete: ' + result.incompleteReason);
153
+ printNewline();
154
+ }
155
+
156
+ if (result && result.order && result.order.random) {
157
+ print('Randomized with seed ' + result.order.seed);
158
+ print(
159
+ ' (jasmine-browser-runner runSpecs --seed=' + result.order.seed + ')'
160
+ );
161
+ printNewline();
162
+ }
163
+ };
164
+
165
+ this.specDone = function(result) {
166
+ specCount++;
167
+
168
+ if (result.status == 'pending') {
169
+ pendingSpecs.push(result);
170
+ executableSpecCount++;
171
+ print(colored('yellow', '*'));
172
+ return;
173
+ }
174
+
175
+ if (result.status == 'passed') {
176
+ executableSpecCount++;
177
+ print(colored('green', '.'));
178
+ return;
179
+ }
180
+
181
+ if (result.status == 'failed') {
182
+ failureCount++;
183
+ failedSpecs.push(result);
184
+ executableSpecCount++;
185
+ print(colored('red', 'F'));
186
+ }
187
+ };
188
+
189
+ this.suiteDone = function(result) {
190
+ if (result.failedExpectations && result.failedExpectations.length > 0) {
191
+ failureCount++;
192
+ failedSuites.push(result);
193
+ }
194
+ };
195
+
196
+ return this;
197
+
198
+ function printNewline() {
199
+ print('\n');
200
+ }
201
+
202
+ function colored(color, str) {
203
+ return showColors ? ansi[color] + str + ansi.none : str;
204
+ }
205
+
206
+ function plural(str, count) {
207
+ return count == 1 ? str : str + 's';
208
+ }
209
+
210
+ function repeat(thing, times) {
211
+ const arr = [];
212
+ for (let i = 0; i < times; i++) {
213
+ arr.push(thing);
214
+ }
215
+ return arr;
216
+ }
217
+
218
+ function indent(str, spaces) {
219
+ const lines = (str || '').split('\n');
220
+ const newArr = [];
221
+ for (let i = 0; i < lines.length; i++) {
222
+ newArr.push(repeat(' ', spaces).join('') + lines[i]);
223
+ }
224
+ return newArr.join('\n');
225
+ }
226
+
227
+ function defaultStackFilter(stack) {
228
+ if (!stack) {
229
+ return '';
230
+ }
231
+
232
+ const filteredStack = stack
233
+ .split('\n')
234
+ .filter(function(stackLine) {
235
+ return stackLine.indexOf(jasmineCorePath) === -1;
236
+ })
237
+ .join('\n');
238
+ return filteredStack;
239
+ }
240
+
241
+ function specFailureDetails(result, failedSpecNumber) {
242
+ printNewline();
243
+ print(failedSpecNumber + ') ');
244
+ print(result.fullName);
245
+ printFailedExpectations(result);
246
+
247
+ if (result.debugLogs) {
248
+ printNewline();
249
+ print(indent('Debug logs:', 2));
250
+ printNewline();
251
+
252
+ for (const entry of result.debugLogs) {
253
+ print(indent(`${entry.timestamp}ms: ${entry.message}`, 4));
254
+ printNewline();
255
+ }
256
+ }
257
+ }
258
+
259
+ function suiteFailureDetails(result) {
260
+ printNewline();
261
+ print('Suite error: ' + result.fullName);
262
+ printFailedExpectations(result);
263
+ }
264
+
265
+ function printFailedExpectations(result) {
266
+ for (let i = 0; i < result.failedExpectations.length; i++) {
267
+ const failedExpectation = result.failedExpectations[i];
268
+ printNewline();
269
+ print(indent('Message:', 2));
270
+ printNewline();
271
+ print(colored('red', indent(failedExpectation.message, 4)));
272
+ printNewline();
273
+ print(indent('Stack:', 2));
274
+ printNewline();
275
+ print(indent(stackFilter(failedExpectation.stack), 4));
276
+ }
277
+
278
+ // When failSpecWithNoExpectations = true and a spec fails because of no expectations found,
279
+ // jasmine-core reports it as a failure with no message.
280
+ //
281
+ // Therefore we assume that when there are no failed or passed expectations,
282
+ // the failure was because of our failSpecWithNoExpectations setting.
283
+ //
284
+ // Same logic is used by jasmine.HtmlReporter, see https://github.com/jasmine/jasmine/blob/main/src/html/HtmlReporter.js
285
+ if (
286
+ result.failedExpectations.length === 0 &&
287
+ result.passedExpectations.length === 0
288
+ ) {
289
+ printNewline();
290
+ print(indent('Message:', 2));
291
+ printNewline();
292
+ print(colored('red', indent('Spec has no expectations', 4)));
293
+ }
294
+
295
+ printNewline();
296
+ }
297
+
298
+ function pendingSpecDetails(result, pendingSpecNumber) {
299
+ printNewline();
300
+ printNewline();
301
+ print(pendingSpecNumber + ') ');
302
+ print(result.fullName);
303
+ printNewline();
304
+ let pendingReason = 'No reason given';
305
+ if (result.pendingReason && result.pendingReason !== '') {
306
+ pendingReason = result.pendingReason;
307
+ }
308
+ print(indent(colored('yellow', pendingReason), 2));
309
+ printNewline();
310
+ }
311
+ }
@@ -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
+ }
package/lib/runner.js CHANGED
@@ -18,22 +18,6 @@ function getBatch(driver) {
18
18
  );
19
19
  }
20
20
 
21
- async function jsDomReporterResults(webdriver) {
22
- const resultsEl = await new Promise(function(resolve) {
23
- function poll() {
24
- webdriver.findElement({ id: 'jasmine-jsonDomReporter-done' }).then(
25
- el => resolve(el),
26
- () => setTimeout(poll, 1000)
27
- );
28
- }
29
-
30
- poll();
31
- });
32
-
33
- const text = await resultsEl.getText();
34
- return JSON.parse(text);
35
- }
36
-
37
21
  function proxyToReporters(batch, reporters) {
38
22
  batch.forEach(function(result) {
39
23
  const fn = result[0];
@@ -91,8 +75,6 @@ function urlParams(runOptions) {
91
75
  filterUndefined({
92
76
  random: runOptions.random,
93
77
  seed: runOptions.seed,
94
- failFast: runOptions.failFast,
95
- throwFailures: runOptions.stopOnFailure,
96
78
  spec: runOptions.filter,
97
79
  })
98
80
  )
@@ -122,21 +104,7 @@ class Runner {
122
104
  this._options.host + urlParams(runOptions)
123
105
  );
124
106
 
125
- if (runOptions.jsonDomReporter) {
126
- console.log(
127
- "Note: Results won't be shown until the entire suite has finished running."
128
- );
129
- let results = await jsDomReporterResults(this._options.webdriver);
130
- proxyToReporters(results, this._options.reporters);
131
-
132
- if (!isDone(results)) {
133
- throw new Error("Didn't find a jasmineDone event");
134
- }
135
-
136
- return runDetails(results);
137
- } else {
138
- return await runTillEnd(this._options.webdriver, this._options.reporters);
139
- }
107
+ return await runTillEnd(this._options.webdriver, this._options.reporters);
140
108
  }
141
109
  }
142
110
 
package/lib/server.js CHANGED
@@ -15,11 +15,10 @@ class Server {
15
15
  */
16
16
  constructor(options) {
17
17
  this.options = options;
18
- this.clearReporters = options.clearReporters || false;
19
- this.batchReporter = options.batchReporter || false;
20
- this.jsonDomReporter = options.jsonDomReporter || false;
18
+ this.useHtmlReporter =
19
+ options.useHtmlReporter === undefined ? true : options.useHtmlReporter;
21
20
  this.projectBaseDir = options.projectBaseDir || path.resolve();
22
- this.jasmineCore = options.jasmineCore || require('jasmine-core');
21
+ this.jasmineCore = options.jasmineCore || require('./jasmineCore');
23
22
  this.jasmineCssFiles = this.jasmineCore.files.cssFiles.map(function(
24
23
  fileName
25
24
  ) {
@@ -29,11 +28,15 @@ class Server {
29
28
  .map(function(fileName) {
30
29
  return unWindows(path.join('/__jasmine__', fileName));
31
30
  })
32
- .concat(
33
- this.jasmineCore.files.bootFiles.map(function(fileName) {
34
- return unWindows(path.join('/__boot__', fileName));
35
- })
36
- );
31
+ .concat(this.bootFiles());
32
+ }
33
+
34
+ bootFiles() {
35
+ const bootFiles = this.jasmineCore.files.bootFiles.map(function(fileName) {
36
+ return unWindows(path.join('/__boot__', fileName));
37
+ });
38
+ bootFiles.splice(1, 0, '/__config__/config.js');
39
+ return bootFiles;
37
40
  }
38
41
 
39
42
  allCss() {
@@ -48,25 +51,18 @@ class Server {
48
51
  getUrls(baseDir, globs, urlRoot) {
49
52
  return findFiles(path.join(this.projectBaseDir, baseDir), globs || []).map(
50
53
  function(p) {
51
- return unWindows(path.join(urlRoot, p));
54
+ return isUrl(p) ? p : unWindows(path.join(urlRoot, p));
52
55
  }
53
56
  );
54
57
  }
55
58
 
56
59
  getSupportFiles() {
57
60
  var result = ['/__support__/loadEsModule.js'];
58
- if (this.clearReporters) {
61
+ if (!this.useHtmlReporter) {
59
62
  result.push('/__support__/clearReporters.js');
60
63
  }
61
64
 
62
- if (this.batchReporter) {
63
- result.push('/__support__/batchReporter.js');
64
- }
65
-
66
- if (this.jsonDomReporter) {
67
- result.push('/__support__/jsonDomReporter.js');
68
- }
69
-
65
+ result.push('/__support__/batchReporter.js');
70
66
  return result;
71
67
  }
72
68
 
@@ -118,15 +114,18 @@ class Server {
118
114
  express.static(path.join(this.projectBaseDir, this.options.srcDir))
119
115
  );
120
116
 
121
- var template = ejs.compile(
117
+ var indexTemplate = ejs.compile(
122
118
  fs.readFileSync(path.resolve(__dirname, '../run.html.ejs')).toString()
123
119
  );
120
+ var configTemplate = ejs.compile(
121
+ fs.readFileSync(path.resolve(__dirname, '../config.js.ejs')).toString()
122
+ );
124
123
 
125
124
  var self = this;
126
125
  app.get('/', function(req, res) {
127
126
  try {
128
127
  res.send(
129
- template({
128
+ indexTemplate({
130
129
  cssFiles: self.allCss(),
131
130
  jasmineJsFiles: self.jasmineJs(),
132
131
  userJsFiles: self.userJs(),
@@ -137,6 +136,19 @@ class Server {
137
136
  console.error(error);
138
137
  }
139
138
  });
139
+ app.get('/__config__/config.js', function(req, res) {
140
+ try {
141
+ res.append('Content-type', 'application/javascript');
142
+ res.send(
143
+ configTemplate({
144
+ envConfig: self.options.env || {},
145
+ })
146
+ );
147
+ } catch (error) {
148
+ res.status(500).send('An error occurred');
149
+ console.error(error);
150
+ }
151
+ });
140
152
 
141
153
  var port = findPort(serverOptions.port, this.options.port);
142
154
  return new Promise(resolve => {
@@ -200,6 +212,10 @@ function findPort(serverPort, optionsPort) {
200
212
  return 8888;
201
213
  }
202
214
 
215
+ function isUrl(s) {
216
+ return s.startsWith('http://') || s.startsWith('https://');
217
+ }
218
+
203
219
  function findFiles(baseDir, globs) {
204
220
  const { includeGlobs, excludeGlobs } = globs.reduce(
205
221
  function(ongoing, g) {
@@ -220,12 +236,16 @@ function findFiles(baseDir, globs) {
220
236
  const result = [];
221
237
 
222
238
  for (const g of includeGlobs) {
223
- const files = glob.sync(g, { ignore: excludeGlobs, cwd: baseDir });
239
+ if (isUrl(g)) {
240
+ result.push(g);
241
+ } else {
242
+ const files = glob.sync(g, { ignore: excludeGlobs, cwd: baseDir });
224
243
 
225
- for (const f of files) {
226
- // De-duplicate
227
- if (result.indexOf(f) === -1) {
228
- result.push(f);
244
+ for (const f of files) {
245
+ // De-duplicate
246
+ if (result.indexOf(f) === -1) {
247
+ result.push(f);
248
+ }
229
249
  }
230
250
  }
231
251
  }
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
  /**
@@ -104,7 +116,7 @@
104
116
  * @type boolean | undefined
105
117
  */
106
118
  /**
107
- * The browser name. Valid values include "internet explorer", "firefox",
119
+ * The browser name. Valid values include "firefox",
108
120
  * "safari", "MicrosoftEdge", "chrome", and "headlessChrome".
109
121
  * @name BrowserInfo#name
110
122
  * @type string | undefined
@@ -0,0 +1,9 @@
1
+ class UsageError extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ }
5
+ }
6
+
7
+ UsageError.prototype.name = 'UsageError';
8
+
9
+ module.exports = UsageError;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jasmine-browser-runner",
3
- "version": "0.7.0",
3
+ "version": "1.0.0",
4
4
  "description": "Serve and run your Jasmine specs in a browser",
5
5
  "bin": "bin/jasmine-browser-runner",
6
6
  "exports": "./index.js",
@@ -9,45 +9,48 @@
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",
28
31
  "testing",
29
32
  "tdd"
30
33
  ],
31
- "engines": {
32
- "node": ">=14"
33
- },
34
34
  "author": "Slackersoft",
35
35
  "license": "MIT",
36
36
  "bugs": {
37
- "url": "https://github.com/jasmine/jasmine-browser/issues"
37
+ "url": "https://github.com/jasmine/jasmine-browser-runner/issues"
38
38
  },
39
- "homepage": "https://github.com/jasmine/jasmine-browser#readme",
39
+ "homepage": "https://github.com/jasmine/jasmine-browser-runner#readme",
40
40
  "dependencies": {
41
- "ejs": "^2.6.1",
41
+ "ejs": "^3.1.6",
42
42
  "express": "^4.16.4",
43
43
  "glob": "^7.1.7",
44
- "jasmine": "^3.8.0",
45
- "jasmine-core": "^3.6.0",
46
- "selenium-webdriver": "^4.0.0-alpha.1"
44
+ "selenium-webdriver": "^4.1.0"
45
+ },
46
+ "peerDependencies": {
47
+ "jasmine-core": "^4.0.0"
47
48
  },
48
49
  "devDependencies": {
49
- "eslint": "^6.8.0",
50
- "eslint-plugin-jasmine": "^2.10.1",
50
+ "eslint": "^7.32.0",
51
+ "eslint-plugin-jasmine": "^4.1.3",
52
+ "jasmine": "^4.0.0",
53
+ "jasmine-core": "^4.0.0",
51
54
  "prettier": "^1.17.1",
52
55
  "shelljs": "^0.8.3",
53
56
  "temp": "^0.9.4"
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
- const ConsoleReporter = require('jasmine').ConsoleReporter;
2
- const util = require('util');
3
-
4
- function DefaultReporter(options) {
5
- options = options || {};
6
- ConsoleReporter.call(this);
7
-
8
- this.setOptions({
9
- print: function() {
10
- process.stdout.write(util.format.apply(this, arguments));
11
- },
12
- randomSeedReproductionCmd: function(seed) {
13
- return 'jasmine-browser-runner runSpecs --seed=' + seed;
14
- },
15
- showColors: options.color === 'undefined' ? true : options.color,
16
- });
17
- }
18
-
19
- module.exports = DefaultReporter;
@@ -1,45 +0,0 @@
1
- /* eslint-env browser, jasmine */
2
-
3
- // This is mainly intended for use with IE. When controlling IE,
4
- // webdriver.executeScript is unreliable. It tends to fail if too
5
- // much JS is running on the page when it's called or if the return
6
- // value of the executed JS is non-tiny. Both of these are common
7
- // conditions in Jasmine. JsonDomReporter works around the problem
8
- // by exposing all data through the DOM, so that the runner can
9
- // entirely avoid calling webdriver.executeScript. The drawback is
10
- // that results can't be shown until all specs have finished.
11
- function JsonDomReporter() {
12
- var events = [];
13
-
14
- this.jasmineStarted = function(info) {
15
- events.push(['jasmineStarted', info]);
16
- };
17
-
18
- this.suiteStarted = function(info) {
19
- events.push(['suiteStarted', info]);
20
- };
21
-
22
- this.specStarted = function(info) {
23
- events.push(['specStarted', info]);
24
- };
25
-
26
- this.jasmineDone = function(info) {
27
- events.push(['jasmineDone', info]);
28
-
29
- var resultsEl = document.createElement('div');
30
- resultsEl.id = 'jasmine-jsonDomReporter-done';
31
- resultsEl.textContent = JSON.stringify(events);
32
- document.body.appendChild(resultsEl);
33
- };
34
-
35
- this.suiteDone = function(info) {
36
- events.push(['suiteDone', info]);
37
- };
38
-
39
- this.specDone = function(info) {
40
- events.push(['specDone', info]);
41
- };
42
- }
43
-
44
- window.jsonDomReporter = new JsonDomReporter();
45
- jasmine.getEnv().addReporter(window.jsonDomReporter);