Skip to content
This repository was archived by the owner on Oct 11, 2024. It is now read-only.

Commit 212fa65

Browse files
authored
feat: local mocking in node (#324)
* feat: local mocking in node You now have a global mock function available e.g const [FancySpan] = aw.mock([["**/react/src/button.js", "() => (<span>hhhhh</span>)"]], ["../src/fancy-button"]); which can be used locally in your test file * chore: cleanup lint
1 parent 740cdf4 commit 212fa65

37 files changed

Lines changed: 487 additions & 156 deletions

File tree

.eslintrc.json

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,22 @@
3939
]
4040
}
4141
],
42-
"react/prefer-stateless-function": 0
42+
"react/prefer-stateless-function": 0,
43+
"no-restricted-syntax": 0,
44+
"global-require": 0,
45+
"import/no-dynamic-require": 0,
46+
"no-unused-expressions": 0,
47+
"no-underscore-dangle": 0,
48+
"class-methods-use-this": 0,
49+
"prefer-const": ["error", { "destructuring": "all" }],
50+
"max-len": 0,
51+
"no-param-reassign": 0,
52+
"no-console": 0,
53+
"no-bitwise": 0,
54+
"radix": 0,
55+
"no-plusplus": 0,
56+
"no-new": 0,
57+
"import/no-unresolved": 0
4358
},
4459
"extends": [
4560
"airbnb"

appveyor.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ install:
1515
- npm --version
1616
# install modules
1717
- npm install
18-
- npm run bootstrap -- --no-ci
1918

2019
# Post-install test scripts.
2120
test_script:

aw.config.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
/* eslint import/no-extraneous-dependencies: 0, prefer-destructuring: 0, no-param-reassign: 0, no-unused-expressions: 0, max-len: 0 */
2-
31
const yargs = require('yargs');
42
const path = require('path');
53
const globby = require('globby');
@@ -22,7 +20,7 @@ yargs
2220
.coerce('scope', (scope) => {
2321
const scopes = new Map();
2422
globby.sync(packages.map(p => `${p}/package.json`)).forEach((p) => {
25-
const { name } = require(`./${p}`); //eslint-disable-line
23+
const { name } = require(`./${p}`);
2624
const pkgPath = path.dirname(p);
2725
scopes.set(name, pkgPath);
2826
});

commands-common/register/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@
2121
"homepage": "https://github.com/qlik-oss/after-work.js#readme",
2222
"dependencies": {
2323
"@after-work.js/transform": "^5.0.0-beta.5",
24+
"@after-work.js/utils": "^5.0.0-beta.5",
2425
"find-cache-dir": "2.0.0",
2526
"import-cwd": "2.1.0",
2627
"minimatch": "3.0.4",
2728
"pirates": "4.0.0",
29+
"require-from-string": "2.0.2",
2830
"source-map-support": "0.5.8"
2931
},
3032
"files": [

commands-common/register/src/index.js

Lines changed: 68 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1-
/* eslint global-require: 0, import/no-dynamic-require: 0 */
1+
/* global aw */
22

33
const path = require('path');
44
const fs = require('fs');
55
const { addHook } = require('pirates');
66
const sourceMapSupport = require('source-map-support');
7-
const { transformFile, getTransform } = require('@after-work.js/transform');
7+
const { transformFile, getTransform, deleteTransform } = require('@after-work.js/transform');
88
const minimatch = require('minimatch');
99
const mod = require('module');
10+
const requireFromString = require('require-from-string');
11+
const utils = require('@after-work.js/utils');
1012

11-
const originLoader = mod._load; //eslint-disable-line
13+
const originLoader = mod._load;
1214
let removeCompileHook = () => { };
1315
let removeLoadHook = () => { };
1416

@@ -39,32 +41,39 @@ function compileHook(argv, code, filename, virtualMock = false) {
3941
return transformFile(filename, newArgv, code);
4042
}
4143

42-
function compile(value, filename, options) {
43-
const Module = module.constructor;
44-
const m = new Module();
44+
function compile(value, filename, options, injectReact) {
4545
let src;
46-
let virtualMock = false;
4746
if (fs.existsSync(value)) {
4847
src = fs.readFileSync(value, 'utf8');
4948
} else {
50-
virtualMock = true;
51-
src = `export default ${value}`;
49+
src = `${injectReact ? 'import React from "react";\n' : ''}export default ${value}`;
5250
}
53-
src = compileHook(options, src, filename, virtualMock);
54-
m._compile(src, filename); //eslint-disable-line
55-
return m.exports;
51+
src = compileHook(options, src, filename, true);
52+
return requireFromString(src);
5653
}
5754

5855
function hookedLoader(options, request, parent, isMain) {
5956
let filename;
6057
try {
61-
filename = mod._resolveFilename(request, parent); // eslint-disable-line
58+
filename = mod._resolveFilename(request, parent);
6259
} catch (err) {
6360
filename = request;
6461
}
6562

66-
for (const item of options.mocks ||  []) { // eslint-disable-line
67-
let [key, value] = item; //eslint-disable-line
63+
// Explicit mocks in modules e.g aw.mock(...)
64+
for (let [key, [value, injectReact]] of aw.mocks) {
65+
if (minimatch(filename, key)) {
66+
if (value === undefined && fs.existsSync(filename)) {
67+
const src = fs.readFileSync(filename, 'utf8');
68+
value = `${JSON.stringify(src)}`;
69+
}
70+
return compile(value, filename, options, injectReact);
71+
}
72+
}
73+
74+
// Global config mocks
75+
for (const item of options.mocks || []) {
76+
let [key, value] = item;
6877
if (minimatch(filename, key)) {
6978
if (value === undefined && fs.existsSync(filename)) {
7079
const src = fs.readFileSync(filename, 'utf8');
@@ -78,9 +87,9 @@ function hookedLoader(options, request, parent, isMain) {
7887

7988
function addLoadHook(options) {
8089
const loadHook = hookedLoader.bind(null, options);
81-
mod._load = loadHook; //eslint-disable-line
90+
mod._load = loadHook;
8291
return () => {
83-
mod._load = originLoader; //eslint-disable-line
92+
mod._load = originLoader;
8493
};
8594
}
8695

@@ -106,7 +115,48 @@ function installSourceMapSupport() {
106115
sourceMapSupportInstalled = true;
107116
}
108117

109-
module.exports = function register(options = {}) {
118+
class AW {
119+
constructor(srcFiles, testFiles) {
120+
this.srcFiles = srcFiles;
121+
this.testFiles = testFiles;
122+
this.mocks = new Map();
123+
}
124+
125+
canInjectReact() {
126+
try {
127+
require.resolve('react');
128+
} catch (_) {
129+
return false;
130+
}
131+
return true;
132+
}
133+
134+
mock(mocks, reqs) {
135+
const injectReact = this.canInjectReact();
136+
mocks.forEach(([key, value]) => this.mocks.set(key, [value, injectReact]));
137+
const [filename] = utils.getCurrentFilenameStackInfo(this.testFiles);
138+
const deps = utils.getAllDependencies(this.srcFiles, filename);
139+
deps.forEach((d) => {
140+
utils.safeDeleteCache(d);
141+
});
142+
143+
const mods = reqs.map((r) => {
144+
const p = require.resolve(path.resolve(path.dirname(filename), r));
145+
const m = require(p);
146+
return m.__esModule && m.default ? m.default : m;
147+
});
148+
149+
mocks.forEach(([key]) => this.mocks.delete(key));
150+
deps.forEach((d) => {
151+
utils.safeDeleteCache(d);
152+
deleteTransform(d);
153+
});
154+
return mods;
155+
}
156+
}
157+
158+
module.exports = function register(options = {}, srcFiles, testFiles) {
159+
global.aw = new AW(srcFiles, testFiles);
110160
installSourceMapSupport();
111161
removeCompileHook();
112162
removeLoadHook();

commands-common/transform-middleware/src/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ module.exports = function transform(argv) {
88
if (ctx.url.length && ctx.url.startsWith('/')) {
99
url = ctx.url.substring(1);
1010
}
11-
const shouldInstrument = argv.coverage && argv.instrument && argv.instrument.testExclude.shouldInstrument(url); //eslint-disable-line
11+
const shouldInstrument = argv.coverage && argv.instrument && argv.instrument.testExclude.shouldInstrument(url);
1212
const shouldTransform = argv.transform && argv.transform.testExclude.shouldInstrument(url);
1313

1414
if (shouldInstrument || shouldTransform) {

commands-common/transform/src/file-cache.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,11 @@ class FileCache {
3838
instrument,
3939
transform,
4040
} = options;
41-
transformItem.hash = this.getCacheHash(filename, { ...babelOptions, ...instrument, ...transform }); // eslint-disable-line no-param-reassign
42-
if (!virtualMock) {
43-
transformItem.mtime = +fs.statSync(filename).mtime; // eslint-disable-line no-param-reassign
41+
transformItem.hash = this.getCacheHash(filename, { ...babelOptions, ...instrument, ...transform });
42+
if (virtualMock) {
43+
return;
4444
}
45+
transformItem.mtime = +fs.statSync(filename).mtime;
4546
this.transform.set(filename, transformItem);
4647
this.safeSaveCacheSync(filename);
4748
}
@@ -92,7 +93,7 @@ class FileCache {
9293
const gz = zlib.gzipSync(str);
9394
fs.writeFileSync(this.getCacheFilename(filename), gz, 'utf8');
9495
} catch (err) {
95-
console.log(err); // eslint-disable-line no-console
96+
console.log(err);
9697
}
9798
}
9899
}

commands-common/transform/src/index.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ function transformTypescript(filePath, sourceRoot, tsContent, argv) {
3030
}
3131
const transpileOpts = { fileName, compilerOptions };
3232
const res = typescript.transpileModule(tsContent, transpileOpts);
33-
tsContent = res.outputText; // eslint-disable-line no-param-reassign
33+
tsContent = res.outputText;
3434
let tsBabelOpts = {
3535
sourceMaps: 'both',
3636
};
@@ -48,21 +48,21 @@ function transformFile(filename, argv, content = null) {
4848
return cachedTransform.map;
4949
}
5050
if (!content) {
51-
filename = ensureFilePath(filename); // eslint-disable-line no-param-reassign
51+
filename = ensureFilePath(filename);
5252
const cachedTransform = fileCache.getSync(filename, argv);
5353
if (cachedTransform) {
5454
return cachedTransform.code;
5555
}
56-
content = fs.readFileSync(filename, 'utf8'); // eslint-disable-line no-param-reassign
56+
content = fs.readFileSync(filename, 'utf8');
5757
}
5858
const cachedTransform = fileCache.getSync(filename, argv);
5959
if (cachedTransform) {
6060
return cachedTransform.code;
6161
}
6262
let babelOpts = getBabelOpts(filename, argv);
6363
if (isTypescript(filename)) {
64-
const { tsContent, tsBabelOpts } = transformTypescript(filename, babelOpts.sourceRoot, content, argv); // eslint-disable-line
65-
content = tsContent; // eslint-disable-line no-param-reassign
64+
const { tsContent, tsBabelOpts } = transformTypescript(filename, babelOpts.sourceRoot, content, argv);
65+
content = tsContent;
6666
babelOpts = Object.assign({}, babelOpts, tsBabelOpts);
6767
}
6868
babelOpts.ast = false;

commands-common/utils/src/index.js

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
/* eslint max-len: 0, import/no-dynamic-require: 0, global-require: 0 */
12
const fs = require('fs');
3+
const path = require('path');
24
const readline = require('readline');
35
const importCwd = require('import-cwd');
46

@@ -48,7 +50,7 @@ const utils = {
4850
let found = importCwd.silent(name);
4951
if (!found) {
5052
try {
51-
found = require(name); // eslint-disable-line
53+
found = require(name);
5254
} catch (err) {
5355
found = null;
5456
}
@@ -57,7 +59,7 @@ const utils = {
5759
},
5860
coerceBabel(opt) {
5961
if (opt.enable && opt.core && typeof opt.core === 'string') {
60-
opt.babel = importCwd(opt.core); // eslint-disable-line no-param-reassign
62+
opt.babel = importCwd(opt.core);
6163
} else if (opt.enable && !opt.core) {
6264
let core = utils.safeGetModule('@babel/core');
6365
if (!core) {
@@ -66,16 +68,82 @@ const utils = {
6668
throw new Error('Can not get babel core module');
6769
}
6870
}
69-
opt.babel = core; // eslint-disable-line no-param-reassign
71+
opt.babel = core;
7072
}
7173
if (typeof opt.babelPluginIstanbul === 'string') {
72-
opt.babelPluginIstanbul = importCwd(opt.babelPluginIstanbul).default; // eslint-disable-line no-param-reassign, max-len
74+
opt.babelPluginIstanbul = importCwd(opt.babelPluginIstanbul).default;
7375
}
7476
if (typeof opt.typescript === 'string') {
75-
opt.typescript = importCwd.silent(opt.typescript); // eslint-disable-line no-param-reassign
77+
opt.typescript = importCwd.silent(opt.typescript);
7678
}
7779
return opt;
7880
},
81+
getCurrentFilenameStackInfo(testFiles) {
82+
// Magically figure out the current test from the stack trace (callsites not working with sourcemaps)
83+
const s = new Error().stack
84+
.split('\n')
85+
.slice(1)
86+
.map(c => c.split(/\(([^)]+)\)/)[1])
87+
.filter(c => c !== undefined)
88+
.map((c) => {
89+
const parts = c.split(':');
90+
const columnno = parts.pop();
91+
const lineno = parts.pop();
92+
const filename = path.resolve(parts.join(':'));
93+
return [filename, lineno, columnno];
94+
})
95+
.filter(([filename]) => testFiles.indexOf(filename) !== -1);
96+
if (!s.length) {
97+
throw new Error('Can not find test file');
98+
}
99+
return s.shift();
100+
},
101+
safeDeleteCache(f) {
102+
if (require.cache[f]) {
103+
delete require.cache[f];
104+
}
105+
},
106+
safeRequireCache(f) {
107+
try {
108+
require(f);
109+
return require.cache[f];
110+
} catch (_) { } //eslint-disable-line
111+
return { children: [] };
112+
},
113+
matchDependency(found, testName) {
114+
let use = found;
115+
if (found.length > 1) {
116+
const matchName = found.filter(id => path.basename(id).split('.').shift() === testName);
117+
if (matchName.length === 1) {
118+
use = matchName;
119+
} else {
120+
use = found.splice(0, 1);
121+
}
122+
}
123+
return use;
124+
},
125+
getDependencies(files, file) {
126+
const name = path.basename(file).split('.').shift();
127+
const mod = this.safeRequireCache(file);
128+
const found = mod
129+
.children
130+
.filter(m => files.indexOf(m.id) !== -1)
131+
.map(m => m.id);
132+
return this.matchDependency(found, name);
133+
},
134+
getAllDependencies(files, file) {
135+
let all = [];
136+
const deps = this.getDependencies(files, file);
137+
const walk = (currentDeps) => {
138+
all = all.concat(currentDeps);
139+
currentDeps.forEach((d) => {
140+
const childDeps = this.getDependencies(files, d);
141+
walk(childDeps, files, d);
142+
});
143+
};
144+
walk(deps, files, file);
145+
return all;
146+
},
79147
};
80148

81149
module.exports = utils;

0 commit comments

Comments
 (0)