testem 3.19.1 → 3.20.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.
@@ -0,0 +1,119 @@
1
+ const url = require('url');
2
+
3
+ const expandGlobPattern = require('./expand_glob_pattern');
4
+ const { convertToPosix } = require('./posix');
5
+ const { pathMatchesAny } = require('./path_pattern_match');
6
+
7
+ /**
8
+ * Normalize config `src_files`-style values to a list of glob strings.
9
+ * @param {string|string[]|Array<{src: string}>|undefined|null} value
10
+ * @returns {string[]}
11
+ */
12
+ function flattenPatternList(value) {
13
+ if (value === undefined || value === null) {
14
+ return [];
15
+ }
16
+ if (Array.isArray(value)) {
17
+ return value.map(function(entry) {
18
+ if (typeof entry === 'string') {
19
+ return entry;
20
+ }
21
+ if (entry && typeof entry.src === 'string') {
22
+ return entry.src;
23
+ }
24
+ return String(entry);
25
+ });
26
+ }
27
+ return [String(value)];
28
+ }
29
+
30
+ /**
31
+ * Build the include / ignore pattern lists used by `FileWatcher` with the watch engine
32
+ * (policy filtering; the engine watches cwd and applies these patterns). Single place for glob +
33
+ * ignore semantics.
34
+ *
35
+ * @param {{ get: function(string): *, isCwdMode: function(): boolean }} config
36
+ * @returns {{ includePatterns: string[], ignorePatterns: string[] }}
37
+ */
38
+ function buildWatchGlobPolicy(config) {
39
+ const includePatterns = [];
40
+
41
+ const confFile = config.get('file');
42
+ if (confFile) {
43
+ includePatterns.push(String(confFile));
44
+ }
45
+ if (config.isCwdMode()) {
46
+ includePatterns.push('*.js');
47
+ }
48
+ const watchFiles = config.get('watch_files');
49
+ if (watchFiles) {
50
+ includePatterns.push(...flattenPatternList(watchFiles));
51
+ }
52
+ const srcFiles = config.get('src_files');
53
+ const srcList = srcFiles !== undefined && srcFiles !== null ? srcFiles : '*.js';
54
+ includePatterns.push(...flattenPatternList(srcList));
55
+
56
+ const ignoreRaw = config.get('src_files_ignore');
57
+ const ignorePatterns = ignoreRaw ? flattenPatternList(ignoreRaw) : [];
58
+
59
+ return { includePatterns, ignorePatterns };
60
+ }
61
+
62
+ /**
63
+ * Whether a path would be treated as a watched file (matches an include pattern and
64
+ * no ignore pattern). Paths are normalized with {@link convertToPosix} for matching
65
+ * on Windows vs POSIX.
66
+ *
67
+ * @param {string} filePath - Absolute or relative path as emitted by the filesystem / watcher.
68
+ * @param {{ includePatterns: string[], ignorePatterns: string[] }} policy
69
+ * @param {object} [matchOptions] - forwarded to minimatch (via path_pattern_match).
70
+ * @returns {boolean}
71
+ */
72
+ function pathMatchesWatchTarget(filePath, policy, matchOptions) {
73
+ const posix = convertToPosix(filePath);
74
+ if (pathMatchesAny(posix, policy.ignorePatterns, matchOptions)) {
75
+ return false;
76
+ }
77
+ return pathMatchesAny(posix, policy.includePatterns, matchOptions);
78
+ }
79
+
80
+ /**
81
+ * Expand all on-disk files matching the watch policy (same idea as `getFileSet` for
82
+ * `src_files`, but driven by the watch policy). Skips `http:` / `https:` URLs; keeps
83
+ * them as literal entries. Uses {@link expandGlobPattern} with `config.resolvePath`.
84
+ *
85
+ * @param {{ get: function(string): *, cwd: function(): string, resolvePath: function(string): string }} config
86
+ * @returns {Promise<string[]>} Sorted unique absolute paths (and any literal URLs).
87
+ */
88
+ async function expandWatchableFilePaths(config) {
89
+ const policy = buildWatchGlobPolicy(config);
90
+ const ignorePosix = policy.ignorePatterns.map(function(p) {
91
+ return convertToPosix(config.resolvePath(p));
92
+ });
93
+ const out = [];
94
+
95
+ for (let i = 0; i < policy.includePatterns.length; i++) {
96
+ const pattern = policy.includePatterns[i];
97
+ const parsed = url.parse(pattern);
98
+ if (parsed.protocol && parsed.protocol !== 'file:' && parsed.protocol !== '') {
99
+ out.push(pattern);
100
+ continue;
101
+ }
102
+ let fsPattern = pattern;
103
+ if (parsed.protocol === 'file:') {
104
+ fsPattern = parsed.hostname + parsed.path;
105
+ }
106
+ const resolved = convertToPosix(config.resolvePath(fsPattern));
107
+ const files = await expandGlobPattern(resolved, ignorePosix);
108
+ out.push(...files);
109
+ }
110
+
111
+ return [...new Set(out)].sort();
112
+ }
113
+
114
+ module.exports = {
115
+ buildWatchGlobPolicy,
116
+ flattenPatternList,
117
+ pathMatchesWatchTarget,
118
+ expandWatchableFilePaths,
119
+ };
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Detects EMFILE errors from `fs.watch` / watcher backends (e.g. chokidar `error`).
3
+ * Fireworm uses a dedicated `emfile` event; other engines surface this via `error`.
4
+ *
5
+ * @param {unknown} err
6
+ * @returns {boolean}
7
+ */
8
+ function isEmfileError(err) {
9
+ return (
10
+ err !== null &&
11
+ typeof err === 'object' &&
12
+ /** @type {{ code?: string }} */ (err).code === 'EMFILE'
13
+ );
14
+ }
15
+
16
+ module.exports = isEmfileError;
@@ -1,5 +1,3 @@
1
-
2
-
3
1
  const browserArgs = require('./browser-args');
