Skip to content

Commit 8dcafca

Browse files
committed
feat(tests): more test tools + adds test related to debuggin issues
1 parent f170285 commit 8dcafca

10 files changed

Lines changed: 375 additions & 75 deletions

File tree

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// this is a template!
2+
3+
const fs = require('fs');
4+
const path = require('path');
5+
const root = __dirname;
6+
const writeProcessIoTo = {{writeProcessIoTo}};
7+
8+
exports.afterProcess = function (args, result) {
9+
// const source = args[0];
10+
const filePath = args[1];
11+
const relPath = path.relative(root, filePath);
12+
if (writeProcessIoTo && filePath.startsWith(`${root}${path.sep}`)) {
13+
const dest = `${path.join(writeProcessIoTo, relPath)}.json`;
14+
const segments = relPath.split(path.sep);
15+
segments.pop();
16+
const madeSegments = [];
17+
segments.forEach(segment => {
18+
madeSegments.push(segment);
19+
const p = join(writeProcessIoTo, madeSegments.join(sep));
20+
if (!fs.existsSync(p)) fs.mkdirSync(p);
21+
});
22+
fs.writeFileSync(dest, JSON.stringify({
23+
in: args,
24+
out: typeof result === 'object' ? result.code : result,
25+
}), 'utf8');
26+
}
27+
}

e2e/__helpers__/test-case.ts

Lines changed: 159 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// tslint:disable-file:no-shadowed-variable
22
import { sync as spawnSync } from 'cross-spawn';
3-
import { join } from 'path';
3+
import { join, relative, sep } from 'path';
44
import * as Paths from '../../scripts/paths';
55
import * as fs from 'fs-extra';
66

@@ -133,6 +133,8 @@ export interface RunTestOptions {
133133
template?: string;
134134
env?: {};
135135
args?: string[];
136+
inject?: (() => any) | string;
137+
writeIo?: boolean;
136138
}
137139

