UNPKG

5.91 kBJavaScriptView Raw
1"use strict";
2
3const MemoryFS = require('memory-fs');
4const path = require('path');
5const utils = require('../utils');
6const Promise = require('bluebird');
7const _ = require('lodash');
8const lazyRequire = require('../commons/lazyRequire');
9const { AbortError } = require('../commons/AbortError');
10
11const { isEqual, cloneDeep } = require('lodash');
12
13// compiler instance we can reuse between calls
14const state = {
15 compiler: null,
16 webpackConfig: null
17};
18
19exports.buildCodeTests = async function buildCodeTestsGuarded(
20 files,
21 webpackConfig,
22 runnerOptionsToMaybeCopyToTestEnvironment,
23 fileSystem,
24 optionalAbortSignal
25) {
26 try {
27 return await buildCodeTests(files, webpackConfig, runnerOptionsToMaybeCopyToTestEnvironment, fileSystem, optionalAbortSignal);
28 } catch (e) {
29 if (e && e.code === 'ENOENT') {
30 // this is a webpack config error - it means webpack didn't pick up on a file change and didn't generate the correct bundle
31 // according to the guid hash
32 state.compiler = null;
33 state.webpackConfig = null;
34 fileSystem.data = {}; // reset the memory filesystem in case we left a mess there in the previous invocation
35 return await buildCodeTests(files, webpackConfig, runnerOptionsToMaybeCopyToTestEnvironment, fileSystem, optionalAbortSignal);
36 }
37 throw e;
38 }
39}
40
41async function buildCodeTests(files, webpackConfig = {mode: 'development'}, runnerOptionsToMaybeCopyToTestEnvironment, fileSystem, optionalAbortSignal) {
42
43 const webpack = await lazyRequire('webpack');
44
45 const suite = {};
46 const webpackConfigBeforeOurChanges = cloneDeep(webpackConfig);
47
48 webpackConfig.externals = { // define testim as an external
49 'testim': '__testim',
50 // 'chai': '__chai'
51 };
52 webpackConfig.devtool = webpackConfig.devtool || 'inline-source-map';
53
54 webpackConfig.plugins = webpackConfig.plugins || [];
55
56 webpackConfig.plugins.push(new webpack.DefinePlugin(getEnvironmentVariables(runnerOptionsToMaybeCopyToTestEnvironment)))
57 webpackConfig.plugins.push(new webpack.DefinePlugin({
58 'process.argv': JSON.stringify(process.argv)
59 }));
60 files = files.map(f => path.resolve(f));
61
62 const fileHashes = files.map(x => utils.guid(30));
63
64 webpackConfig.optimization = { minimize: false };
65
66 webpackConfig.entry = _.fromPairs(_.zip(files, fileHashes).map(([filename, hash]) => {
67 return [hash, filename];
68 }));
69 webpackConfig.output = Object.assign({
70 devtoolModuleFilenameTemplate: (info) => `file:///${info.absoluteResourcePath}`,
71 filename: '[name].bundle.js',
72 }, webpackConfig.output);
73
74
75 let compiler;
76 // if we are passed a filesystem, assume reuse between calls and turn on watch mode
77 if (fileSystem) {
78 // were we passed a filesystem before to compile the same thing?
79 if (isEqual(state.webpackConfig, webpackConfigBeforeOurChanges) && state.compiler) {
80 // we already have a compiler up and running
81 compiler = state.compiler;
82 } else {
83 state.webpackConfig = webpackConfigBeforeOurChanges;
84 state.compiler = webpack(webpackConfig);
85 compiler = state.compiler;
86 }
87 } else {
88 compiler = webpack(webpackConfig); // no caching
89 }
90
91 const mfs = fileSystem || new MemoryFS();
92 compiler.outputFileSystem = mfs; // no need to write compiled tests to disk
93
94 // This can only reject
95 const abortSignalPromise = Promise.fromCallback(cb => {
96 if (optionalAbortSignal) {
97 optionalAbortSignal.addEventListener("abort", () => {
98 cb(new AbortError());
99 });
100 }
101 });
102
103 // run compiler:
104 try {
105 const stats = await Promise.race([Promise.fromCallback(cb => compiler.run(cb)), abortSignalPromise]);
106 if (stats.hasErrors()) {
107 throw new Error(stats.toJson().errors);
108 }
109 } catch (e) {
110 const {ArgError} = require('../errors');
111
112 const cantFindFile = e.message.match(/Entry module not found: Error: Can't resolve '(.*)'/);
113 if (cantFindFile && cantFindFile.length === 2) {
114 if (webpackConfig.output && webpackConfig.output.library === 'tdk') {
115 throw new ArgError(`Could not open dev-kit functions file in ${cantFindFile[1]}`);
116 }
117 throw new ArgError(`Can't find test files in: '${cantFindFile[1]}'`);
118 }
119
120 throw new ArgError("Compilation Webpack Error in tests: " + e.message);
121 }
122
123 const fileResults = files.map((file, i) => ({code: mfs.readFileSync(path.resolve('./dist', `${fileHashes[i]}.bundle.js`)), name: file })); // read all files
124
125 suite.tests = [fileResults.map(({code, name}) => ({
126 code: code.toString(),
127 baseUrl: "", // not supported at the moment
128 name: path.resolve(name),
129 testConfig: {},
130 testConfigId: null,
131 testId: utils.guid(),
132 resultId: utils.guid(),
133 isTestsContainer: true
134 }))];
135 suite.runName = 'Testim Dev Kit Run ' + (new Date().toLocaleString());
136 return suite;
137}
138
139// copied mostly from facebook/create-react-app/blob/8b7b819b4b9e6ba457e011e92e33266690e26957/packages/react-scripts/config/env.js
140function getEnvironmentVariables(runnerOptionsToMaybeCopyToTestEnvironment) {
141
142 let fromEnvironment = _.fromPairs(
143 Object.keys(process.env)
144 .filter(key => /^TDK_/i.test(key) || key === 'BASE_URL')
145 .map(key => [key, process.env[key]])
146 );
147
148 let fromConfig = {
149 'BASE_URL': runnerOptionsToMaybeCopyToTestEnvironment.baseUrl
150 };
151
152 return {
153 'process.env': stringifyValues({ ...fromConfig, ...fromEnvironment})
154 };
155}
156function stringifyValues(object) {
157 return _.fromPairs(Object.entries(object).map(([key, value]) => [key, JSON.stringify(value)]));
158}