4
2
  const path = require('path');
5
3
  const fs = require('fs');
@@ -8,17 +6,20 @@ const os = require('os');
8
6
 
9
7
  function chromeWinPaths(homeDir, name) {
10
8
  return [
11
- homeDir + '\\Local Settings\\Application Data\\Google\\' + name + '\\Application\\chrome.exe',
9
+ homeDir +
10
+ '\\Local Settings\\Application Data\\Google\\' +
11
+ name +
12
+ '\\Application\\chrome.exe',
12
13
  homeDir + '\\AppData\\Local\\Google\\' + name + '\\Application\\chrome.exe',
13
14
  'C:\\Program Files\\Google\\' + name + '\\Application\\Chrome.exe',
14
- 'C:\\Program Files (x86)\\Google\\' + name + '\\Application\\Chrome.exe'
15
+ 'C:\\Program Files (x86)\\Google\\' + name + '\\Application\\Chrome.exe',
15
16
  ];
16
17
  }
17
18
 
18
19
  function chromeOSXPaths(name) {
19
20
  return [
20
21
  process.env.HOME + '/Applications/' + name + '.app/Contents/MacOS/' + name,
21
- '/Applications/' + name + '.app/Contents/MacOS/' + name
22
+ '/Applications/' + name + '.app/Contents/MacOS/' + name,
22
23
  ];
23
24
  }
24
25
 
@@ -31,7 +32,9 @@ function chromeArgs(browserTmpDir, url) {
31
32
  '--test-type',
32
33
  '--disable-renderer-backgrounding',
33
34
  '--disable-background-timer-throttling',
34
- url
35
+ '--disable-infobars',
36
+ '--disable-session-crashed-bubble',
37
+ url,
35
38
  ];
36
39
  }
37
40
 
@@ -42,15 +45,24 @@ function headlessChromeArgs(browserTmpDir, url) {
42
45
  '--disable-software-rasterizer',
43
46
  '--mute-audio',
44
47
  '--remote-debugging-port=0',
45
- '--window-size=1440,900'].concat(chromeArgs(browserTmpDir, url));
48
+ '--window-size=1440,900',
49
+ ].concat(chromeArgs(browserTmpDir, url));
46
50
  }
