testem 3.19.1 → 3.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/config.js CHANGED
@@ -8,14 +8,13 @@ or `testem.json` config file.
8
8
 
9
9
  */
10
10
 
11
-
12
11
  const os = require('os');
13
12
  const fs = require('fs');
14
13
  const yaml = require('js-yaml');
15
14
  const log = require('./log');
16
15
  const path = require('path');
17
- const glob = require('glob').glob;
18
16
  const url = require('url');
17
+ const { pathToFileURL } = url;
19
18
  const querystring = require('querystring');
20
19
  const { filter, reduce } = require('./utils/promises');
21
20
 
@@ -29,7 +28,7 @@ const _ = require('lodash');
29
28
 
30
29
  const knownBrowsers = require('./utils/known-browsers');
31
30
  const { convertToPosix } = require('./utils/posix');
32
- const globAsync = glob;
31
+ const expandGlobPattern = require('./utils/expand_glob_pattern');
33
32
 
34
33
  class Config {
35
34
  constructor(appMode, progOptions, config) {
@@ -39,7 +38,7 @@ class Config {
39
38
  this.fileOptions = {};
40
39
  this.config = config || {};
41
40
  this.getters = {
42
- test_page: 'getTestPage'
41
+ test_page: 'getTestPage',
43
42
  };
44
43
 
45
44
  if (appMode === 'dev') {
@@ -71,12 +70,33 @@ class Config {
71
70
 
72
71
  // Try all testem.json, testem.yml and testem.js
73
72
  // testem.json gets precedence
74
- let files = ['testem.json', '.testem.json', '.testem.yml', 'testem.yml', 'testem.js', '.testem.js', 'testem.cjs', '.testem.cjs'];
75
- return filter(files.map(this.resolveConfigPath.bind(this)), fileExists).then(matched => {
73
+ let files = [
74
+ 'testem.json',
75
+ '.testem.json',
76
+ '.testem.yml',
77
+ 'testem.yml',
78
+ 'testem.js',
79
+ '.testem.js',
80
+ 'testem.mjs',
81
+ '.testem.mjs',
82
+ 'testem.cjs',
83
+ '.testem.cjs',
84
+ ];
85
+ return filter(
86
+ files.map(this.resolveConfigPath.bind(this)),
87
+ fileExists,
88
+ ).then((matched) => {
76
89
  let configFile = matched[0];
77
90
  if (matched.length > 1) {
78
- let baseNames = matched.map(fileName => path.basename(fileName));
79
- console.warn('Found ' + matched.length + ' config files (' + baseNames + '), using ' + baseNames[0]);
91
+ let baseNames = matched.map((fileName) => path.basename(fileName));
92
+ console.warn(
93
+ 'Found ' +
94
+ matched.length +
95
+ ' config files (' +
96
+ baseNames +
97
+ '), using ' +
98
+ baseNames[0],
99
+ );
80
100
  }
81
101
  if (configFile) {
82
102
  this.readConfigFile(configFile, callback);
@@ -99,7 +119,7 @@ class Config {
99
119
 
100
120
  client() {
101
121
  return {
102
- decycle_depth: this.get('client_decycle_depth')
122
+ decycle_depth: this.get('client_decycle_depth'),
103
123
  };
104
124
  }
105
125
 
@@ -122,11 +142,12 @@ class Config {
122
142
  }
123
143
 
124
144
  readConfigFile(configFile, callback) {
125
- if (!configFile) { // allow empty configFile for programmatic setups
145
+ if (!configFile) {
146
+ // allow empty configFile for programmatic setups
126
147
  if (callback) {
127
148
  callback.call(this);
128
149
  }
129
- } else if (configFile.match(/\.c?js$/)) {
150
+ } else if (configFile.match(/\.(c?js|mjs)$/)) {
130
151
  this.readJS(configFile, callback);
131
152
  } else if (configFile.match(/\.json$/)) {
132
153
  this.readJSON(configFile, callback);
@@ -140,28 +161,70 @@ class Config {
140
161
  }
141
162
  }
142
163
 
164
+ unwrapJsExport(exported) {
165
+ if (
166
+ exported &&
167
+ typeof exported === 'object' &&
168
+ exported.__esModule === true &&
169
+ Object.prototype.hasOwnProperty.call(exported, 'default')
170
+ ) {
171
+ return exported.default;
172
+ }
173
+ return exported;
174
+ }
175
+
176
+ loadJsExportedConfig(absPath) {
177
+ const ext = path.extname(absPath);
178
+ if (ext === '.cjs') {
179
+ return Promise.resolve(this.unwrapJsExport(require(absPath)));
180
+ }
181
+ if (ext === '.mjs') {
182
+ return import(pathToFileURL(absPath).href).then((mod) =>
183
+ mod.default !== undefined ? mod.default : mod,
184
+ );
185
+ }
186
+ try {
187
+ return Promise.resolve(this.unwrapJsExport(require(absPath)));
188
+ } catch (e) {
189
+ if (e.code === 'ERR_REQUIRE_ESM') {
190
+ return import(pathToFileURL(absPath).href).then((mod) =>
191
+ mod.default !== undefined ? mod.default : mod,
192
+ );
193
+ }
194
+ throw e;
195
+ }
196
+ }
197
+
143
198
  readJS(configFile, callback) {
144
- let exportedConfig = require(this.resolveConfigPath(configFile));
145
- if (typeof exportedConfig === 'function') {
146
- Promise.resolve(exportedConfig()).then(obj => {
147
- this.fileOptions = obj;
199
+ const absPath = this.resolveConfigPath(configFile);
200
+ this.loadJsExportedConfig(absPath)
201
+ .then((exportedConfig) => {
202
+ if (typeof exportedConfig === 'function') {
203
+ return Promise.resolve(exportedConfig()).then((obj) => {
204
+ this.fileOptions = obj;
205
+ });
206
+ }
207
+ this.fileOptions = exportedConfig;
208
+ })
209
+ .catch((err) => {
210
+ log.error(err);
211
+ })
212
+ .then(() => {
148
213
  if (callback) {
149
214
  callback.call(this);
150
215
  }
151
216
  });
152
- } else {
153
- this.fileOptions = exportedConfig;
154
- if (callback) {
155
- callback.call(this);
156
- }
157
- }
158
217
  }
159
218
 
160
219
  readYAML(configFile, callback) {
161
- return fs.promises.readFile(configFile)
162
- .then(data => {
163
- this.fileOptions = yaml.load(String(data));
164
- }, () => {})
220
+ return fs.promises
221
+ .readFile(configFile)
222
+ .then(
223
+ (data) => {
224
+ this.fileOptions = yaml.load(String(data));
225
+ },
226
+ () => {},
227
+ )
165
228
  .then(() => {
166
229
  if (callback) {
167
230
  callback.call(this);
@@ -170,11 +233,15 @@ class Config {
170
233
  }
171
234
 
172
235
  readJSON(configFile, callback) {
173
- return fs.promises.readFile(configFile)
174
- .then(data => {
175
- this.fileOptions = JSON.parse(data.toString());
176
- this.progOptions.file = configFile;
177
- }, () => {})
236
+ return fs.promises
237
+ .readFile(configFile)
238
+ .then(
239
+ (data) => {
240
+ this.fileOptions = JSON.parse(data.toString());
241
+ this.progOptions.file = configFile;
242
+ },
243
+ () => {},
244
+ )
178
245
  .then(() => {
179
246
  if (callback) {
180
247
  callback.call(this);
@@ -196,11 +263,12 @@ class Config {
196
263
 
197
264
  let urlObj = url.parse(urlString);
198
265
  let outputQueryParams = querystring.parse(urlObj.query) || {};
199
- Object.keys(queryParamsObj).forEach(param => {
266
+ Object.keys(queryParamsObj).forEach((param) => {
200
267
  outputQueryParams[param] = queryParamsObj[param];
201
268
  });
202
269
  urlObj.query = outputQueryParams;
203
- urlObj.search = querystring.stringify(outputQueryParams)
270
+ urlObj.search = querystring
271
+ .stringify(outputQueryParams)
204
272
  .replace(/=&/g, '&')
205
273
  .replace(/=$/, '');
206
274
  urlObj.path = urlObj.pathname + urlObj.search;
@@ -215,20 +283,32 @@ class Config {
215
283
  testPage = [testPage];
216
284
  }
217
285
 
218
- return testPage.map(page => this.mergeUrlAndQueryParams(page, queryParams));
286
+ return testPage.map((page) =>
287
+ this.mergeUrlAndQueryParams(page, queryParams),
288
+ );
219
289
  }
220
290
 
221
291
  getConfigProperty(key) {
222
292
  if (this.config && key in this.config) {
223
293
  return this.config[key];
224
294
  }
225
- if (key in this.progOptions && typeof this.progOptions[key] !== 'undefined') {
295
+ if (
296
+ key in this.progOptions &&
297
+ typeof this.progOptions[key] !== 'undefined'
298
+ ) {
226
299
  return this.progOptions[key];
227
300
  }
228
- if (key in this.fileOptions && typeof this.fileOptions[key] !== 'undefined') {
301
+ if (
302
+ key in this.fileOptions &&
303
+ typeof this.fileOptions[key] !== 'undefined'
304
+ ) {
229
305
  return this.fileOptions[key];
230
306
  }
231
- if (this.defaultOptions && key in this.defaultOptions && typeof this.defaultOptions[key] !== 'undefined') {
307
+ if (
308
+ this.defaultOptions &&
309
+ key in this.defaultOptions &&
310
+ typeof this.defaultOptions[key] !== 'undefined'
311
+ ) {
232
312
  return this.defaultOptions[key];
233
313
  }
234
314
  if (key in this.defaults) {
@@ -264,27 +344,35 @@ class Config {
264
344
 
265
345
  getAvailableLaunchers(cb) {
266
346
  let browsers = knownBrowsers(process.platform, this);
267
- browser_launcher.getAvailableBrowsers(this, browsers, (err, availableBrowsers) => {
268
- if (err) {
269
- return cb(err);
270
- }
347
+ browser_launcher.getAvailableBrowsers(
348
+ this,
349
+ browsers,
350
+ (err, availableBrowsers) => {
351
+ if (err) {
352
+ return cb(err);
353
+ }
271
354
 
272
- let availableLaunchers = {};
273
- availableBrowsers.forEach(browser => {
274
- let newLauncher = new LauncherFactory(browser.name, browser, this);
275
- availableLaunchers[browser.name.toLowerCase()] = newLauncher;
276
- });
355
+ let availableLaunchers = {};
356
+ availableBrowsers.forEach((browser) => {
357
+ let newLauncher = new LauncherFactory(browser.name, browser, this);
358
+ availableLaunchers[browser.name.toLowerCase()] = newLauncher;
359
+ });
277
360
 
278
- // add custom launchers
279
- let customLaunchers = this.get('launchers');
280
- if (customLaunchers) {
281
- for (let name in customLaunchers) {
282
- let newLauncher = new LauncherFactory(name, customLaunchers[name], this);
283
- availableLaunchers[name.toLowerCase()] = newLauncher;
361
+ // add custom launchers
362
+ let customLaunchers = this.get('launchers');
363
+ if (customLaunchers) {
364
+ for (let name in customLaunchers) {
365
+ let newLauncher = new LauncherFactory(
366
+ name,
367
+ customLaunchers[name],
368
+ this,
369
+ );
370
+ availableLaunchers[name.toLowerCase()] = newLauncher;
371
+ }
284
372
  }
285
- }
286
- cb(null, availableLaunchers);
287
- });
373
+ cb(null, availableLaunchers);
374
+ },
375
+ );
288
376
  }
289
377
 
290
378
  getLaunchers(cb) {
@@ -310,7 +398,7 @@ class Config {
310
398
  let skip = this.get('skip');
311
399
  if (skip) {
312
400
  skip = skip.toLowerCase().split(',');
313
- launchers = launchers.filter(name => skip.indexOf(name) === -1);
401
+ launchers = launchers.filter((name) => skip.indexOf(name) === -1);
314
402
  }
315
403
  return launchers;
316
404
  }
@@ -320,7 +408,7 @@ class Config {
320
408
  let wanted = this.getWantedLauncherNames(available);
321
409
  let err = null;
322
410
 
323
- wanted.forEach(name => {
411
+ wanted.forEach((name) => {
324
412
  let launcher = available[name.toLowerCase()];
325
413
  if (!launcher) {
326
414
  if (this.appMode === 'dev' || this.get('ignore_missing_launchers')) {
@@ -328,10 +416,12 @@ class Config {
328
416
  } else {
329
417
  err = new Error(
330
418
  'Launcher ' +
331
- name +
332
- ' not found. Not installed? Available: ' +
333
- Object.keys(available).map(elm => `"${elm}"`).join(', ') +
334
- '.'
419
+ name +
420
+ ' not found. Not installed? Available: ' +
421
+ Object.keys(available)
422
+ .map((elm) => `"${elm}"`)
423
+ .join(', ') +
424
+ '.',
335
425
  );
336
426
  }
337
427
  } else {
@@ -343,38 +433,62 @@ class Config {
343
433
 
344
434
  printLauncherInfo() {
345
435
  this.getAvailableLaunchers((err, launchers) => {
346
- let launch_in_dev = (this.get('launch_in_dev') || [])
347
- .map(s => s.toLowerCase());
436
+ let launch_in_dev = (this.get('launch_in_dev') || []).map((s) =>
437
+ s.toLowerCase(),
438
+ );
348
439
  let launch_in_ci = this.get('launch_in_ci');
349
440
  if (launch_in_ci) {
350
- launch_in_ci = launch_in_ci.map(s => s.toLowerCase());
441
+ launch_in_ci = launch_in_ci.map((s) => s.toLowerCase());
351
442
  }
352
- launchers = Object.keys(launchers).map(k => launchers[k]);
443
+ launchers = Object.keys(launchers).map((k) => launchers[k]);
353
444
 
354
- const launcherColumnWidth = launchers.reduce((acc, current) => acc > current.name.length ? acc : current.name.length, 0);
445
+ const launcherColumnWidth = launchers.reduce(
446
+ (acc, current) =>
447
+ acc > current.name.length ? acc : current.name.length,
448
+ 0,
449
+ );
355
450
  const launcherTitle = 'Launcher'.padEnd(launcherColumnWidth, ' ');
356
451
  const launcherTitleDivider = ''.padEnd(launcherColumnWidth, '-');
357
452
 
358
- console.log('Have ' + launchers.length + ' launchers available; auto-launch info displayed on the right.');
453
+ console.log(
454
+ 'Have ' +
455
+ launchers.length +
456
+ ' launchers available; auto-launch info displayed on the right.',
457
+ );
359
458
  console.log(); // newline
360
459
  console.log(`${launcherTitle} Type CI Dev`);
361
460
  console.log(`${launcherTitleDivider} ------------ -- ---`);
362
- console.log(launchers.map(launcher => {
363
- let protocol = launcher.settings.protocol;
364
- let kind = protocol === 'browser' ?
365
- 'browser' : (
366
- protocol === 'tap' ?
367
- 'process(TAP)' : 'process');
368
- let dev = launch_in_dev.indexOf(launcher.name.toLowerCase()) !== -1 ?
369
- Chars.mark :
370
- ' ';
371
- let ci = !launch_in_ci || launch_in_ci.indexOf(launcher.name.toLowerCase()) !== -1 ?
372
- Chars.mark :
373
- ' ';
374
- return (pad(launcher.name, launcherColumnWidth + 2, ' ', 1) +
375
- pad(kind, 12, ' ', 1) +
376
- ' ' + ci + ' ' + dev + ' ');
377
- }).join('\n'));
461
+ console.log(
462
+ launchers
463
+ .map((launcher) => {
464
+ let protocol = launcher.settings.protocol;
465
+ let kind =
466
+ protocol === 'browser'
467
+ ? 'browser'
468
+ : protocol === 'tap'
469
+ ? 'process(TAP)'
470
+ : 'process';
471
+ let dev =
472
+ launch_in_dev.indexOf(launcher.name.toLowerCase()) !== -1
473
+ ? Chars.mark
474
+ : ' ';
475
+ let ci =
476
+ !launch_in_ci ||
477
+ launch_in_ci.indexOf(launcher.name.toLowerCase()) !== -1
478
+ ? Chars.mark
479
+ : ' ';
480
+ return (
481
+ pad(launcher.name, launcherColumnWidth + 2, ' ', 1) +
482
+ pad(kind, 12, ' ', 1) +
483
+ ' ' +
484
+ ci +
485
+ ' ' +
486
+ dev +
487
+ ' '
488
+ );
489
+ })
490
+ .join('\n'),
491
+ );
378
492
  });
379
493
  }
380
494
 
@@ -389,7 +503,7 @@ class Config {
389
503
  // Filter glob < 6 negation patterns to still support them
390
504
  // See https://github.com/isaacs/node-glob/tree/3f883c43#comments-and-negation
391
505
  let positiveWants = [];
392
- want.forEach(patternEntry => {
506
+ want.forEach((patternEntry) => {
393
507
  let pattern = isa(patternEntry, String) ? patternEntry : patternEntry.src;
394
508
  if (pattern.indexOf('!') === 0) {
395
509
  return dontWant.push(pattern.substring(1));
@@ -398,33 +512,51 @@ class Config {
398
512
  positiveWants.push(patternEntry);
399
513
  });
400
514
 
401
- dontWant = dontWant.map(p => p ? this.resolvePath(p) : p);
402
- return reduce(positiveWants, (allThatIWant, patternEntry) => {
403
- let pattern = isa(patternEntry, String) ? patternEntry : patternEntry.src;
404
- let attrs = patternEntry.attrs || [];
405
- let patternUrl = url.parse(pattern);
406
-
407
- if (patternUrl.protocol === 'file:') {
408
- pattern = patternUrl.hostname + patternUrl.path;
409
- } else if (patternUrl.protocol) {
410
- return allThatIWant.concat({ src: pattern, attrs: attrs });
411
- }
515
+ dontWant = dontWant.map((p) => (p ? this.resolvePath(p) : p));
516
+ return reduce(
517
+ positiveWants,
518
+ (allThatIWant, patternEntry) => {
519
+ let pattern = isa(patternEntry, String)
520
+ ? patternEntry
521
+ : patternEntry.src;
522
+ let attrs = patternEntry.attrs || [];
523
+ let patternUrl = url.parse(pattern);
524
+
525
+ if (patternUrl.protocol === 'file:') {
526
+ pattern = patternUrl.hostname + patternUrl.path;
527
+ } else if (patternUrl.protocol) {
528
+ return allThatIWant.concat({ src: pattern, attrs: attrs });
529
+ }
412
530
 
413
- return globAsync(convertToPosix(this.resolvePath(pattern)), { ignore: dontWant.map(convertToPosix) })
414
- .then(files => files.sort())
415
- .then(files => allThatIWant.concat(files.map(f => {
416
- f = this.reverseResolvePath(f);
417
- return { src: f, attrs: attrs };
418
- })));
419
- }, [])
420
- .then(result => _.uniqBy(result, 'src'))
421
- .then(result => {
422
- if (callback) { callback(null, result); }
423
- return result;
424
- }, err => {
425
- if (callback) { callback(err); }
426
- throw err;
427
- });
531
+ return expandGlobPattern(
532
+ convertToPosix(this.resolvePath(pattern)),
533
+ dontWant.map(convertToPosix),
534
+ ).then((files) =>
535
+ allThatIWant.concat(
536
+ files.map((f) => {
537
+ f = this.reverseResolvePath(f);
538
+ return { src: f, attrs: attrs };
539
+ }),
540
+ ),
541
+ );
542
+ },
543
+ [],
544
+ )
545
+ .then((result) => _.uniqBy(result, 'src'))
546
+ .then(
547
+ (result) => {
548
+ if (callback) {
549
+ callback(null, result);
550
+ }
551
+ return result;
552
+ },
553
+ (err) => {
554
+ if (callback) {
555
+ callback(err);
556
+ }
557
+ throw err;
558
+ },
559
+ );
428
560
  }
429
561
 
430
562
  getSrcFiles(callback) {
@@ -440,10 +572,10 @@ class Config {
440
572
  return this.getFileSet(want, dontWant, callback);
441
573
  }
442
574
 
443
-
444
575
  getServeFiles(callback) {
445
576
  let want = this.get('serve_files') || this.get('src_files') || '*.js';
446
- let dontWant = this.get('serve_files_ignore') || this.get('src_files_ignore') || '';
577
+ let dontWant =
578
+ this.get('serve_files_ignore') || this.get('src_files_ignore') || '';
447
579
  return this.getFileSet(want, dontWant, callback);
448
580
  }
449
581
 
@@ -471,13 +603,19 @@ class Config {
471
603
  return;
472
604
  }
473
605
  if (o.options) {
474
- o.options.forEach(o => {
606
+ o.options.forEach((o) => {
475
607
  options.push(o.name());
476
608
  });
477
609
  }
478
610
  getOptions(o.parent);
479
611
  }
480
- getOptions(this.progOptions);
612
+ const progOpts = this.progOptions;
613
+ // In subcommand modes, progOptions is a plain object from optsWithGlobals() with
614
+ // a _commander reference pointing to the Command instance for option enumeration.
615
+ // In dev mode (no subcommand), progOptions is the Commander program object directly.
616
+ getOptions(
617
+ progOpts && progOpts._commander ? progOpts._commander : progOpts,
618
+ );
481
619
  return options;
482
620
  }
483
621
 
@@ -497,16 +635,16 @@ class Config {
497
635
  ret[key] = this.config[key];
498
636
  }
499
637
 
500
- const replaceSlashes = f => ({
638
+ const replaceSlashes = (f) => ({
501
639
  src: f.src.replace(/\\/g, '/'),
502
- attrs: f.attrs
640
+ attrs: f.attrs,
503
641
  });
504
642
 
505
643
  try {
506
644
  const [serveFiles, cssFiles, footerScripts] = await Promise.all([
507
645
  this.getServeFiles(),
508
646
  this.getCSSFiles(),
509
- this.getFooterScripts()
647
+ this.getFooterScripts(),
510
648
  ]);
511
649
 
512
650
  ret.serve_files = serveFiles.map(replaceSlashes);
@@ -543,10 +681,13 @@ Config.prototype.defaults = {
543
681
  client_decycle_depth: 5,
544
682
  socket_heartbeat_timeout() {
545
683
  let browserDisconnectTimeout = this.get('browser_disconnect_timeout');
546
- let defaultBrowserDisconnectTimeout = this.defaults.browser_disconnect_timeout;
684
+ let defaultBrowserDisconnectTimeout =
685
+ this.defaults.browser_disconnect_timeout;
547
686
 
548
- return (browserDisconnectTimeout !== defaultBrowserDisconnectTimeout) ? browserDisconnectTimeout : 5;
549
- }
687
+ return browserDisconnectTimeout !== defaultBrowserDisconnectTimeout
688
+ ? browserDisconnectTimeout
689
+ : 5;
690
+ },
550
691
  };
551
692
 
552
693
  module.exports = Config;