138140
export interface TestRunResult {
@@ -141,6 +143,12 @@ export interface TestRunResult {
141143
stdout: string;
142144
stderr: string;
143145
output: string;
146+
ioDataFor(relPath: string): TestFileIoData;
147+
}
148+
interface TestFileIoData {
149+
in: [string, jest.Path, jest.ProjectConfig, jest.TransformOptions?];
150+
// out: string | jest.TransformedSource;
151+
out: string;
144152
}
145153

146154
// tslint:disable-next-line:interface-over-type-literal
@@ -194,27 +202,73 @@ export function templateNameForPath(path: string): string {
194202
return 'default';
195203
}
196204

197-
export function run(
198-
name: string,
199-
{ args = [], env = {}, template }: RunTestOptions = {},
200-
): TestRunResult {
201-
const dir = prepareTest(
205+
export function run(name: string, options: RunTestOptions = {}): TestRunResult {
206+
const { args = [], env = {}, template, inject, writeIo } = options;
207+
const { workdir: dir, sourceDir, hooksFile, ioDir } = prepareTest(
202208
name,
203209
template || templateNameForPath(join(Paths.e2eSourceDir, name)),
210+
options,
204211
);
205212
const pkg = require(join(dir, 'package.json'));
206213

207-
const prefix =
208-
pkg.scripts && pkg.scripts.test
209-
? ['npm', '-s', 'run', 'test']
210-
: [join(dir, 'node_modules', '.bin', 'jest')];
211-
args = [...prefix, ...args];
212-
const cmd = args.shift();
214+
let cmdArgs: string[] = [];
215+
if (inject) {
216+
cmdArgs.push('--testPathPattern="/__eval\\\\.ts$"');
217+
} // '--testRegex=""'
218+
if (process.argv.find(v => ['--updateSnapshot', '-u'].includes(v))) {
219+
cmdArgs.push('-u');
220+
}
221+
cmdArgs.push(...args);
222+
if (!inject && pkg.scripts && pkg.scripts.test) {
223+
if (cmdArgs.length) {
224+
cmdArgs.unshift('--');
225+
}
226+
cmdArgs = ['npm', '-s', 'run', 'test', ...cmdArgs];
227+
} else {
228+
cmdArgs.unshift(join(dir, 'node_modules', '.bin', 'jest'));
229+
}
230+
231+
const cmd = cmdArgs.shift();
232+
233+
// Add both process.env which is the standard and custom env variables
234+
const mergedEnv: any = {
235+
...process.env,
236+
...env,
237+
};
238+
if (inject) {
239+
const injected =
240+
typeof inject === 'function'
241+
? `(${inject.toString()}).apply(this);`
242+
: inject;
243+
mergedEnv.__TS_JEST_EVAL = injected;
244+
}
245+
if (writeIo) {
246+
mergedEnv.__TS_JEST_HOOKS = hooksFile;
247+
}
213248

214-
const result = spawnSync(cmd, args, {
249+
const result = spawnSync(cmd, cmdArgs, {
215250
cwd: dir,
216-
// Add both process.env which is the standard and custom env variables
217-
env: { ...process.env, ...env },
251+
env: mergedEnv,
252+
});
253+
254+
// we need to copy each snapshot which does NOT exists in the source dir
255+
fs.readdirSync(dir).forEach(item => {
256+
if (
257+
item === 'node_modules' ||
258+
!fs.statSync(join(dir, item)).isDirectory()
259+
) {
260+
return;
261+
}
262+
const srcDir = join(sourceDir, item);
263+
const wrkDir = join(dir, item);
264+
fs.copySync(wrkDir, srcDir, {
265+
overwrite: false,
266+
filter: from => {
267+
return relative(sourceDir, from)
268+
.split(sep)
269+
.includes('__snapshots__');
270+
},
271+
});
218272
});
219273

220274
// Call to string on byte arrays and strip ansi color codes for more accurate string comparison.
@@ -224,13 +278,19 @@ export function run(
224278
? stripAnsiColors(result.output.join('\n\n'))
225279
: '';
226280

227-
return {
281+
const res = {
228282
[TestRunResultFlag]: true,
229283
status: result.status,
230284
stderr,
231285
stdout,
232286
output,
233287
};
288+
if (writeIo) {
289+
Object.defineProperty(res, 'ioDataFor', {
290+
value: (relPath: string) => require(`${ioDir}/${relPath}.json`),
291+
});
292+
}
293+
return res as any;
234294
}
235295

236296
// from https://stackoverflow.com/questions/25245716/remove-all-ansi-colors-styles-from-strings
@@ -241,18 +301,29 @@ function stripAnsiColors(stringToStrip: string): string {
241301
);
242302
}
243303

244-
function prepareTest(name: string, template: string): string {
304+
interface PreparedTest {
305+
workdir: string;
306+
templateDir: string;
307+
sourceDir: string;
308+
ioDir: string;
309+
hooksFile: string;
310+
}
311+
function prepareTest(
312+
name: string,
313+
template: string,
314+
options: RunTestOptions = {},
315+
): PreparedTest {
245316
const sourceDir = join(Paths.e2eSourceDir, name);
246317
// working directory is in the temp directory, different for each template name
247-
const caseDir = join(Paths.e2eWorkDir, template, name);
318+
const caseWorkdir = join(Paths.e2eWorkDir, template, name);
248319
const templateDir = join(Paths.e2eWorkTemplatesDir, template);
249320

250321
// recreate the directory
251-
fs.removeSync(caseDir);
252-
fs.mkdirpSync(caseDir);
322+
fs.removeSync(caseWorkdir);
323+
fs.mkdirpSync(caseWorkdir);
253324

254325
const tmplModulesDir = join(templateDir, 'node_modules');
255-
const caseModulesDir = join(caseDir, 'node_modules');
326+
const caseModulesDir = join(caseWorkdir, 'node_modules');
256327

257328
// link the node_modules dir if the template has one
258329
if (fs.existsSync(tmplModulesDir)) {
@@ -264,19 +335,82 @@ function prepareTest(name: string, template: string): string {
264335
if (TEMPLATE_EXCLUDED_ITEMS.includes(item)) {
265336
return;
266337
}
267-
fs.copySync(join(templateDir, item), join(caseDir, item));
338+
fs.copySync(join(templateDir, item), join(caseWorkdir, item));
268339
});
269340

270341
// copy source and test files
271-
fs.copySync(sourceDir, caseDir);
342+
const snapshotDirs: Record<string, 0> = Object.create(null);
343+
fs.copySync(sourceDir, caseWorkdir, {
344+
filter: src => {
345+
const relPath = relative(sourceDir, src);
346+
const segments = relPath.split(sep);
347+
if (segments.includes('__snapshots__')) {
348+
// link snapshots
349+
while (segments[segments.length - 1] !== '__snapshots__') {
350+
segments.pop();
351+
}
352+
snapshotDirs[segments.join(sep)] = 0;
353+
return false;
354+
} else {
355+
return true;
356+
}
357+
},
358+
});
359+
// create symbolic links for the existing snapshots
360+
Object.keys(snapshotDirs).forEach(dir => {
361+
fs.ensureSymlinkSync(join(sourceDir, dir), join(caseWorkdir, dir));
362+
});
363+
364+
// create the special files
365+
fs.outputFileSync(join(caseWorkdir, '__eval.ts'), EVAL_SOURCE, 'utf8');
366+
let ioDir!: string;
367+
if (options.writeIo) {
368+
ioDir = join(caseWorkdir, '__io');
369+
fs.mkdirpSync(ioDir);
370+
}
371+
const hooksFile = join(caseWorkdir, '__hooks.js');
372+
fs.outputFileSync(
373+
hooksFile,
374+
hooksSourceWith({
375+
writeProcessIoTo: ioDir || false,
376+
}),
377+
'utf8',
378+
);
272379

273380
// create a package.json if it does not exists, and/or enforce the package name
274-
const pkgFile = join(caseDir, 'package.json');
381+
const pkgFile = join(caseWorkdir, 'package.json');
275382
const pkg: any = fs.existsSync(pkgFile) ? fs.readJsonSync(pkgFile) : {};
276383
pkg.name = name;
277384
pkg.private = true;
278385
pkg.version = `0.0.0-mock0`;
279386
fs.outputJsonSync(pkgFile, pkg, { spaces: 2 });
280387

281-
return caseDir;
388+
return { workdir: caseWorkdir, templateDir, sourceDir, hooksFile, ioDir };
389+
}
390+
391+
const EVAL_SOURCE = `
392+
describe.skip('__eval', () => {
393+
test.skip('__test', () => {
394+
expect(true).toBe(true);
395+
});
396+
it.skip('__test', () => {
397+
expect(true).toBe(true);
398+
});
399+
});
400+
401+
eval(process.__TS_JEST_EVAL);
402+
`;
403+
404+
// tslint:disable-next-line:variable-name
405+
let __hooksSource: string;
406+
function hooksSourceWith(vars: Record<string, any>): string {
407+
if (!__hooksSource) {
408+
__hooksSource = fs.readFileSync(
409+
join(__dirname, '__hooks-source__.js.hbs'),
410+
'utf8',
411+
);
412+
}
413+
return __hooksSource.replace(/\{\{([^\}]+)\}\}/g, (_, key) =>
414+
JSON.stringify(vars[key]),
415+
);
282416
}

0 commit comments

Comments
 (0)