47
51
 
48
52
  function braveWinPaths(homeDir, name) {
49
53
  return [
50
- homeDir + '\\Local Settings\\Application Data\\BraveSoftware\\' + name + '\\Application\\brave.exe',
51
- homeDir + '\\AppData\\Local\\BraveSoftware\\' + name + '\\Application\\brave.exe',
54
+ homeDir +
55
+ '\\Local Settings\\Application Data\\BraveSoftware\\' +
56
+ name +
57
+ '\\Application\\brave.exe',
58
+ homeDir +
59
+ '\\AppData\\Local\\BraveSoftware\\' +
60
+ name +
61
+ '\\Application\\brave.exe',
52
62
  'C:\\Program Files\\BraveSoftware\\' + name + '\\Application\\brave.exe',
53
- 'C:\\Program Files (x86)\\BraveSoftware\\' + name + '\\Application\\brave.exe'
63
+ 'C:\\Program Files (x86)\\BraveSoftware\\' +
64
+ name +
65
+ '\\Application\\brave.exe',
54
66
  ];
55
67
  }
56
68
 
@@ -68,15 +80,17 @@ function headlessBraveArgs(browserTmpDir, url) {
68
80
  '--disable-gpu',
69
81
  '--mute-audio',
70
82
  '--remote-debugging-port=0',
71
- '--window-size=1440,900'].concat(braveArgs(browserTmpDir, url));
83
+ '--window-size=1440,900',
84
+ ].concat(braveArgs(browserTmpDir, url));
72
85
  }
73
86
 
74
87
  function edgePaths() {
75
88
  return [
76
89
  'C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe',
77
90
  'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe',
78
- process.env.HOME + '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
79
- '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge'
91
+ process.env.HOME +
92
+ '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
93
+ '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
80
94
  ];
81
95
  }
82
96
 
@@ -84,9 +98,11 @@ function firefoxPaths() {
84
98
  return [
85
99
  'C:\\Program Files\\Mozilla Firefox\\firefox.exe',
86
100
  'C:\\Program Files (x86)\\Mozilla Firefox\\firefox.exe',
87
- 'C:\\Users\\' + process.env.USERNAME + '\\AppData\\Local\\Mozilla Firefox\\firefox.exe',
101
+ 'C:\\Users\\' +
102
+ process.env.USERNAME +
103
+ '\\AppData\\Local\\Mozilla Firefox\\firefox.exe',
88
104
  process.env.HOME + '/Applications/Firefox.app/Contents/MacOS/firefox',
89
- '/Applications/Firefox.app/Contents/MacOS/firefox'
105
+ '/Applications/Firefox.app/Contents/MacOS/firefox',
90
106
  ];
91
107
  }
92
108
 
