@percy/config 1.0.3 → 1.0.4

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.
Files changed (2) hide show
  1. package/package.json +4 -4
  2. package/test/helpers.js +118 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@percy/config",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -16,7 +16,7 @@
16
16
  "files": [
17
17
  "dist",
18
18
  "types/index.d.ts",
19
- "tests/helpers.js"
19
+ "test/helpers.js"
20
20
  ],
21
21
  "main": "./dist/index.js",
22
22
  "types": "./types/index.d.ts",
@@ -34,7 +34,7 @@
34
34
  "test:types": "tsd"
35
35
  },
36
36
  "dependencies": {
37
- "@percy/logger": "1.0.3",
37
+ "@percy/logger": "1.0.4",
38
38
  "ajv": "^8.6.2",
39
39
  "cosmiconfig": "^7.0.0",
40
40
  "yaml": "^1.10.0"
@@ -42,5 +42,5 @@
42
42
  "devDependencies": {
43
43
  "json-schema-typed": "^7.0.3"
44
44
  },
45
- "gitHead": "a259d5cff0933711bced21a979c577e70765d318"
45
+ "gitHead": "db5b67f953f01c9a6d13b7a5a1d701ab983215cc"
46
46
  }
@@ -0,0 +1,118 @@
1
+ import fs from 'fs';
2
+ import os from 'os';
3
+ import url from 'url';
4
+ import path from 'path';
5
+ import Module from 'module';
6
+
7
+ // Reset various global @percy/config internals for testing
8
+ export async function resetPercyConfig(all) {
9
+ // aliased to src during tests
10
+ let { clearMigrations } = await import('../dist/migrate.js');
11
+ let { resetSchema } = await import('../dist/validate.js');
12
+ let { cache } = await import('../dist/load.js');
13
+ if (all) clearMigrations();
14
+ if (all) resetSchema();
15
+ cache.clear();
16
+ }
17
+
18
+ // When mocking fs, these classes should not be spied on
19
+ const FS_CLASSES = [
20
+ 'Stats', 'Dirent',
21
+ 'StatWatcher', 'FSWatcher',
22
+ 'ReadStream', 'WriteStream'
23
+ ];
24
+
25
+ // Used to bypass mocking internal package files
26
+ const INTERNAL_FILE_REG = new RegExp(
27
+ '(/|\\\\)(packages)\\1((?:(?!\\1).)+?)\\1' +
28
+ '(src|dist|test|package\\.json)(\\1|$)'
29
+ );
30
+
31
+ // Mock and spy on fs methods using an in-memory filesystem
32
+ export async function mockfs({
33
+ // set `true` to allow mocking files within `node_modules` (may cause dynamic import issues)
34
+ $modules = false,
35
+ // list of filepaths or function matchers to allow direct access to the real filesystem
36
+ $bypass = [],
37
+ // initial flat map of files and/or directories to create
38
+ ...initial
39
+ } = {}) {
40
+ let memfs = await import('memfs');
41
+ let vol = new memfs.Volume();
42
+
43
+ // automatically cleanup mock imports
44
+ global.__MOCK_IMPORTS__?.clear();
45
+
46
+ // when .js files are created, also mock the module for importing
47
+ spyOn(vol, 'writeFileSync').and.callFake((...args) => {
48
+ if (args[0].endsWith('.js')) mockFileModule(...args);
49
+ return vol.writeFileSync.and.originalFn.apply(vol, args);
50
+ });
51
+
52
+ // initial volume contents include the cwd and tmpdir
53
+ vol.fromJSON({
54
+ [process.cwd()]: null,
55
+ [os.tmpdir()]: null,
56
+ ...initial
57
+ });
58
+
59
+ let bypass = [
60
+ // bypass babel config for runtime registration
61
+ path.resolve(url.fileURLToPath(import.meta.url), '../../../../babel.config.cjs'),
62
+ // bypass descriptors that don't exist in the current volume
63
+ p => typeof p === 'number' && !vol.fds[p],
64
+ // bypass node_modules by default to avoid dynamic import issues
65
+ p => !$modules && p.includes?.('node_modules'),
66
+ // bypass internal package files to avoid dynamic import issues
67
+ p => p.match?.(INTERNAL_FILE_REG) && !vol.existsSync(p),
68
+ // additional bypass matches
69
+ ...$bypass
70
+ ];
71
+
72
+ // spies on fs methods and calls in-memory methods unless bypassed
73
+ let installFakes = (og, fake) => {
74
+ for (let k in og) {
75
+ if (k in fake && typeof og[k] === 'function' && !FS_CLASSES.includes(k)) {
76
+ spyOn(og, k).and.callFake((...args) => bypass.some(p => (
77
+ typeof p === 'function' ? p(...args) : (p === args[0])
78
+ )) ? og[k].and.originalFn(...args) : fake[k](...args));
79
+ }
80
+ }
81
+ };
82
+
83
+ // mock and install fs methods using the in-memory filesystem
84
+ let mock = memfs.createFsFromVolume(vol);
85
+ installFakes(fs.promises, mock.promises);
86
+ installFakes(fs, mock);
87
+
88
+ // allow tests access to the in-memory filesystem
89
+ fs.$bypass = bypass;
90
+ fs.$vol = vol;
91
+ return vol;
92
+ }
93
+
94
+ // Mock module loading to avoid node using internal C++ fs bindings
95
+ function mockFileModule(filepath, content = '') {
96
+ if (!jasmine.isSpy(Module._load)) {
97
+ spyOn(Module, '_load').and.callThrough();
98
+ spyOn(Module, '_resolveFilename').and.callThrough();
99
+ }
100
+
101
+ let mod = new Module();
102
+ let fp = mod.filename = path.resolve(filepath);
103
+ let any = { asymmetricMatch: () => true };
104
+
105
+ let matchFilepath = {
106
+ asymmetricMatch: f => path.resolve(f) === fp ||
107
+ fp.endsWith(path.join('node_modules', f))
108
+ };
109
+
110
+ Module._resolveFilename.withArgs(matchFilepath, any).and.returnValue(fp);
111
+ Module._load.withArgs(matchFilepath, any, any).and.callFake(() => {
112
+ mod.loaded = mod.loaded || (mod._compile(content, fp), true);
113
+ return mod.exports;
114
+ });
115
+ }
116
+
117
+ // export fs for convenience
118
+ export { fs };