UNPKG

14.3 kBJavaScriptView Raw
1/* eslint-env node, mocha */
2/* eslint-disable prefer-arrow-callback, func-names */
3
4// Dependencies.
5const assert = require('assert').strict;
6const path = require('path');
7const sinon = require('sinon');
8const lib = require('./lib');
9const webpack = require('webpack');
10const fsExtra = require('fs-extra');
11
12// Dependencies used by tests assertions
13const devServerFn = require('../lib/dev-server');
14const logger = require('../lib/logger');
15const wpDevConf = require('../webpack-config/webpack.base.conf');
16const febsModule = require('../index');
17
18describe('FEBS Development Tests', function () {
19 let compile;
20 let fs;
21
22 logger.setLogLevel('warn'); // Suppress info messages
23
24 beforeEach(function () {
25 process.env.FEBS_TEST = true;
26
27 // Keep reference to fs for test assertions.
28 fs = lib.createFS();
29
30 // Create compile function using in-memory fs and dev env.
31 compile = lib.createCompileFn(fs, 'development');
32 });
33
34 describe('ECMAScript', async function () {
35 it('builds ES bundle', async function () {
36 const compiled = await compile(lib.createConf({
37 entry: {
38 app: lib.absPath('fixtures/src/main-es2015.js'),
39 },
40 }));
41
42 assert.equal(compiled.code.app[0].filename, 'app.bundle.js');
43 assert(compiled.code.app[0].content.includes('add: function add()'));
44 });
45
46 it('transpiles ES from @rei namespace only', async function () {
47 // Create temp @rei and non-@rei namespace modules in node_modules
48 const srcReiNamespace = path.join(__dirname, 'test-modules/@rei/test');
49 const destReiNamespace = path.join(__dirname, '../node_modules/@rei/test');
50 const srcNonReiNamespace = path.join(__dirname, 'test-modules/some-module');
51 const destNonReiNamespace = path.join(__dirname, '../node_modules/some-module');
52
53 fsExtra.copySync(srcReiNamespace, destReiNamespace);
54 fsExtra.copySync(srcNonReiNamespace, destNonReiNamespace);
55
56 const compiled = await compile(lib.createConf({
57 entry: {
58 app: lib.absPath('fixtures/src/main-es2015-rei-namespace.js'),
59 },
60 }));
61
62 // @rei namespace should be transpiled
63 assert(compiled.code.app[0].content.includes('add3: function add3'));
64
65 // non-@rei namespace should not be transpiled
66 assert(!compiled.code.app[0].content.includes('add4: function add4'));
67
68 // Cleanup temp modules
69 fsExtra.removeSync(destReiNamespace);
70 fsExtra.removeSync(destNonReiNamespace);
71 });
72
73 it('builds multiple ES bundles', async function () {
74 const compiled = await compile(lib.createConf({
75 entry: {
76 app1: lib.absPath('fixtures/src/main-es2015.js'),
77 app2: lib.absPath('fixtures/src/main-es2015.js'),
78 },
79 }));
80
81 assert(lib.compiledContains(compiled, {
82 entryName: /app1/,
83 content: /unction add/,
84 fileName: /\.js$/,
85 }));
86
87 assert(lib.compiledContains(compiled, {
88 entryName: /app2/,
89 content: /unction add/,
90 fileName: /\.js$/,
91 }));
92 });
93
94 it('detects ES syntax errors', async function () {
95 await compile(lib.createConf({
96 entry: {
97 app: lib.absPath('fixtures/src/main-es2015-syntax-errors.js'),
98 },
99 })).then((o) => {
100 assert.ok(o.stats.compilation.errors[0].message.includes('Unexpected token'));
101 });
102 });
103
104 it('polyfills based on supported browsers (IE11)', async function () {
105 const compiled = await compile(lib.createConf({
106 entry: {
107 app1: lib.absPath('fixtures/src/main-es-polyfill.js'),
108 },
109 }));
110
111 // Object.assign
112 assert(lib.compiledContains(compiled, {
113 entryName: /app1/,
114 content: /es6.object.assign/,
115 fileName: /\.js$/,
116 }));
117
118 // Promise
119 assert(lib.compiledContains(compiled, {
120 entryName: /app1/,
121 content: /es6.promise/,
122 fileName: /\.js$/,
123 }));
124 })
125 });
126
127
128 describe('Vue', function () {
129 it('compiles Vue tags', async function () {
130 const compiled = await compile(lib.createConf({
131 entry: {
132 app: lib.absPath('fixtures/src/vue/main-vue.js'),
133 },
134 }));
135
136 assert(lib.compiledWithNoErrors(compiled), compiled.stats.compilation.errors);
137
138 assert(lib.compiledContains(compiled, {
139 entryName: /^app$/,
140 content: /Vue says/,
141 fileName: /\.js$/,
142 }));
143 });
144
145 it('extracted external vue css styles and put into app css file', async function () {
146 const compiled = await compile(lib.createConf({
147 entry: {
148 app: lib.absPath('fixtures/src/vue/main-vue.js'),
149 },
150 }));
151
152 assert(lib.compiledWithNoErrors(compiled), compiled.stats.compilation.errors);
153
154 assert(lib.compiledContains(compiled, {
155 entryName: /^app$/,
156 content: /papayawhip/,
157 fileName: /\.css$/,
158 }));
159 });
160
161 it('extracted inline vue css styles and put into app css file', async function () {
162 const compiled = await compile(lib.createConf({
163 entry: {
164 app: lib.absPath('fixtures/src/vue/main-vue.js'),
165 },
166 }));
167
168 assert(lib.compiledContains(compiled, {
169 entryName: /^app$/,
170 content: /red/,
171 fileName: /\.css$/,
172 }));
173
174 assert(lib.compiledWithNoErrors(compiled), compiled.stats.compilation.errors);
175 });
176
177 it('transpiles es2015+ Vue tags', async function () {
178 const compiled = await compile(lib.createConf({
179 entry: {
180 app: lib.absPath('fixtures/src/vue/main-vue.js'),
181 },
182 }));
183
184 assert(lib.compiledWithNoErrors(compiled), compiled.stats.compilation.errors);
185
186 assert(lib.compiledContains(compiled, {
187 entryName: /^app$/,
188 content: /function helloWorld/,
189 fileName: /\.js$/,
190 }));
191 });
192
193 it('detects Vue JavaScript syntax errors', async function () {
194 await compile(lib.createConf({
195 entry: {
196 app: lib.absPath('fixtures/src/vue/main-vue-syntax-error.js'),
197 },
198 })).then((o) => {
199 assert.ok(o.stats.compilation.errors[0].message.includes('SyntaxError'));
200 });
201 });
202 });
203
204 describe('Sourcemaps', async function () {
205 it('generates inline ES sourcemaps', async function () {
206 const compiled = await compile(lib.createConf({
207 entry: {
208 app: lib.absPath('fixtures/src/main-es2015.js'),
209 },
210 }));
211
212 assert(compiled.code.app[0].content.includes('sourceURL'));
213 });
214 });
215
216 describe('Manifest', async function () {
217 it('generates a manifest json file for versioned asset mappings', async function () {
218 const getJsonFromFS = lib.getJsonFromFile(fs);
219
220 const compiled = await compile(lib.createConf({
221 entry: {
222 app: lib.absPath('fixtures/src/main-es2015.js'),
223 },
224 }));
225
226 assert(lib.compiledWithNoErrors(compiled), compiled.stats.compilation.errors);
227
228 const manifestFile = path.resolve(compiled.options.output.path, 'febs-manifest.json');
229 assert(fs.statSync(manifestFile).isFile());
230
231 const manifestJson = getJsonFromFS(manifestFile);
232 assert.equal(manifestJson['app.js'], 'app.bundle.js');
233 });
234 });
235
236 describe('Logger', function () {
237 it('should contain setLogLevel function', function () {
238 assert(logger.setLogLevel);
239 });
240
241 it('allow changing log levels', function () {
242 logger.setLogLevel('warn');
243 assert.equal(logger.transports.console.level, 'warn');
244 });
245 });
246
247 describe('addVueSSRToWebpackConfig', function () {
248 it('should add VueSSRServerPlugin to webpack config', function () {
249 const febs = febsModule({
250 fs,
251 });
252
253 const wpConfig = febs.addVueSSRToWebpackConfig(true, wpDevConf);
254
255 assert(wpConfig.plugins.some(plugin => plugin.constructor.name === 'VueSSRServerPlugin'));
256 assert.equal(wpConfig.output.libraryTarget, 'commonjs2');
257 });
258
259 it('should not add VueSSRServerPlugin', function () {
260 const febs = febsModule({
261 fs,
262 });
263
264 const wpConfig = febs.addVueSSRToWebpackConfig(false, wpDevConf);
265
266 assert(wpConfig.plugins.every(plugin => plugin.constructor.name !== 'VueSSRServerPlugin'));
267 });
268 });
269
270 describe('getWebpackConfig', function () {
271 it('should not return multiple plugin entries after merging confs', function () {
272 const febs = febsModule({
273 fs,
274 });
275 const expectedLength = wpDevConf.module.rules.length;
276 const wpConfig = febs.getWebpackConfig(false)(wpDevConf);
277 assert.equal(expectedLength, wpConfig.module.rules.length);
278 });
279
280 it('should not contain ManifestPlugin if SSR build', function () {
281 const febs = febsModule({
282 fs,
283 });
284
285 const wpConfig = febs.getWebpackConfigFn(true)(wpDevConf);
286 assert(wpConfig.plugins.every(plugin => plugin.constructor.name !== 'ManifestPlugin'));
287 });
288
289 it('should contain ManifestPlugin if not SSR build', function () {
290 const febs = febsModule({
291 fs,
292 });
293
294 const wpConfig = febs.getWebpackConfigFn(false)(wpDevConf);
295 assert(wpConfig.plugins.some(plugin => plugin.constructor.name === 'ManifestPlugin'));
296 });
297 });
298
299 describe('Webpack config', async function () {
300 it('Output path can be modified', async function () {
301 const compiled = await compile(lib.createConf({
302 entry: {
303 app: lib.absPath('fixtures/src/main-es2015.js'),
304 },
305 output: {
306 path: lib.absPath('build/modified-output-path'),
307 },
308 }));
309
310 assert(compiled.options.output.path.includes('build/modified-output-path'));
311 });
312 });
313
314 describe('febs-config', function () {
315 it('should allow dist path to be changed', function () {
316 const desiredOutputPath = path.resolve('./cool_output_path');
317
318 const febs = febsModule({
319 fs,
320 }, {
321 output: {
322 path: desiredOutputPath,
323 },
324 });
325
326 const webpackConfig = febs.getWebpackConfig(false)(wpDevConf);
327
328 assert.equal(webpackConfig.output.path, path.resolve(desiredOutputPath, '@rei', 'febs'));
329 });
330
331 it('should allow entry points to be changed', function () {
332 const desiredEntryPath = 'src/js/entryX.js';
333
334 const webpackConfig = febsModule({
335 fs,
336 }, {
337 entry: {
338 app: [
339 desiredEntryPath,
340 ],
341 },
342 }).getWebpackConfig(false)(wpDevConf);
343
344 assert(webpackConfig.entry.app[0].endsWith(desiredEntryPath));
345 });
346
347 describe('febsConfigMerge', function () {
348 it('should override output path from febs-config', function () {
349 const febs = febsModule({
350 fs,
351 });
352
353 const febsConfig = {
354 output: {
355 path: 'a',
356 },
357 };
358
359 const wpConfig = {
360 output: {
361 path: 'b',
362 },
363 };
364
365 const expected = path.resolve(process.cwd(), febsConfig.output.path, '@rei/febs');
366 assert.deepEqual(febs.febsConfigMerge(febsConfig, wpConfig).output.path, expected);
367 });
368
369 it('should use wpConf output if none in febs-config', function () {
370 const febs = febsModule({
371 fs,
372 });
373
374 const febsConfig = {
375 entry: {},
376 };
377
378 const wpConfig = {
379 output: {
380 path: 'b',
381 },
382 };
383
384 assert.deepEqual(febs.febsConfigMerge(febsConfig, wpConfig).output.path, 'b');
385 });
386
387 it('should update wpConfig entry with fully qualified paths', function () {
388 const febs = febsModule({
389 fs,
390 });
391
392 const febsConfig = {
393 entry: {
394 details: [
395 'relative/path/to/entry0.js',
396 'relative/path/to/entry1.js',
397 ],
398 },
399 };
400
401 const wpConfig = {
402 entry: {
403 app: [
404 'some/path/to/entry.js',
405 ],
406 },
407 output: {
408 path: 'b',
409 },
410 };
411
412 const expected0 = path.resolve(process.cwd(), febsConfig.entry.details[0]);
413 const expected1 = path.resolve(process.cwd(), febsConfig.entry.details[1]);
414 assert.deepEqual(febs.febsConfigMerge(febsConfig, wpConfig).entry.details[0], expected0);
415 assert.deepEqual(febs.febsConfigMerge(febsConfig, wpConfig).entry.details[1], expected1);
416 });
417
418 it('original webpack config should not be modified', function () {
419 const febs = febsModule({
420 fs,
421 });
422
423 const febsConfig = {
424 entry: {
425 details: [
426 'relative/path/to/entry0.js',
427 'relative/path/to/entry1.js',
428 ],
429 },
430 };
431
432 const wpConfig = {
433 entry: {
434 app: [
435 'some/path/to/entry.js',
436 ],
437 },
438 output: {
439 path: 'b',
440 },
441 };
442
443 febs.febsConfigMerge(febsConfig, wpConfig);
444
445 assert.equal(wpConfig.output.path, 'b');
446 assert.deepEqual(wpConfig.entry.app, ['some/path/to/entry.js']);
447 });
448 });
449 });
450
451 describe('Exit codes', function () {
452 it('should not return exit code 1 in dev mode so that watching persists)', async function () {
453 await compile(lib.createConf({
454 entry: {
455 app1: lib.absPath('fixtures/src/main-es2015-syntax-errors.js'),
456 },
457 })).then((o) => {
458 assert.equal(o.exitCode, 0);
459 });
460 });
461 });
462
463 describe('Dev Server', function () {
464 // So we aren't starting an actual server during unit tests.
465 let FakeWDS;
466
467 beforeEach(() => FakeWDS = function (compiler) {
468 this.listen = () => {
469 };
470 this.compiler = compiler;
471 });
472
473 it('should create new server', function () {
474 const devServer = devServerFn(FakeWDS, () => {
475 });
476 assert(devServer instanceof FakeWDS);
477 });
478
479 it('should pass in compiler and webpack conf', function () {
480 const febs = febsModule({
481 fs,
482 });
483
484 // Assert dev server returned
485 const devServer = febs.startDevServerFn(FakeWDS)();
486 assert(devServer instanceof FakeWDS);
487
488 // Assert webpack compiler passed to FakeWDS
489 assert(devServer.compiler instanceof webpack.Compiler);
490 });
491 });
492});