@@ -95,13 +111,14 @@ function firefoxSetup(browserTmpDir, config, done) {
95
111
  let perfJS = path.join(browserTmpDir, 'user.js');
96
112
 
97
113
  if (config.get('firefox_user_js')) {
98
- return fs.promises.readFile(config.get('firefox_user_js'))
99
- .then(content => fs.promises.writeFile(perfJS, content))
114
+ return fs.promises
115
+ .readFile(config.get('firefox_user_js'))
116
+ .then((content) => fs.promises.writeFile(perfJS, content))
100
117
  .then(() => done(null), done);
101
118
  }
102
119
 
103
- // using user.js to suppress the check default browser popup,
104
- // the welcome start page, and first-run helper installation prompts
120
+ // user.js: legacy Testem defaults plus prefs aligned with common Firefox --pref automation
121
+ // sets (welcome, onboarding, messaging, telemetry) and fewer prompts / background noise.
105
122
  let prefs = [
106
123
  'user_pref("browser.shell.checkDefaultBrowser", false);',
107
124
  'user_pref("browser.cache.disk.smart_size.first_run", false);',
@@ -110,9 +127,34 @@ function firefoxSetup(browserTmpDir, config, done) {
110
127
  'user_pref("app.update.auto", false);',
111
128
  'user_pref("app.update.enabled", false);',
112
129
  'user_pref("browser.EULA.override", true);',
113
- 'user_pref("toolkit.telemetry.reportingpolicy.firstRun", false);'
130
+ 'user_pref("toolkit.telemetry.reportingpolicy.firstRun", false);',
131
+ 'user_pref("browser.aboutwelcome.enabled", false);',
132
+ 'user_pref("browser.startup.homepage", "about:blank");',
133
+ 'user_pref("browser.startup.page", 0);',
134
+ 'user_pref("browser.startup.firstrunSkipsHomepage", true);',
135
+ 'user_pref("browser.startup.homepage_override.mstone", "ignore");',
136
+ 'user_pref("browser.startup.homepage_welcome_url", "");',
137
+ 'user_pref("browser.startup.homepage_welcome_url.additional", "");',
138
+ 'user_pref("browser.startup.cohort", "ignore");',
139
+ 'user_pref("browser.messaging-system.prompts.enabled", false);',
140
+ 'user_pref("browser.onboarding.enabled", false);',
141
+ 'user_pref("browser.tour.enabled", false);',
142
+ 'user_pref("browser.startup.upgradeDialog.enabled", false);',
143
+ 'user_pref("browser.uiCustomization.skipDefaultState", true);',
144
+ 'user_pref("toolkit.telemetry.enabled", false);',
145
+ 'user_pref("toolkit.telemetry.unified", false);',
146
+ 'user_pref("browser.ping-centre.telemetry", false);',
147
+ 'user_pref("browser.sessionstore.resume_from_crash", false);',
148
+ 'user_pref("browser.tabs.warnOnClose", false);',
149
+ 'user_pref("browser.tabs.warnOnCloseOtherTabs", false);',
150
+ 'user_pref("browser.tabs.warnOnOpen", false);',
151
+ 'user_pref("browser.download.manager.showWhenStarting", false);',
152
+ 'user_pref("extensions.update.enabled", false);',
153
+ 'user_pref("dom.webnotifications.enabled", false);',
154
+ 'user_pref("gfx.direct2d.disabled", true);',
114
155
  ];
115
- fs.promises.writeFile(perfJS, prefs.join(os.EOL))
156
+ fs.promises
157
+ .writeFile(perfJS, prefs.join(os.EOL))
116
158
  .then(() => done(null), done);
117
159
  }
118
160
 
@@ -138,7 +180,7 @@ module.exports = function knownBrowsers(platform, config) {
138
180
  },
139
181
  setup(config, done) {
140
182
  firefoxSetup(this.browserTmpDir(), config, done);
141
- }
183
+ },
142
184
  },
143
185
  {
144
186
  name: 'Headless Firefox',
@@ -150,156 +192,156 @@ module.exports = function knownBrowsers(platform, config) {
150
192
  },
151
193
  setup(config, done) {
152
194
  firefoxSetup(this.browserTmpDir(), config, done);
153
- }
195
+ },
154
196
  },
155
197
  {
156
198
  name: 'Brave',
157
- possiblePath: braveWinPaths(userHomeDir, 'Brave-Browser').concat(braveOSXPaths('Brave Browser')),
158
- possibleExe: [
159
- 'brave'
160
- ],
199
+ possiblePath: braveWinPaths(userHomeDir, 'Brave-Browser').concat(
200
+ braveOSXPaths('Brave Browser'),
201
+ ),
202
+ possibleExe: ['brave'],
161
203
  args(config, url) {
162
204
  return braveArgs(this.browserTmpDir(), url);
163
- }
205
+ },
164
206
  },
165
207
  {
166
208
  name: 'Headless Brave',
167
- possiblePath: braveWinPaths(userHomeDir, 'Brave-Browser').concat(braveOSXPaths('Brave Browser')),
168
- possibleExe: [
169
- 'brave'
170
- ],
209
+ possiblePath: braveWinPaths(userHomeDir, 'Brave-Browser').concat(
210
+ braveOSXPaths('Brave Browser'),
211
+ ),
212
+ possibleExe: ['brave'],
171
213
  args(config, url) {
172
214
  return headlessBraveArgs(this.browserTmpDir(), url);
173
- }
215
+ },
174
216
  },
