diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 192d686e..6616426d 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -58,7 +58,6 @@ jobs: node-10-canary: node_version: ^10.13.0 webpack_version: next - continue_on_error: true steps: - task: NodeTool@0 inputs: @@ -115,7 +114,6 @@ jobs: node-10-canary: node_version: ^10.13.0 webpack_version: next - continue_on_error: true steps: - task: NodeTool@0 inputs: @@ -172,7 +170,6 @@ jobs: node-10-canary: node_version: ^10.13.0 webpack_version: next - continue_on_error: true steps: - script: 'git config --global core.autocrlf input' displayName: 'Config git core.autocrlf' diff --git a/package-lock.json b/package-lock.json index 657c4a1b..1ebd2489 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1486,9 +1486,9 @@ "dev": true }, "@types/node": { - "version": "12.12.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.18.tgz", - "integrity": "sha512-DBkZuIMFuAfjJHiunyRc+aNvmXYNwV1IPMgGKGlwCp6zh6MKrVtmvjSWK/axWcD25KJffkXgkfvFra8ndenXAw==", + "version": "12.12.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.19.tgz", + "integrity": "sha512-OXw80IpKyLeuZ5a8r2XCxVNnRAtS3lRDHBleSUQmbgu3C6eKqRsz7/5XNBU0EvK0RTVfotvYFgvRwwe2jeoiKw==", "dev": true }, "@types/normalize-package-data": { @@ -3901,8 +3901,9 @@ } }, "css-loader": { - "version": "github:webpack-contrib/css-loader#63a74b2ccbec74c8e019e66465018c74378b95ad", - "from": "github:webpack-contrib/css-loader#master", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-3.4.0.tgz", + "integrity": "sha512-JornYo4RAXl1Mzt0lOSVPmArzAMV3rGY2VuwtaDc732WTWjdwTaeS19nCGWMcSCf305Q396lhhDAJEWWM0SgPQ==", "dev": true, "requires": { "camelcase": "^5.3.1", diff --git a/package.json b/package.json index 5ff361ac..aad58a03 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "dist" ], "peerDependencies": { - "webpack": "^4.0.0" + "webpack": "^4.0.0 || ^5.0.0" }, "dependencies": { "loader-utils": "^1.2.3", @@ -58,7 +58,7 @@ "babel-jest": "^24.9.0", "commitlint-azure-pipelines-cli": "^1.0.2", "cross-env": "^6.0.3", - "css-loader": "webpack-contrib/css-loader#master", + "css-loader": "^3.4.0", "del": "^5.1.0", "del-cli": "^3.0.0", "es-check": "^5.1.0", diff --git a/test/__snapshots__/loader.test.js.snap b/test/__snapshots__/loader.test.js.snap index 57a784ae..3011264f 100644 --- a/test/__snapshots__/loader.test.js.snap +++ b/test/__snapshots__/loader.test.js.snap @@ -314,6 +314,54 @@ exports[`loader should work when ref is negative when the "injectType" option is exports[`loader should work when ref is negative when the "injectType" option is "lazyStyleTag": warnings 1`] = `Array []`; +exports[`loader should work when the "injectType" option is "lazySingletonStyleTag" and CommonJS module syntax used: DOM 1`] = ` +" + style-loader test + + + +

Body

+
+ + + +" +`; + +exports[`loader should work when the "injectType" option is "lazySingletonStyleTag" and CommonJS module syntax used: errors 1`] = `Array []`; + +exports[`loader should work when the "injectType" option is "lazySingletonStyleTag" and CommonJS module syntax used: warnings 1`] = `Array []`; + +exports[`loader should work when the "injectType" option is "lazySingletonStyleTag" and ES module syntax used: DOM 1`] = ` +" + style-loader test + + + +

Body

+
+ + + +" +`; + +exports[`loader should work when the "injectType" option is "lazySingletonStyleTag" and ES module syntax used: errors 1`] = `Array []`; + +exports[`loader should work when the "injectType" option is "lazySingletonStyleTag" and ES module syntax used: warnings 1`] = `Array []`; + exports[`loader should work when the "injectType" option is "lazySingletonStyleTag": DOM 1`] = ` " style-loader test @@ -338,6 +386,54 @@ exports[`loader should work when the "injectType" option is "lazySingletonStyleT exports[`loader should work when the "injectType" option is "lazySingletonStyleTag": warnings 1`] = `Array []`; +exports[`loader should work when the "injectType" option is "lazyStyleTag" and CommonJS module syntax used: DOM 1`] = ` +" + style-loader test + + + +

Body

+
+ + + +" +`; + +exports[`loader should work when the "injectType" option is "lazyStyleTag" and CommonJS module syntax used: errors 1`] = `Array []`; + +exports[`loader should work when the "injectType" option is "lazyStyleTag" and CommonJS module syntax used: warnings 1`] = `Array []`; + +exports[`loader should work when the "injectType" option is "lazyStyleTag" and ES module syntax used: DOM 1`] = ` +" + style-loader test + + + +

Body

+
+ + + +" +`; + +exports[`loader should work when the "injectType" option is "lazyStyleTag" and ES module syntax used: errors 1`] = `Array []`; + +exports[`loader should work when the "injectType" option is "lazyStyleTag" and ES module syntax used: warnings 1`] = `Array []`; + exports[`loader should work when the "injectType" option is "lazyStyleTag": DOM 1`] = ` " style-loader test @@ -362,7 +458,7 @@ exports[`loader should work when the "injectType" option is "lazyStyleTag": erro exports[`loader should work when the "injectType" option is "lazyStyleTag": warnings 1`] = `Array []`; -exports[`loader should work when the "injectType" option is "linkTag" and "file-loader" uses CommonJS module syntax: DOM 1`] = ` +exports[`loader should work when the "injectType" option is "linkTag" and CommonJS module syntax used: DOM 1`] = ` " style-loader test @@ -376,11 +472,11 @@ exports[`loader should work when the "injectType" option is "linkTag" and "file- " `; -exports[`loader should work when the "injectType" option is "linkTag" and "file-loader" uses CommonJS module syntax: errors 1`] = `Array []`; +exports[`loader should work when the "injectType" option is "linkTag" and CommonJS module syntax used: errors 1`] = `Array []`; -exports[`loader should work when the "injectType" option is "linkTag" and "file-loader" uses CommonJS module syntax: warnings 1`] = `Array []`; +exports[`loader should work when the "injectType" option is "linkTag" and CommonJS module syntax used: warnings 1`] = `Array []`; -exports[`loader should work when the "injectType" option is "linkTag" and "file-loader" uses ES module syntax: DOM 1`] = ` +exports[`loader should work when the "injectType" option is "linkTag" and ES module syntax used: DOM 1`] = ` " style-loader test @@ -394,9 +490,9 @@ exports[`loader should work when the "injectType" option is "linkTag" and "file- " `; -exports[`loader should work when the "injectType" option is "linkTag" and "file-loader" uses ES module syntax: errors 1`] = `Array []`; +exports[`loader should work when the "injectType" option is "linkTag" and ES module syntax used: errors 1`] = `Array []`; -exports[`loader should work when the "injectType" option is "linkTag" and "file-loader" uses ES module syntax: warnings 1`] = `Array []`; +exports[`loader should work when the "injectType" option is "linkTag" and ES module syntax used: warnings 1`] = `Array []`; exports[`loader should work when the "injectType" option is "linkTag": DOM 1`] = ` " @@ -416,6 +512,54 @@ exports[`loader should work when the "injectType" option is "linkTag": errors 1` exports[`loader should work when the "injectType" option is "linkTag": warnings 1`] = `Array []`; +exports[`loader should work when the "injectType" option is "singletonStyleTag" and CommonJS module syntax used: DOM 1`] = ` +" + style-loader test + + + +

Body

+
+ + + +" +`; + +exports[`loader should work when the "injectType" option is "singletonStyleTag" and CommonJS module syntax used: errors 1`] = `Array []`; + +exports[`loader should work when the "injectType" option is "singletonStyleTag" and CommonJS module syntax used: warnings 1`] = `Array []`; + +exports[`loader should work when the "injectType" option is "singletonStyleTag" and ES module syntax used: DOM 1`] = ` +" + style-loader test + + + +

Body

+
+ + + +" +`; + +exports[`loader should work when the "injectType" option is "singletonStyleTag" and ES module syntax used: errors 1`] = `Array []`; + +exports[`loader should work when the "injectType" option is "singletonStyleTag" and ES module syntax used: warnings 1`] = `Array []`; + exports[`loader should work when the "injectType" option is "singletonStyleTag": DOM 1`] = ` " style-loader test @@ -440,6 +584,54 @@ exports[`loader should work when the "injectType" option is "singletonStyleTag": exports[`loader should work when the "injectType" option is "singletonStyleTag": warnings 1`] = `Array []`; +exports[`loader should work when the "injectType" option is "styleTag" and CommonJS module syntax used: DOM 1`] = ` +" + style-loader test + + + +

Body

+
+ + + +" +`; + +exports[`loader should work when the "injectType" option is "styleTag" and CommonJS module syntax used: errors 1`] = `Array []`; + +exports[`loader should work when the "injectType" option is "styleTag" and CommonJS module syntax used: warnings 1`] = `Array []`; + +exports[`loader should work when the "injectType" option is "styleTag" and ES module syntax used: DOM 1`] = ` +" + style-loader test + + + +

Body

+
+ + + +" +`; + +exports[`loader should work when the "injectType" option is "styleTag" and ES module syntax used: errors 1`] = `Array []`; + +exports[`loader should work when the "injectType" option is "styleTag" and ES module syntax used: warnings 1`] = `Array []`; + exports[`loader should work when the "injectType" option is "styleTag": DOM 1`] = ` " style-loader test diff --git a/test/__snapshots__/validate-options.test.js.snap b/test/__snapshots__/validate-options.test.js.snap index bb8d5a10..749d3d72 100644 --- a/test/__snapshots__/validate-options.test.js.snap +++ b/test/__snapshots__/validate-options.test.js.snap @@ -1,20 +1,20 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`validate options 1`] = ` -"Invalid options object. Style Loader has been initialised using an options object that does not match the API schema. - - options.injectType should be one of these: - \\"styleTag\\" | \\"singletonStyleTag\\" | \\"lazyStyleTag\\" | \\"lazySingletonStyleTag\\" | \\"linkTag\\" - -> Allows to setup how styles will be injected into DOM (https://github.com/webpack-contrib/style-loader#injecttype)." -`; - -exports[`validate options 2`] = ` +exports[`validate options should throw an error on the "attributes" option with "true" value 1`] = ` "Invalid options object. Style Loader has been initialised using an options object that does not match the API schema. - options.attributes should be an object: object { … } -> Adds custom attributes to tag (https://github.com/webpack-contrib/style-loader#attributes)." `; -exports[`validate options 3`] = ` +exports[`validate options should throw an error on the "injectType" option with "unknown" value 1`] = ` +"Invalid options object. Style Loader has been initialised using an options object that does not match the API schema. + - options.injectType should be one of these: + \\"styleTag\\" | \\"singletonStyleTag\\" | \\"lazyStyleTag\\" | \\"lazySingletonStyleTag\\" | \\"linkTag\\" + -> Allows to setup how styles will be injected into DOM (https://github.com/webpack-contrib/style-loader#injecttype)." +`; + +exports[`validate options should throw an error on the "insert" option with "true" value 1`] = ` "Invalid options object. Style Loader has been initialised using an options object that does not match the API schema. - options.insert should be one of these: string | function @@ -24,7 +24,49 @@ exports[`validate options 3`] = ` * options.insert should be an instance of function." `; -exports[`validate options 4`] = ` +exports[`validate options should throw an error on the "unknown" option with "/test/" value 1`] = ` +"Invalid options object. Style Loader has been initialised using an options object that does not match the API schema. + - options has an unknown property 'unknown'. These properties are valid: + object { injectType?, attributes?, insert?, base? }" +`; + +exports[`validate options should throw an error on the "unknown" option with "[]" value 1`] = ` +"Invalid options object. Style Loader has been initialised using an options object that does not match the API schema. + - options has an unknown property 'unknown'. These properties are valid: + object { injectType?, attributes?, insert?, base? }" +`; + +exports[`validate options should throw an error on the "unknown" option with "{"foo":"bar"}" value 1`] = ` +"Invalid options object. Style Loader has been initialised using an options object that does not match the API schema. + - options has an unknown property 'unknown'. These properties are valid: + object { injectType?, attributes?, insert?, base? }" +`; + +exports[`validate options should throw an error on the "unknown" option with "{}" value 1`] = ` +"Invalid options object. Style Loader has been initialised using an options object that does not match the API schema. + - options has an unknown property 'unknown'. These properties are valid: + object { injectType?, attributes?, insert?, base? }" +`; + +exports[`validate options should throw an error on the "unknown" option with "1" value 1`] = ` +"Invalid options object. Style Loader has been initialised using an options object that does not match the API schema. + - options has an unknown property 'unknown'. These properties are valid: + object { injectType?, attributes?, insert?, base? }" +`; + +exports[`validate options should throw an error on the "unknown" option with "false" value 1`] = ` +"Invalid options object. Style Loader has been initialised using an options object that does not match the API schema. + - options has an unknown property 'unknown'. These properties are valid: + object { injectType?, attributes?, insert?, base? }" +`; + +exports[`validate options should throw an error on the "unknown" option with "test" value 1`] = ` +"Invalid options object. Style Loader has been initialised using an options object that does not match the API schema. + - options has an unknown property 'unknown'. These properties are valid: + object { injectType?, attributes?, insert?, base? }" +`; + +exports[`validate options should throw an error on the "unknown" option with "true" value 1`] = ` "Invalid options object. Style Loader has been initialised using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: object { injectType?, attributes?, insert?, base? }" diff --git a/test/attributes-option.test.js b/test/attributes-option.test.js index 65228082..2868190a 100644 --- a/test/attributes-option.test.js +++ b/test/attributes-option.test.js @@ -1,4 +1,11 @@ -import { compile, getTestId, runTestInJsdom } from './helpers'; +import { + compile, + getCompiler, + getEntryByInjectType, + getErrors, + getWarnings, + runInJsDom, +} from './helpers/index'; describe('attributes option', () => { const injectTypes = [ @@ -13,58 +20,53 @@ describe('attributes option', () => { it(`should add attributes to tag when the "injectType" option is "${injectType}"`, async () => { expect.assertions(3); - const testId = getTestId('simple.js', injectType); - const stats = await compile(testId, { - loader: { - options: { - injectType, - attributes: { - type: 'text/css', - foo: 'bar', - 'data-id': 'style-tag-id', - }, - }, + const entry = getEntryByInjectType('simple.js', injectType); + const compiler = getCompiler(entry, { + injectType, + attributes: { + type: 'text/css', + foo: 'bar', + 'data-id': 'style-tag-id', }, }); + const stats = await compile(compiler); - runTestInJsdom(stats, (dom) => { + runInJsDom('main.bundle.js', compiler, stats, (dom) => { expect(dom.serialize()).toMatchSnapshot('DOM'); }); - expect(stats.compilation.warnings).toMatchSnapshot('warnings'); - expect(stats.compilation.errors).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); }); it(`should add nonce attribute when "injectType" option is "${injectType}"`, async () => { expect.assertions(3); - const testId = getTestId('nonce-require.js', injectType); - const stats = await compile(testId, { - loader: { options: { injectType } }, - }); + const entry = getEntryByInjectType('nonce-require.js', injectType); + const compiler = getCompiler(entry, { injectType }); + const stats = await compile(compiler); - runTestInJsdom(stats, (dom) => { + runInJsDom('main.bundle.js', compiler, stats, (dom) => { expect(dom.serialize()).toMatchSnapshot('DOM'); }); - expect(stats.compilation.warnings).toMatchSnapshot('warnings'); - expect(stats.compilation.errors).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); }); it(`should add nonce attribute when "injectType" option is "${injectType}" #2`, async () => { expect.assertions(3); - const testId = getTestId('nonce-import.js', injectType); - const stats = await compile(testId, { - loader: { options: { injectType } }, - }); + const entry = getEntryByInjectType('nonce-import.js', injectType); + const compiler = getCompiler(entry, { injectType }); + const stats = await compile(compiler); - runTestInJsdom(stats, (dom) => { + runInJsDom('main.bundle.js', compiler, stats, (dom) => { expect(dom.serialize()).toMatchSnapshot('DOM'); }); - expect(stats.compilation.warnings).toMatchSnapshot('warnings'); - expect(stats.compilation.errors).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); }); }); }); diff --git a/test/base-option.test.js b/test/base-option.test.js index ac5d1967..f76e9a2a 100644 --- a/test/base-option.test.js +++ b/test/base-option.test.js @@ -1,24 +1,23 @@ -import compile from './helpers/compiler'; -import runTestInJsdom from './helpers/runTestInJsdom'; +import { + compile, + getCompiler, + getErrors, + getWarnings, + runInJsDom, +} from './helpers/index'; describe('base option', () => { it('should work', async () => { expect.assertions(3); - const testId = './simple.js'; - const stats = await compile(testId, { - loader: { - options: { - base: 1000, - }, - }, - }); + const compiler = getCompiler('./simple.js', { base: 1000 }); + const stats = await compile(compiler); - runTestInJsdom(stats, (dom) => { + runInJsDom('main.bundle.js', compiler, stats, (dom) => { expect(dom.serialize()).toMatchSnapshot('DOM'); }); - expect(stats.compilation.warnings).toMatchSnapshot('warnings'); - expect(stats.compilation.errors).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); }); }); diff --git a/test/helpers/compile.js b/test/helpers/compile.js new file mode 100644 index 00000000..066873ab --- /dev/null +++ b/test/helpers/compile.js @@ -0,0 +1,11 @@ +export default (compiler) => { + return new Promise((resolve, reject) => { + compiler.run((error, stats) => { + if (error) { + return reject(error); + } + + return resolve(stats); + }); + }); +}; diff --git a/test/helpers/compiler.js b/test/helpers/compiler.js deleted file mode 100644 index 0f6ae6e2..00000000 --- a/test/helpers/compiler.js +++ /dev/null @@ -1,97 +0,0 @@ -/* eslint-disable - import/order, - multiline-ternary, - no-param-reassign, -*/ -import del from 'del'; -import path from 'path'; -import webpack from 'webpack'; -import { createFsFromVolume, Volume } from 'memfs'; - -const module = (config) => { - const shouldUseFileLoader = - config.loader && - config.loader.options && - config.loader.options.injectType === 'linkTag'; - - return { - rules: config.rules - ? config.rules - : [ - { - test: (config.loader && config.loader.test) || /\.css$/i, - use: [ - { - loader: path.join(__dirname, '../../src'), - options: (config.loader && config.loader.options) || {}, - }, - shouldUseFileLoader - ? { - loader: 'file-loader', - options: - (config.fileLoader && config.fileLoader.options) || {}, - } - : { - loader: 'css-loader', - options: - (config.cssLoader && config.cssLoader.options) || {}, - }, - ], - }, - ], - }; -}; - -const plugins = (config) => [].concat(config.plugins || []); - -const output = (config) => { - return { - path: path.resolve( - __dirname, - `../outputs/${config.output ? config.output : ''}` - ), - filename: '[name].bundle.js', - }; -}; - -export default function(fixture, config = {}, options = {}) { - // webpack Config - config = { - mode: config.mode || 'development', - devtool: config.devtool || false, - context: path.resolve(__dirname, '..', 'fixtures'), - entry: `./${fixture}`, - output: output(config), - module: module(config), - plugins: plugins(config), - optimization: { - runtimeChunk: false, - }, - }; - // Compiler Options - options = Object.assign({ output: false }, options); - - if (options.output) { - del.sync(config.output.path); - } - - const compiler = webpack(config); - - if (!options.output) { - const outputFileSystem = createFsFromVolume(new Volume()); - // Todo remove when we drop webpack@4 support - outputFileSystem.join = path.join.bind(path); - - compiler.outputFileSystem = outputFileSystem; - } - - return new Promise((resolve, reject) => - compiler.run((err, stats) => { - if (err) { - return reject(err); - } - - return resolve(stats); - }) - ); -} diff --git a/test/helpers/execute.js b/test/helpers/execute.js new file mode 100644 index 00000000..866001ae --- /dev/null +++ b/test/helpers/execute.js @@ -0,0 +1,22 @@ +import Module from 'module'; +import path from 'path'; + +const parentModule = module; + +export default (code) => { + const resource = 'test.js'; + const module = new Module(resource, parentModule); + // eslint-disable-next-line no-underscore-dangle + module.paths = Module._nodeModulePaths( + path.resolve(__dirname, '../fixtures') + ); + module.filename = resource; + + // eslint-disable-next-line no-underscore-dangle + module._compile( + `let __export__;${code};module.exports = __export__;`, + resource + ); + + return module.exports; +}; diff --git a/test/helpers/getCompiler.js b/test/helpers/getCompiler.js new file mode 100644 index 00000000..12622781 --- /dev/null +++ b/test/helpers/getCompiler.js @@ -0,0 +1,50 @@ +import path from 'path'; + +import webpack from 'webpack'; +import { createFsFromVolume, Volume } from 'memfs'; + +export default (fixture, loaderOptions = {}, config = {}) => { + const fullConfig = { + mode: 'development', + devtool: config.devtool || false, + context: path.resolve(__dirname, '../fixtures'), + entry: path.resolve(__dirname, '../fixtures', fixture), + output: { + path: path.resolve(__dirname, '../outputs'), + filename: '[name].bundle.js', + chunkFilename: '[name].chunk.js', + }, + module: { + rules: [ + { + test: /\.css$/i, + use: [ + { + loader: path.resolve(__dirname, '../../src'), + options: loaderOptions || {}, + }, + loaderOptions && + loaderOptions.injectType && + loaderOptions.injectType === 'linkTag' + ? { loader: 'file-loader' } + : { loader: 'css-loader' }, + ], + }, + ], + }, + plugins: [], + ...config, + }; + + const compiler = webpack(fullConfig); + + if (!config.outputFileSystem) { + const outputFileSystem = createFsFromVolume(new Volume()); + // Todo remove when we drop webpack@4 support + outputFileSystem.join = path.join.bind(path); + + compiler.outputFileSystem = outputFileSystem; + } + + return compiler; +}; diff --git a/test/helpers/getTestId.js b/test/helpers/getEntryByInjectType.js similarity index 58% rename from test/helpers/getTestId.js rename to test/helpers/getEntryByInjectType.js index 7f6489a3..18639195 100644 --- a/test/helpers/getTestId.js +++ b/test/helpers/getEntryByInjectType.js @@ -1,7 +1,7 @@ -function getTestId(testId, injectType) { +function getEntryByInjectType(testId, injectType) { const isLazy = injectType && injectType.toLowerCase().includes('lazy'); return `./${isLazy ? 'lazy-' : ''}${testId}`; } -export default getTestId; +export default getEntryByInjectType; diff --git a/test/helpers/getErrors.js b/test/helpers/getErrors.js new file mode 100644 index 00000000..716fbbb4 --- /dev/null +++ b/test/helpers/getErrors.js @@ -0,0 +1,5 @@ +import normalizeErrors from './normalizeErrors'; + +export default (stats) => { + return normalizeErrors(stats.compilation.errors); +}; diff --git a/test/helpers/getWarnings.js b/test/helpers/getWarnings.js new file mode 100644 index 00000000..c8a09d6d --- /dev/null +++ b/test/helpers/getWarnings.js @@ -0,0 +1,5 @@ +import normalizeErrors from './normalizeErrors'; + +export default (stats) => { + return normalizeErrors(stats.compilation.warnings); +}; diff --git a/test/helpers/index.js b/test/helpers/index.js index 6b078c24..8c65bc60 100644 --- a/test/helpers/index.js +++ b/test/helpers/index.js @@ -1,5 +1,23 @@ -import compile from './compiler'; -import getTestId from './getTestId'; -import runTestInJsdom from './runTestInJsdom'; +import compile from './compile'; +import execute from './execute'; +import getCompiler from './getCompiler'; +import getEntryByInjectType from './getEntryByInjectType'; +import getErrors from './getErrors'; +import getWarnings from './getWarnings'; +import normalizeErrors from './normalizeErrors'; +import readAsset from './readAsset'; +import readsAssets from './readAssets'; +import runInJsDom from './runInJsDom'; -export { compile, getTestId, runTestInJsdom }; +export { + compile, + execute, + getCompiler, + getEntryByInjectType, + getErrors, + getWarnings, + normalizeErrors, + readAsset, + readsAssets, + runInJsDom, +}; diff --git a/test/helpers/normalizeErrors.js b/test/helpers/normalizeErrors.js new file mode 100644 index 00000000..d540ed76 --- /dev/null +++ b/test/helpers/normalizeErrors.js @@ -0,0 +1,25 @@ +function removeCWD(str) { + const isWin = process.platform === 'win32'; + let cwd = process.cwd(); + + if (isWin) { + // eslint-disable-next-line no-param-reassign + str = str.replace(/\\/g, '/'); + // eslint-disable-next-line no-param-reassign + cwd = cwd.replace(/\\/g, '/'); + } + + return str.replace(new RegExp(cwd, 'g'), ''); +} + +export default (errors) => { + return errors.map((error) => + removeCWD( + error + .toString() + .split('\n') + .slice(0, 2) + .join('\n') + ) + ); +}; diff --git a/test/helpers/readAsset.js b/test/helpers/readAsset.js new file mode 100644 index 00000000..8f4699f0 --- /dev/null +++ b/test/helpers/readAsset.js @@ -0,0 +1,23 @@ +import path from 'path'; + +export default (asset, compiler, stats) => { + const usedFs = compiler.outputFileSystem; + const outputPath = stats.compilation.outputOptions.path; + + let data = ''; + let targetFile = asset; + + const queryStringIdx = targetFile.indexOf('?'); + + if (queryStringIdx >= 0) { + targetFile = targetFile.substr(0, queryStringIdx); + } + + try { + data = usedFs.readFileSync(path.join(outputPath, targetFile)).toString(); + } catch (error) { + data = error.toString(); + } + + return data; +}; diff --git a/test/helpers/readAssets.js b/test/helpers/readAssets.js new file mode 100644 index 00000000..a2fb7837 --- /dev/null +++ b/test/helpers/readAssets.js @@ -0,0 +1,11 @@ +import readAsset from './readAsset'; + +export default function readAssets(compiler, stats) { + const assets = {}; + + Object.keys(stats.compilation.assets).forEach((asset) => { + assets[asset] = readAsset(asset, compiler, stats); + }); + + return assets; +} diff --git a/test/helpers/runTestInJsdom.js b/test/helpers/runInJsDom.js similarity index 76% rename from test/helpers/runTestInJsdom.js rename to test/helpers/runInJsDom.js index 8b5faa06..088751fb 100644 --- a/test/helpers/runTestInJsdom.js +++ b/test/helpers/runInJsDom.js @@ -1,7 +1,9 @@ -const jsdom = require('jsdom'); +import jsdom from 'jsdom'; -function runTestInJsdom(stats, testFn) { - const bundle = stats.compilation.assets['main.bundle.js'].source(); +import { readAsset } from './index'; + +function runInJsDom(assetName, compiler, stats, testFn) { + const bundle = readAsset(assetName, compiler, stats); const virtualConsole = new jsdom.VirtualConsole(); virtualConsole.sendTo(console); @@ -39,4 +41,4 @@ function runTestInJsdom(stats, testFn) { } } -export default runTestInJsdom; +export default runInJsDom; diff --git a/test/injectType-option.test.js b/test/injectType-option.test.js index 7e6daa1a..26cec3df 100644 --- a/test/injectType-option.test.js +++ b/test/injectType-option.test.js @@ -1,6 +1,13 @@ /* eslint-env browser */ -import { compile, getTestId, runTestInJsdom } from './helpers'; +import { + compile, + getCompiler, + getEntryByInjectType, + getErrors, + getWarnings, + runInJsDom, +} from './helpers/index'; describe('injectType option', () => { const injectTypes = [ @@ -15,17 +22,16 @@ describe('injectType option', () => { it(`should work when the "injectType" option is "${injectType}"`, async () => { expect.assertions(3); - const testId = getTestId('simple.js', injectType); - const stats = await compile(testId, { - loader: { options: { injectType } }, - }); + const entry = getEntryByInjectType('simple.js', injectType); + const compiler = getCompiler(entry, { injectType }); + const stats = await compile(compiler); - runTestInJsdom(stats, (dom) => { + runInJsDom('main.bundle.js', compiler, stats, (dom) => { expect(dom.serialize()).toMatchSnapshot('DOM'); }); - expect(stats.compilation.warnings).toMatchSnapshot('warnings'); - expect(stats.compilation.errors).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); }); }); }); diff --git a/test/insert-option.test.js b/test/insert-option.test.js index c2f94f7f..7692ba86 100644 --- a/test/insert-option.test.js +++ b/test/insert-option.test.js @@ -1,5 +1,12 @@ /* eslint-env browser */ -import { compile, getTestId, runTestInJsdom } from './helpers'; +import { + compile, + getCompiler, + getEntryByInjectType, + getErrors, + getWarnings, + runInJsDom, +} from './helpers/index'; describe('insert option', () => { const injectTypes = [ @@ -14,61 +21,57 @@ describe('insert option', () => { it(`should insert styles into "head" bottom when not specified and when the "injectType" option is "${injectType}"`, async () => { expect.assertions(3); - const testId = getTestId('simple.js', injectType); - const stats = await compile(testId, { - loader: { options: { injectType } }, - }); + const entry = getEntryByInjectType('simple.js', injectType); + const compiler = getCompiler(entry, { injectType }); + const stats = await compile(compiler); - runTestInJsdom(stats, (dom) => { + runInJsDom('main.bundle.js', compiler, stats, (dom) => { expect(dom.serialize()).toMatchSnapshot('DOM'); }); - expect(stats.compilation.warnings).toMatchSnapshot('warnings'); - expect(stats.compilation.errors).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); }); it(`should insert styles into "body" bottom when the "injectType" option is "${injectType}"`, async () => { expect.assertions(3); - const testId = getTestId('simple.js', injectType); - const stats = await compile(testId, { - loader: { options: { injectType, insert: 'body' } }, - }); + const entry = getEntryByInjectType('simple.js', injectType); + const compiler = getCompiler(entry, { injectType, insert: 'body' }); + const stats = await compile(compiler); - runTestInJsdom(stats, (dom) => { + runInJsDom('main.bundle.js', compiler, stats, (dom) => { expect(dom.serialize()).toMatchSnapshot('DOM'); }); - expect(stats.compilation.warnings).toMatchSnapshot('warnings'); - expect(stats.compilation.errors).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); }); it(`should insert styles into "div.target" bottom when the "injectType" option is "${injectType}"`, async () => { expect.assertions(3); - const testId = getTestId('simple.js', injectType); - const stats = await compile(testId, { - loader: { options: { injectType, insert: 'div.target' } }, - }); + const entry = getEntryByInjectType('simple.js', injectType); + const compiler = getCompiler(entry, { injectType, insert: 'div.target' }); + const stats = await compile(compiler); - runTestInJsdom(stats, (dom) => { + runInJsDom('main.bundle.js', compiler, stats, (dom) => { expect(dom.serialize()).toMatchSnapshot('DOM'); }); - expect(stats.compilation.warnings).toMatchSnapshot('warnings'); - expect(stats.compilation.errors).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); }); it(`should insert styles into "iframe.iframeTarget" bottom when the "injectType" option is "${injectType}"`, async () => { expect.assertions(4); const selector = 'iframe.iframeTarget'; - const testId = getTestId('simple.js', injectType); - const stats = await compile(testId, { - loader: { options: { injectType, insert: selector } }, - }); + const entry = getEntryByInjectType('simple.js', injectType); + const compiler = getCompiler(entry, { injectType, insert: selector }); + const stats = await compile(compiler); - runTestInJsdom(stats, (dom) => { + runInJsDom('main.bundle.js', compiler, stats, (dom) => { expect( dom.window.document.querySelector(selector).contentDocument.head .innerHTML @@ -76,106 +79,97 @@ describe('insert option', () => { expect(dom.serialize()).toMatchSnapshot('DOM'); }); - expect(stats.compilation.warnings).toMatchSnapshot('warnings'); - expect(stats.compilation.errors).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); }); it(`should insert styles into runtime created element bottom when the "injectType" option is "${injectType}"`, async () => { expect.assertions(3); - const testId = getTestId('element.js', injectType); - const stats = await compile(testId, { - loader: { - options: { - injectType, - insert: (element) => - document.querySelector('#test-shadow').appendChild(element), - }, - }, + const entry = getEntryByInjectType('element.js', injectType); + const compiler = getCompiler(entry, { + injectType, + insert: (element) => + document.querySelector('#test-shadow').appendChild(element), }); + const stats = await compile(compiler); - runTestInJsdom(stats, (dom) => { + runInJsDom('main.bundle.js', compiler, stats, (dom) => { expect(dom.serialize()).toMatchSnapshot('DOM'); }); - expect(stats.compilation.warnings).toMatchSnapshot('warnings'); - expect(stats.compilation.errors).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); }); it(`should insert styles into "head" top when the "injectType" option is "${injectType}"`, async () => { expect.assertions(3); - const testId = getTestId('simple.js', injectType); - const stats = await compile(testId, { - loader: { - options: { - injectType, - insert: (element) => { - const parent = document.querySelector('head'); - const lastInsertedElement = - // eslint-disable-next-line no-underscore-dangle - window._lastElementInsertedByStyleLoader; - - if (!lastInsertedElement) { - parent.insertBefore(element, parent.firstChild); - } else if (lastInsertedElement.nextSibling) { - parent.insertBefore(element, lastInsertedElement.nextSibling); - } else { - parent.appendChild(element); - } - - // eslint-disable-next-line no-underscore-dangle - window._lastElementInsertedByStyleLoader = element; - }, - }, + const entry = getEntryByInjectType('simple.js', injectType); + const compiler = getCompiler(entry, { + injectType, + insert: (element) => { + const parent = document.querySelector('head'); + const lastInsertedElement = + // eslint-disable-next-line no-underscore-dangle + window._lastElementInsertedByStyleLoader; + + if (!lastInsertedElement) { + parent.insertBefore(element, parent.firstChild); + } else if (lastInsertedElement.nextSibling) { + parent.insertBefore(element, lastInsertedElement.nextSibling); + } else { + parent.appendChild(element); + } + + // eslint-disable-next-line no-underscore-dangle + window._lastElementInsertedByStyleLoader = element; }, }); + const stats = await compile(compiler); - runTestInJsdom(stats, (dom) => { + runInJsDom('main.bundle.js', compiler, stats, (dom) => { expect(dom.serialize()).toMatchSnapshot('DOM'); }); - expect(stats.compilation.warnings).toMatchSnapshot('warnings'); - expect(stats.compilation.errors).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); }); it(`should insert styles into before "#existing-style" id when the "injectType" option is "${injectType}"`, async () => { expect.assertions(3); - const testId = getTestId('simple.js', injectType); - const stats = await compile(testId, { - loader: { - options: { - injectType, - insert: (element) => { - const parent = document.querySelector('head'); - const target = document.querySelector('#existing-style'); - - const lastInsertedElement = - // eslint-disable-next-line no-underscore-dangle - window._lastElementInsertedByStyleLoader; - - if (!lastInsertedElement) { - parent.insertBefore(element, target); - } else if (lastInsertedElement.nextSibling) { - parent.insertBefore(element, lastInsertedElement.nextSibling); - } else { - parent.appendChild(element); - } - - // eslint-disable-next-line no-underscore-dangle - window._lastElementInsertedByStyleLoader = element; - }, - }, + const entry = getEntryByInjectType('simple.js', injectType); + const compiler = getCompiler(entry, { + injectType, + insert: (element) => { + const parent = document.querySelector('head'); + const target = document.querySelector('#existing-style'); + + const lastInsertedElement = + // eslint-disable-next-line no-underscore-dangle + window._lastElementInsertedByStyleLoader; + + if (!lastInsertedElement) { + parent.insertBefore(element, target); + } else if (lastInsertedElement.nextSibling) { + parent.insertBefore(element, lastInsertedElement.nextSibling); + } else { + parent.appendChild(element); + } + + // eslint-disable-next-line no-underscore-dangle + window._lastElementInsertedByStyleLoader = element; }, }); + const stats = await compile(compiler); - runTestInJsdom(stats, (dom) => { + runInJsDom('main.bundle.js', compiler, stats, (dom) => { expect(dom.serialize()).toMatchSnapshot('DOM'); }); - expect(stats.compilation.warnings).toMatchSnapshot('warnings'); - expect(stats.compilation.errors).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); }); }); }); diff --git a/test/loader.test.js b/test/loader.test.js index 1ebcd394..ccac85a2 100644 --- a/test/loader.test.js +++ b/test/loader.test.js @@ -1,8 +1,18 @@ /* eslint-env browser */ +import path from 'path'; + import webpack from 'webpack'; -import { compile, getTestId, runTestInJsdom } from './helpers'; +import { + compile, + getCompiler, + getEntryByInjectType, + getErrors, + getWarnings, + readAsset, + runInJsDom, +} from './helpers/index'; describe('loader', () => { const injectTypes = [ @@ -14,217 +24,284 @@ describe('loader', () => { ]; it('should work', async () => { - const testId = './simple.js'; - const stats = await compile(testId); - - runTestInJsdom(stats, (dom) => { - expect(dom.serialize()).toMatchSnapshot('DOM'); - }); - - expect(stats.compilation.warnings).toMatchSnapshot('warnings'); - expect(stats.compilation.errors).toMatchSnapshot('errors'); - }); - - it('should work when the "injectType" option is "linkTag" and "file-loader" uses ES module syntax', async () => { - const testId = './simple.js'; - const stats = await compile(testId, { - loader: { options: { injectType: 'linkTag' } }, - fileLoader: { options: { esModule: true } }, - }); - - runTestInJsdom(stats, (dom) => { - expect(dom.serialize()).toMatchSnapshot('DOM'); - }); + const compiler = getCompiler('./simple.js'); + const stats = await compile(compiler); - expect(stats.compilation.warnings).toMatchSnapshot('warnings'); - expect(stats.compilation.errors).toMatchSnapshot('errors'); - }); - - it('should work when the "injectType" option is "linkTag" and "file-loader" uses CommonJS module syntax', async () => { - const testId = './simple.js'; - const stats = await compile(testId, { - loader: { options: { injectType: 'linkTag' } }, - fileLoader: { options: { esModule: false } }, - }); - - runTestInJsdom(stats, (dom) => { + runInJsDom('main.bundle.js', compiler, stats, (dom) => { expect(dom.serialize()).toMatchSnapshot('DOM'); }); - expect(stats.compilation.warnings).toMatchSnapshot('warnings'); - expect(stats.compilation.errors).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); }); injectTypes.forEach((injectType) => { it(`should work when the "injectType" option is "${injectType}"`, async () => { expect.assertions(3); - const testId = getTestId('simple.js', injectType); - const stats = await compile(testId, { - loader: { options: { injectType } }, - }); + const entry = getEntryByInjectType('simple.js', injectType); + const compiler = getCompiler(entry, { injectType }); + const stats = await compile(compiler); - runTestInJsdom(stats, (dom) => { + runInJsDom('main.bundle.js', compiler, stats, (dom) => { expect(dom.serialize()).toMatchSnapshot('DOM'); }); - expect(stats.compilation.warnings).toMatchSnapshot('warnings'); - expect(stats.compilation.errors).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); }); it(`should work with css modules when the "injectType" option is "${injectType}"`, async () => { expect.assertions(3); - const testId = getTestId('css-modules.js', injectType); - const stats = await compile(testId, { - loader: { options: { injectType } }, - cssLoader: { - options: { - modules: { localIdentName: '[name]-[local]_[hash:base64:7]' }, + const entry = getEntryByInjectType('css-modules.js', injectType); + const compiler = getCompiler( + entry, + {}, + { + module: { + rules: [ + { + test: /\.css$/i, + use: [ + { + loader: path.resolve(__dirname, '../src'), + options: { injectType }, + }, + injectType === 'linkTag' + ? { loader: 'file-loader' } + : { + loader: 'css-loader', + options: { + modules: { + localIdentName: '[name]-[local]_[hash:base64:7]', + }, + }, + }, + ], + }, + ], }, - }, - }); + } + ); + const stats = await compile(compiler); - runTestInJsdom(stats, (dom) => { + runInJsDom('main.bundle.js', compiler, stats, (dom) => { expect(dom.serialize()).toMatchSnapshot('DOM'); }); - expect(stats.compilation.warnings).toMatchSnapshot('warnings'); - expect(stats.compilation.errors).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); }); it(`should not inject hmr code without HotModuleReplacementPlugin when the "injectType" option is "${injectType}"`, async () => { expect.assertions(4); - const testId = './hot.js'; - const stats = await compile(testId, { - loader: { options: { injectType } }, - }); + const compiler = getCompiler('./hot.js', { injectType }); + const stats = await compile(compiler); - runTestInJsdom(stats, (dom) => { + runInJsDom('main.bundle.js', compiler, stats, (dom) => { expect(dom.window.hotApi).not.toBeDefined(); }); - const bundleSource = stats.compilation.assets['main.bundle.js'].source(); + const bundleSource = readAsset('main.bundle.js', compiler, stats); expect(bundleSource).not.toMatch(/module\.hot\.accept/); - - expect(stats.compilation.warnings).toMatchSnapshot('warnings'); - expect(stats.compilation.errors).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); }); it(`should inject hmr code with HotModuleReplacementPlugin when the "injectType" option is "${injectType}"`, async () => { expect.assertions(4); - const testId = './hot.js'; - const stats = await compile(testId, { - plugins: [new webpack.HotModuleReplacementPlugin()], - loader: { options: { injectType } }, - }); + const compiler = getCompiler( + './hot.js', + { injectType }, + { plugins: [new webpack.HotModuleReplacementPlugin()] } + ); + const stats = await compile(compiler); - runTestInJsdom(stats, (dom) => { + runInJsDom('main.bundle.js', compiler, stats, (dom) => { expect(dom.window.hotApi).toBeDefined(); }); - const bundleSource = stats.compilation.assets['main.bundle.js'].source(); + const bundleSource = readAsset('main.bundle.js', compiler, stats); expect(bundleSource).toMatch(/module\.hot\.accept/); - - expect(stats.compilation.warnings).toMatchSnapshot('warnings'); - expect(stats.compilation.errors).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); }); it(`should not generate source maps when previous loader don't emit them when the "injectType" option is "${injectType}"`, async () => { expect.assertions(3); - const testId = getTestId('simple.js', injectType); - const stats = await compile(testId, { - devtool: 'source-map', - loader: { options: { injectType } }, - }); + const entry = getEntryByInjectType('simple.js', injectType); + const compiler = getCompiler( + entry, + { injectType }, + { + devtool: 'source-map', + module: { + rules: [ + { + test: /\.css$/i, + use: [ + { + loader: path.resolve(__dirname, '../src'), + options: { injectType }, + }, + injectType === 'linkTag' + ? { loader: 'file-loader' } + : { + loader: 'css-loader', + options: { sourceMap: false }, + }, + ], + }, + ], + }, + } + ); + const stats = await compile(compiler); - runTestInJsdom(stats, (dom) => { + runInJsDom('main.bundle.js', compiler, stats, (dom) => { expect(dom.serialize()).toMatchSnapshot('DOM'); }); - expect(stats.compilation.warnings).toMatchSnapshot('warnings'); - expect(stats.compilation.errors).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); }); // `linkTag` doesn't generate source maps, original source should contains them it(`should generate source maps when previous loader emit them when the "injectType" option is "${injectType}"`, async () => { expect.assertions(3); - const testId = getTestId('simple.js', injectType); - const stats = await compile(testId, { - devtool: 'source-map', - loader: { options: { injectType } }, - cssLoader: { options: { sourceMap: true } }, + const entry = getEntryByInjectType('simple.js', injectType); + const compiler = getCompiler( + entry, + { injectType }, + { + devtool: 'source-map', + module: { + rules: [ + { + test: /\.css$/i, + use: [ + { + loader: path.resolve(__dirname, '../src'), + options: { injectType }, + }, + injectType === 'linkTag' + ? { loader: 'file-loader' } + : { + loader: 'css-loader', + options: { sourceMap: true }, + }, + ], + }, + ], + }, + } + ); + const stats = await compile(compiler); + + runInJsDom('main.bundle.js', compiler, stats, (dom) => { + expect(dom.serialize()).toMatchSnapshot('DOM'); }); - runTestInJsdom(stats, (dom) => { + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + }); + + it(`should work when the "injectType" option is "${injectType}" and ES module syntax used`, async () => { + const entry = getEntryByInjectType('simple.js', injectType); + const compiler = getCompiler( + entry, + { injectType }, + { + module: { + rules: [ + { + test: /\.css$/i, + use: [ + { + loader: path.resolve(__dirname, '../src'), + options: { injectType }, + }, + injectType === 'linkTag' + ? { loader: 'file-loader' } + : { + loader: 'css-loader', + options: { esModule: true }, + }, + ], + }, + ], + }, + } + ); + const stats = await compile(compiler); + + runInJsDom('main.bundle.js', compiler, stats, (dom) => { expect(dom.serialize()).toMatchSnapshot('DOM'); }); - expect(stats.compilation.warnings).toMatchSnapshot('warnings'); - expect(stats.compilation.errors).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); }); - // Uncomment after `css-loader` release the `esModule` option - // if ( - // [ - // 'styleTag', - // 'singletonStyleTag', - // 'lazyStyleTag', - // 'lazySingletonStyleTag', - // ].includes(injectType) - // ) { - // it(`should work when the "injectType" option is "${injectType}" and "css-loader" uses ES module syntax`, async () => { - // const testId = getTestId('simple.js', injectType); - // const stats = await compile(testId, { - // loader: { options: { injectType } }, - // cssLoader: { options: { esModule: true } }, - // }); - // - // runTestInJsdom(stats, (dom) => { - // expect(dom.serialize()).toMatchSnapshot('DOM'); - // }); - // - // expect(stats.compilation.warnings).toMatchSnapshot('warnings'); - // expect(stats.compilation.errors).toMatchSnapshot('errors'); - // }); - // - // it(`should work when the "injectType" option is "${injectType}" and "css-loader" uses CommonJS module syntax`, async () => { - // const testId = getTestId('simple.js', injectType); - // const stats = await compile(testId, { - // loader: { options: { injectType } }, - // cssLoader: { options: { esModule: true } }, - // }); - // - // runTestInJsdom(stats, (dom) => { - // expect(dom.serialize()).toMatchSnapshot('DOM'); - // }); - // - // expect(stats.compilation.warnings).toMatchSnapshot('warnings'); - // expect(stats.compilation.errors).toMatchSnapshot('errors'); - // }); - // } + it(`should work when the "injectType" option is "${injectType}" and CommonJS module syntax used`, async () => { + const entry = getEntryByInjectType('simple.js', injectType); + const compiler = getCompiler( + entry, + { injectType }, + { + module: { + rules: [ + { + test: /\.css$/i, + use: [ + { + loader: path.resolve(__dirname, '../src'), + options: { injectType }, + }, + injectType === 'linkTag' + ? { loader: 'file-loader' } + : { + loader: 'css-loader', + options: { esModule: false }, + }, + ], + }, + ], + }, + } + ); + const stats = await compile(compiler); + + runInJsDom('main.bundle.js', compiler, stats, (dom) => { + expect(dom.serialize()).toMatchSnapshot('DOM'); + }); + + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + }); if (['lazyStyleTag', 'lazySingletonStyleTag'].includes(injectType)) { it(`should work when ref is negative when the "injectType" option is "${injectType}"`, async () => { expect.assertions(3); - const testId = './lazy-negative-refs.js'; - const stats = await compile(testId, { - loader: { options: { injectType } }, + const compiler = getCompiler('./lazy-negative-refs.js', { + injectType, }); + const stats = await compile(compiler); - runTestInJsdom(stats, (dom) => { + runInJsDom('main.bundle.js', compiler, stats, (dom) => { expect(dom.serialize()).toMatchSnapshot('DOM'); }); - expect(stats.compilation.warnings).toMatchSnapshot('warnings'); - expect(stats.compilation.errors).toMatchSnapshot('errors'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); }); } }); diff --git a/test/validate-options.test.js b/test/validate-options.test.js index a15732ea..940cb836 100644 --- a/test/validate-options.test.js +++ b/test/validate-options.test.js @@ -1,46 +1,74 @@ -import loader from '../src/index'; - -it('validate options', () => { - const validate = (options) => - loader.pitch.call( - Object.assign( - {}, - { - query: options, - loaders: [], - remainingRequest: 'file.css', - currentRequest: 'file.css', - async: () => (error) => { - if (error) { - throw error; - } - }, +import { getCompiler, compile } from './helpers'; + +describe('validate options', () => { + const tests = { + injectType: { + success: [ + 'styleTag', + 'singletonStyleTag', + 'lazyStyleTag', + 'lazySingletonStyleTag', + 'linkTag', + ], + failure: ['unknown'], + }, + attributes: { + success: [{}, { id: 'id' }], + failure: [true], + }, + insert: { + success: ['selector', () => {}], + failure: [true], + }, + unknown: { + success: [], + failure: [1, true, false, 'test', /test/, [], {}, { foo: 'bar' }], + }, + }; + + function stringifyValue(value) { + if ( + Array.isArray(value) || + (value && typeof value === 'object' && value.constructor === Object) + ) { + return JSON.stringify(value); + } + + return value; + } + + async function createTestCase(key, value, type) { + it(`should ${ + type === 'success' ? 'successfully validate' : 'throw an error on' + } the "${key}" option with "${stringifyValue(value)}" value`, async () => { + const compiler = getCompiler('simple.js', { [key]: value }); + + let stats; + + try { + stats = await compile(compiler); + } finally { + if (type === 'success') { + expect(stats.hasErrors()).toBe(false); + } else if (type === 'failure') { + const { + compilation: { errors }, + } = stats; + + expect(errors).toHaveLength(2); + expect(() => { + throw new Error(errors[0].error.message); + }).toThrowErrorMatchingSnapshot(); } - ), - 'file.css' - ); - - expect(() => validate({ injectType: 'styleTag' })).not.toThrow(); - expect(() => validate({ injectType: 'singletonStyleTag' })).not.toThrow(); - expect(() => validate({ injectType: 'lazyStyleTag' })).not.toThrow(); - expect(() => validate({ injectType: 'lazySingletonStyleTag' })).not.toThrow(); - expect(() => validate({ injectType: 'linkTag' })).not.toThrow(); - expect(() => - validate({ injectType: 'unknown' }) - ).toThrowErrorMatchingSnapshot(); - - expect(() => validate({ attributes: {} })).not.toThrow(); - expect(() => validate({ attributes: { id: 'id' } })).not.toThrow(); - expect(() => validate({ attributes: true })).toThrowErrorMatchingSnapshot(); - - expect(() => - validate({ - // eslint-disable-next-line no-undef - insert: (element) => document.querySelector('#root').appendChild(element), - }) - ).not.toThrow(); - expect(() => validate({ insert: 'test' })).not.toThrow(); - expect(() => validate({ insert: true })).toThrowErrorMatchingSnapshot(); - - expect(() => validate({ unknown: 'unknown' })).toThrowErrorMatchingSnapshot(); + } + }); + } + + for (const [key, values] of Object.entries(tests)) { + for (const type of Object.keys(values)) { + for (const value of values[type]) { + createTestCase(key, value, type); + } + } + } });