175
217
  {
176
218
  name: 'Chrome',
177
- possiblePath: chromeWinPaths(userHomeDir, 'Chrome').concat(chromeOSXPaths('Google Chrome')),
178
- possibleExe: [
179
- 'google-chrome',
180
- 'google-chrome-stable',
181
- 'chrome'
182
- ],
219
+ possiblePath: chromeWinPaths(userHomeDir, 'Chrome').concat(
220
+ chromeOSXPaths('Google Chrome'),
221
+ ),
222
+ possibleExe: ['google-chrome', 'google-chrome-stable', 'chrome'],
183
223
  args(config, url) {
184
224
  return chromeArgs(this.browserTmpDir(), url);
185
- }
225
+ },
186
226
  },
187
227
  {
188
228
  name: 'Headless Chrome',
189
- possiblePath: chromeWinPaths(userHomeDir, 'Chrome').concat(chromeOSXPaths('Google Chrome')),
190
- possibleExe: [
191
- 'google-chrome',
192
- 'google-chrome-stable',
193
- 'chrome'
194
- ],
229
+ possiblePath: chromeWinPaths(userHomeDir, 'Chrome').concat(
230
+ chromeOSXPaths('Google Chrome'),
231
+ ),
232
+ possibleExe: ['google-chrome', 'google-chrome-stable', 'chrome'],
195
233
  args(config, url) {
196
234
  return headlessChromeArgs(this.browserTmpDir(), url);
197
- }
235
+ },
198
236
  },
199
237
 
200
238
  // On Windows, Chrome Stable/Beta/Dev are installed on the same path
201
239
  {
202
240
  name: 'Chrome Beta',
203
241
  possiblePath: chromeOSXPaths('Google Chrome Beta'),
204
- possibleExe: [
205
- 'google-chrome-beta'
206
- ],
242
+ possibleExe: ['google-chrome-beta'],
207
243
  args(config, url) {
208
244
  return chromeArgs(this.browserTmpDir(), url);
209
- }
245
+ },
210
246
  },
211
247
  {
212
248
  name: 'Headless Chrome Beta',
213
249
  possiblePath: chromeOSXPaths('Google Chrome Beta'),
214
- possibleExe: [
215
- 'google-chrome-beta'
216
- ],
250
+ possibleExe: ['google-chrome-beta'],
217
251
  args(config, url) {
218
252
  return headlessChromeArgs(this.browserTmpDir(), url);
219
- }
253
+ },
220
254
  },
221
255
  {
222
256
  name: 'Chrome Dev',
223
257
  possiblePath: chromeOSXPaths('Google Chrome Dev'),
224
- possibleExe: [
225
- 'google-chrome-unstable'
226
- ],
258
+ possibleExe: ['google-chrome-unstable'],
227
259
  args(config, url) {
228
260
  return chromeArgs(this.browserTmpDir(), url);
229
- }
261
+ },
230
262
  },
231
263
 
232
264
  // Chrome Canary is not available on Linux
233
265
  {
234
266
  name: 'Chrome Canary',
235
- possiblePath: chromeWinPaths(userHomeDir, 'Chrome SxS').concat(chromeOSXPaths('Google Chrome Canary')),
267
+ possiblePath: chromeWinPaths(userHomeDir, 'Chrome SxS').concat(
268
+ chromeOSXPaths('Google Chrome Canary'),
269
+ ),
236
270
  args(config, url) {
237
271
  return chromeArgs(this.browserTmpDir(), url);
238
- }
272
+ },
239
273
  },
240
274
  {
241
275
  name: 'Chromium',
242
- possiblePath: chromeWinPaths(userHomeDir, 'Chromium').concat(chromeOSXPaths('Chromium')),
243
- possibleExe: [
244
- 'chromium-browser',
245
- 'chromium'
246
- ],
276
+ possiblePath: chromeWinPaths(userHomeDir, 'Chromium').concat(
277
+ chromeOSXPaths('Chromium'),
278
+ ),
279
+ possibleExe: ['chromium-browser', 'chromium'],
247
280
  args(config, url) {
248
281
  return chromeArgs(this.browserTmpDir(), url);
249
- }
282
+ },
250
283
  },
251
284
  {
252
285
  name: 'Edge',
253
286
  possiblePath: edgePaths(),
254
- possibleExe: [
255
- 'microsoft-edge'
256
- ],
287
+ possibleExe: ['microsoft-edge'],
257
288
  args(config, url) {
258
289
  return chromeArgs(this.browserTmpDir(), url);
259
- }
290
+ },
260
291
  },
261
292
  {
262
293
  name: 'Headless Edge',
263
294
  possiblePath: edgePaths(),
264
- possibleExe: [
265
- 'microsoft-edge'
266
- ],
295
+ possibleExe: ['microsoft-edge'],
267
296
  args(config, url) {
268
297
  return headlessChromeArgs(this.browserTmpDir(), url);
269
- }
270
- },
271
- {
272
- name: 'Safari',
273
- possiblePath: [
274
- 'C:\\Program Files\\Safari\\safari.exe',
275
- 'C:\\Program Files (x86)\\Safari\\safari.exe',
276
- process.env.HOME + '/Applications/Safari.app/Contents/MacOS/Safari',
277
- '/Applications/Safari.app/Contents/MacOS/Safari'
278
- ],
279
- setup(config, done) {
280
- let url = this.getUrl();
281
- fs.writeFile(
282
- path.join(this.browserTmpDir(), 'start.html'),
283
- '<script>window.location = \'' + url + '\'</script>',
284
- done
285
- );
286
298
  },
287
- args() {
288
- return [path.join(this.browserTmpDir(), 'start.html')];
289
- }
290
299
  },
300
+ platform === 'darwin'
301
+ ? {
302
+ // Use Launch Services so Safari opens the http(s) URL directly. Passing a local
303
+ // start.html file makes Safari 13+ prompt for confirmation (#1387) and breaks CI.
304
+ name: 'Safari',
305
+ possiblePath: '/usr/bin/open',
306
+ // `open` exits right after handing off to Safari; Testem must not treat that as the
307
+ // browser process dying (browser_test_runner ignoreProcessExit).
308
+ ignoreProcessExit: true,
309
+ args(config, url) {
310
+ return ['-a', 'Safari', url];
311
+ },
312
+ }
313
+ : {
314
+ name: 'Safari',
315
+ possiblePath: [
316
+ 'C:\\Program Files\\Safari\\safari.exe',
317
+ 'C:\\Program Files (x86)\\Safari\\safari.exe',
318
+ process.env.HOME + '/Applications/Safari.app/Contents/MacOS/Safari',
319
+ '/Applications/Safari.app/Contents/MacOS/Safari',
320
+ ],
321
+ setup(config, done) {
322
+ let url = this.getUrl();
323
+ fs.writeFile(
324
+ path.join(this.browserTmpDir(), 'start.html'),
325
+ "<script>window.location = '" + url + "'</script>",
326
+ done,
327
+ );
328
+ },
329
+ args() {
330
+ return [path.join(this.browserTmpDir(), 'start.html')];
331
+ },
332
+ },
291
333
  {
292
334
  name: 'Opera',
293
335
  possiblePath: [
294
336
  'C:\\Program Files\\Opera\\opera.exe',
295
337
  'C:\\Program Files (x86)\\Opera\\opera.exe',
296
338
  process.env.HOME + '/Applications/Opera.app/Contents/MacOS/Opera',
297
- '/Applications/Opera.app/Contents/MacOS/Opera'
339
+ '/Applications/Opera.app/Contents/MacOS/Opera',
298
340
  ],
299
341
  args(config, url) {
300
342
  let operaDataDir = this.browserTmpDir();
301
343
  return ['--user-data-dir=' + operaDataDir, '-pd', operaDataDir, url];
302
- }
344
+ },
303
345
  },
304
346
  {
305
347
  name: 'PhantomJS',
@@ -320,43 +362,38 @@ module.exports = function knownBrowsers(platform, config) {
320
362
  options = phantom_args.concat(options);
321
363
  }
322
364
  return options;
323
- }
324
- }
365
+ },
366
+ },
325
367
  ];
326
368
 
327
369
  if (isWin(platform)) {
328
370
  browsers.push({
329
371
  name: 'IE',
330
- possiblePath: 'C:\\Program Files\\Internet Explorer\\iexplore.exe'
372
+ possiblePath: 'C:\\Program Files\\Internet Explorer\\iexplore.exe',
331
373
  });
332
- } else {
374
+ } else if (platform === 'darwin') {
333
375
  browsers.push({
376
+ // Same Launch Services approach as Safari on macOS (#1387).
334
377
  name: 'Safari Technology Preview',
335
- possiblePath: [
336
- process.env.HOME + '/Applications/Safari Technology Preview.app/Contents/MacOS/Safari Technology Preview',
337
- '/Applications/Safari Technology Preview.app/Contents/MacOS/Safari Technology Preview'
338
- ],
339
- setup(config, done) {
340
- let url = this.getUrl();
341
- fs.writeFile(
342
- path.join(this.browserTmpDir(), 'start.html'),
343
- '<script>window.location = \'' + url + '\'</script>',
344
- done
345
- );
378
+ possiblePath: '/usr/bin/open',
379
+ ignoreProcessExit: true,
380
+ args(config, url) {
381
+ return ['-a', 'Safari Technology Preview', url];
346
382
  },
347
- args() {
348
- return [path.join(this.browserTmpDir(), 'start.html')];
349
- }
350
383
  });
351
384
  }
352
385
 
353
386
  // Add user-defined configs to browser objects
354
387
  let browserPaths = config.get('browser_paths'); // Add config defined 'path' for binary
355
- let browserExes = config.get('browser_exes'); // Add config defined 'exe' for binary
388
+ let browserExes = config.get('browser_exes'); // Add config defined 'exe' for binary
356
389
  if (browserPaths || browserExes) {
357
- browsers.forEach(browserObject => {
358
- let browserPath = browserPaths ? browserPaths[browserObject.name] : undefined;
359
- let browserExe = browserExes ? browserExes[browserObject.name] : undefined;
390
+ browsers.forEach((browserObject) => {
391
+ let browserPath = browserPaths
392
+ ? browserPaths[browserObject.name]
393
+ : undefined;
394
+ let browserExe = browserExes
395
+ ? browserExes[browserObject.name]
396
+ : undefined;
360
397
  if (browserPath) {
361
398
  browserObject.possiblePath = browserPath;
362
399
  }
@@ -0,0 +1,39 @@
1
+ const { minimatch } = require('minimatch');
2
+
3
+ /**
4
+ * Match a path against a single glob pattern. Thin wrapper so the matcher
5
+ * implementation (minimatch today, possibly picomatch later) lives in one place.
6
+ *
7
+ * @param {string} filePath - Path to test (forward slashes recommended; callers often use `convertToPosix`).
8
+ * @param {string} pattern - A single glob pattern.
9
+ * @param {import('minimatch').MinimatchOptions} [options] - Passed through to the matcher.
10
+ * @returns {boolean}
11
+ */
12
+ function pathMatchesPattern(filePath, pattern, options) {
13
+ if (pattern === null || pattern === undefined || pattern === '') {
14
+ return false;
15
+ }
16
+ return minimatch(filePath, pattern, options);
17
+ }
18
+
19
+ /**
20
+ * True if `filePath` matches **any** non-empty pattern in the list.
21
+ *
22
+ * @param {string} filePath
23
+ * @param {string[]} patterns
24
+ * @param {import('minimatch').MinimatchOptions} [options]
25
+ * @returns {boolean}
26
+ */
27
+ function pathMatchesAny(filePath, patterns, options) {
28
+ if (!patterns || patterns.length === 0) {
29
+ return false;
30
+ }
31
+ return patterns.some(function(p) {
32
+ return p && pathMatchesPattern(filePath, p, options);
33
+ });
34
+ }
35
+
36
+ module.exports = {
37
+ pathMatchesPattern,
38
+ pathMatchesAny,
39
+ };