From 25a16a0d395923925ab5a049c5626ebad589da1b Mon Sep 17 00:00:00 2001 From: Alexander Krasnoyarov Date: Fri, 4 Sep 2020 20:48:57 +0300 Subject: [PATCH] refactor: source map code BREAKING CHANGE: `sources` in source maps are absolute --- package-lock.json | 17 +- package.json | 3 +- src/index.js | 59 +- src/options.json | 12 +- src/utils.js | 325 ++++------ test/__snapshots__/loader.test.js.snap | 141 +++- .../validate-options.test.js.snap | 49 +- test/fixtures/config-scope/config/plugin.js | 2 +- .../config-scope/config/postcss.config.js | 1 - test/fixtures/css/index2.js | 3 - test/fixtures/css/plugins.config.js | 4 +- test/fixtures/css/style.css | 44 +- test/fixtures/css/style2.css | 4 - test/fixtures/plugin/other-plugin.js | 15 + test/fixtures/plugin/plugin.js | 15 + test/helpers/getCodeFromBundle.js | 8 +- test/helpers/normalizeMap.js | 26 - test/loader.test.js | 4 - .../options/__snapshots__/config.test.js.snap | 60 +- .../__snapshots__/plugins.test.js.snap | 336 ++++++++-- .../__snapshots__/postcssOptins.test.js.snap | 259 +++++++- .../__snapshots__/sourceMap.test.js.snap | 609 ++++++++++++++++-- .../__snapshots__/stringifier.test.js.snap | 110 +++- test/options/plugins.test.js | 166 ++--- test/options/postcssOptins.test.js | 59 +- test/options/sourceMap.test.js | 234 +++++-- test/validate-options.test.js | 18 +- 27 files changed, 1988 insertions(+), 595 deletions(-) delete mode 100644 test/fixtures/css/index2.js delete mode 100644 test/fixtures/css/style2.css create mode 100644 test/fixtures/plugin/other-plugin.js create mode 100644 test/fixtures/plugin/plugin.js delete mode 100644 test/helpers/normalizeMap.js diff --git a/package-lock.json b/package-lock.json index 2a7113f1..b65195f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10702,9 +10702,9 @@ "dev": true }, "klona": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/klona/-/klona-1.1.2.tgz", - "integrity": "sha512-xf88rTeHiXk+XE2Vhi6yj8Wm3gMZrygGdKjJqN8HkV+PwF/t50/LdAKHoHpPcxFAlmQszTZ1CugrK25S7qDRLA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.3.tgz", + "integrity": "sha512-CgPOT3ZadDpXxKcfV56lEQ9OQSZ42Mk26gnozI+uN/k39vzD8toUhRQoqsX0m9Q3eMPEfsLWmtyUpK/yqST4yg==", "dev": true }, "less": { @@ -12246,7 +12246,8 @@ "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true }, "normalize-url": { "version": "3.3.0", @@ -14465,12 +14466,12 @@ } }, "sass-loader": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-9.0.3.tgz", - "integrity": "sha512-fOwsP98ac1VMme+V3+o0HaaMHp8Q/C9P+MUazLFVi3Jl7ORGHQXL1XeRZt3zLSGZQQPC8xE42Y2WptItvGjDQg==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-10.0.1.tgz", + "integrity": "sha512-b2PSldKVTS3JcFPHSrEXh3BeAfR7XknGiGCAO5aHruR3Pf3kqLP3Gb2ypXLglRrAzgZkloNxLZ7GXEGDX0hBUQ==", "dev": true, "requires": { - "klona": "^1.1.2", + "klona": "^2.0.3", "loader-utils": "^2.0.0", "neo-async": "^2.6.2", "schema-utils": "^2.7.0", diff --git a/package.json b/package.json index 327dff0c..f1f52ea5 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,6 @@ "dependencies": { "cosmiconfig": "^7.0.0", "loader-utils": "^2.0.0", - "normalize-path": "^3.0.0", "postcss": "^7.0.0", "schema-utils": "^2.7.0" }, @@ -80,7 +79,7 @@ "postcss-short": "^5.0.0", "prettier": "^2.0.5", "sass": "^1.26.10", - "sass-loader": "^9.0.3", + "sass-loader": "^10.0.1", "standard": "^14.3.4", "standard-version": "^8.0.2", "strip-ansi": "^6.0.0", diff --git a/src/index.js b/src/index.js index 19f48ad1..e306c266 100644 --- a/src/index.js +++ b/src/index.js @@ -12,9 +12,8 @@ import { loadConfig, getPostcssOptions, exec, - getSourceMapAbsolutePath, - getSourceMapRelativePath, normalizeSourceMap, + normalizeSourceMapAfterPostcss, } from './utils'; /** @@ -38,43 +37,40 @@ export default async function loader(content, sourceMap, meta = {}) { baseDataPath: 'options', }); - const file = this.resourcePath; - const configOptions = + const callback = this.async(); + const configOption = typeof options.postcssOptions === 'undefined' || typeof options.postcssOptions.config === 'undefined' ? true : options.postcssOptions.config; - let loadedConfig = {}; - const callback = this.async(); - - if (configOptions) { + if (configOption) { const dataForLoadConfig = { - path: path.dirname(file), + path: path.dirname(this.resourcePath), ctx: { file: { - extname: path.extname(file), - dirname: path.dirname(file), - basename: path.basename(file), + extname: path.extname(this.resourcePath), + dirname: path.dirname(this.resourcePath), + basename: path.basename(this.resourcePath), }, options: {}, }, }; - if (typeof configOptions.path !== 'undefined') { - dataForLoadConfig.path = path.resolve(configOptions.path); + if (typeof configOption.path !== 'undefined') { + dataForLoadConfig.path = path.resolve(configOption.path); } - if (typeof configOptions.ctx !== 'undefined') { - dataForLoadConfig.ctx.options = configOptions.ctx; + if (typeof configOption.ctx !== 'undefined') { + dataForLoadConfig.ctx.options = configOption.ctx; } dataForLoadConfig.ctx.webpack = this; try { loadedConfig = await loadConfig( - configOptions, + configOption, dataForLoadConfig.ctx, dataForLoadConfig.path, this @@ -104,18 +100,12 @@ export default async function loader(content, sourceMap, meta = {}) { if (useSourceMap) { processOptions.map = { inline: false, annotation: false }; - // options.sourceMap === 'inline' - // ? { inline: true, annotation: false } - // : { inline: false, annotation: false }; if (sourceMap) { - const sourceMapNormalized = normalizeSourceMap(sourceMap); - - sourceMapNormalized.sources = sourceMapNormalized.sources.map((src) => - getSourceMapRelativePath(src, path.dirname(file)) + processOptions.map.prev = normalizeSourceMap( + sourceMap, + this.resourcePath ); - - processOptions.map.prev = sourceMapNormalized; } } @@ -137,11 +127,11 @@ export default async function loader(content, sourceMap, meta = {}) { return; } - result.warnings().forEach((warning) => { + for (const warning of result.warnings()) { this.emitWarning(new Warning(warning)); - }); + } - result.messages.forEach((message) => { + for (const message of result.messages) { if (message.type === 'dependency') { this.addDependency(message.file); } @@ -154,16 +144,13 @@ export default async function loader(content, sourceMap, meta = {}) { message.info ); } - }); + } - const map = result.map ? result.map.toJSON() : null; + // eslint-disable-next-line no-undefined + let map = result.map ? result.map.toJSON() : undefined; if (map && useSourceMap) { - if (typeof map.file !== 'undefined') { - delete map.file; - } - - map.sources = map.sources.map((src) => getSourceMapAbsolutePath(src, file)); + map = normalizeSourceMapAfterPostcss(map, this.resourcePath); } const ast = { diff --git a/src/options.json b/src/options.json index d771823a..193c4aa4 100644 --- a/src/options.json +++ b/src/options.json @@ -75,12 +75,8 @@ ] }, "plugins": { - "description": "Set PostCSS Plugins (https://github.com/postcss/postcss-loader#plugins)", - "anyOf": [ - { "type": "array" }, - { "type": "object" }, - { "instanceof": "Function" } - ] + "description": "Sets PostCSS Plugins (https://github.com/postcss/postcss-loader#plugins)", + "anyOf": [{ "type": "array" }, { "type": "object" }] } } }, @@ -90,7 +86,7 @@ ] }, "exec": { - "description": "Enable PostCSS Parser support in 'CSS-in-JS' (https://github.com/postcss/postcss-loader#exec)", + "description": "Enables/Disables PostCSS Parser support in 'CSS-in-JS' (https://github.com/postcss/postcss-loader#exec)", "type": "boolean" }, "sourceMap": { @@ -98,5 +94,5 @@ "type": "boolean" } }, - "additionalProperties": true + "additionalProperties": false } diff --git a/src/utils.js b/src/utils.js index b1860461..acd169ac 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,10 +1,6 @@ import path from 'path'; - import Module from 'module'; -import normalizePath from 'normalize-path'; - -import postcssPkg from 'postcss/package.json'; import { cosmiconfig } from 'cosmiconfig'; const parentModule = module; @@ -34,63 +30,6 @@ const createContext = (context) => { return result; }; -const load = (plugin, options, file) => { - try { - if ( - options === null || - typeof options === 'undefined' || - Object.keys(options).length === 0 - ) { - // eslint-disable-next-line global-require,import/no-dynamic-require - return require(plugin); - } - - // eslint-disable-next-line global-require,import/no-dynamic-require - return require(plugin)(options); - } catch (error) { - throw new Error( - `Loading PostCSS Plugin failed: ${error.message}\n\n(@${file})` - ); - } -}; - -function loadPlugins(pluginEntry, file) { - const plugins = Object.entries(pluginEntry).filter((i) => { - const [, options] = i; - - return options !== false ? pluginEntry : ''; - }); - - const loadedPlugins = plugins.map((plugin) => { - const [pluginName, pluginOptions] = plugin; - - return load(pluginName, pluginOptions, file); - }); - - if (loadedPlugins.length && loadedPlugins.length > 0) { - loadedPlugins.forEach((plugin, i) => { - if (plugin.default) { - // eslint-disable-next-line no-param-reassign - plugin = plugin.default; - } - - if ( - // eslint-disable-next-line - !( - (typeof plugin === 'object' && Array.isArray(plugin.plugins)) || - typeof plugin === 'function' - ) - ) { - throw new TypeError( - `Invalid PostCSS Plugin found at: plugins[${i}]\n\n(@${file})` - ); - } - }); - } - - return loadedPlugins; -} - function exec(code, loaderContext) { const { resource, context } = loaderContext; @@ -154,29 +93,109 @@ async function loadConfig(config, context, configPath, loaderContext) { return resultConfig; } -function getPostcssOptions(loaderContext, config, options = {}) { - let plugins = []; +function loadPlugin(plugin, options, file) { + // TODO defaults + try { + if (!options || Object.keys(options).length === 0) { + // eslint-disable-next-line global-require,import/no-dynamic-require + return require(plugin); + } - const disabledPlugins = []; + // eslint-disable-next-line global-require,import/no-dynamic-require + return require(plugin)(options); + } catch (error) { + throw new Error( + `Loading PostCSS Plugin failed: ${error.message}\n\n(@${file})` + ); + } +} + +function pluginFactory() { + const listOfPlugins = new Map(); + + return (plugins) => { + if (typeof plugins === 'undefined') { + return listOfPlugins; + } + + if (Array.isArray(plugins)) { + for (const plugin of plugins) { + if (Array.isArray(plugin)) { + const [name, options] = plugin; + + listOfPlugins.set(name, options); + } else if ( + plugin && + Object.keys(plugin).length === 1 && + typeof plugin[Object.keys(plugin)[0]] === 'object' && + plugin[Object.keys(plugin)[0]] !== null + ) { + const [name] = Object.keys(plugin); + const options = plugin[name]; + + if (options === false) { + listOfPlugins.delete(name); + } else { + listOfPlugins.set(name, options); + } + } else if (plugin) { + listOfPlugins.set(plugin); + } + } + } else { + const objectPlugins = Object.entries(plugins); + + for (const [name, options] of objectPlugins) { + if (options === false) { + listOfPlugins.delete(name); + } else { + listOfPlugins.set(name, options); + } + } + } + + return listOfPlugins; + }; +} + +function getPostcssOptions(loaderContext, config, postcssOptions = {}) { const file = loaderContext.resourcePath; + let normalizedPostcssOptions = postcssOptions; + + if (typeof normalizedPostcssOptions === 'function') { + normalizedPostcssOptions = normalizedPostcssOptions(loaderContext); + } + + let plugins = []; + try { - plugins = [ - ...getArrayPlugins(config.plugins, file, false, loaderContext), - ...getArrayPlugins(options.plugins, file, disabledPlugins, loaderContext), - ].filter((i) => !disabledPlugins.includes(i.postcssPlugin)); + const factory = pluginFactory(); + + factory(config.plugins); + factory(normalizedPostcssOptions.plugins); + + plugins = [...factory()].map((item) => { + const [plugin, options] = item; + + if (typeof plugin === 'string') { + return loadPlugin(plugin, options, file); + } + + return plugin; + }); } catch (error) { loaderContext.emitError(error); } const processOptionsFromConfig = { ...config }; - // No need them + // No need them for processOptions delete processOptionsFromConfig.plugins; - const processOptionsFromOptions = { ...options }; + const processOptionsFromOptions = { ...normalizedPostcssOptions }; - // No need them + // No need them for processOptions delete processOptionsFromOptions.config; delete processOptionsFromOptions.plugins; @@ -238,95 +257,26 @@ function getPostcssOptions(loaderContext, config, options = {}) { return { plugins, processOptions, needExecute }; } -function getPlugin(pluginEntry) { - if (!pluginEntry) { - return []; - } +const IS_NATIVE_WIN32_PATH = /^[a-z]:[/\\]|^\\\\/i; +const ABSOLUTE_SCHEME = /^[a-z0-9+\-.]+:/i; - if (isPostcssPlugin(pluginEntry)) { - return [pluginEntry]; - } - - const result = pluginEntry(); - - return Array.isArray(result) ? result : [result]; -} - -function isPostcssPlugin(plugin) { - return plugin.postcssVersion === postcssPkg.version; -} - -function pluginsProcessing(plugins, file, disabledPlugins) { - if (Array.isArray(plugins)) { - return plugins.reduce((accumulator, plugin) => { - let normalizedPlugin = plugin; - - if (Array.isArray(plugin)) { - const [name] = plugin; - let [, options] = plugin; - - options = options || {}; - - normalizedPlugin = { [name]: options }; - } - - if (typeof plugin === 'string') { - normalizedPlugin = { [plugin]: {} }; - } - - // eslint-disable-next-line no-param-reassign - accumulator = accumulator.concat( - pluginsProcessing(normalizedPlugin, file, disabledPlugins) - ); - - return accumulator; - }, []); - } - - if (typeof plugins === 'object') { - if (Object.keys(plugins).length === 0) { - return []; +function getURLType(source) { + if (source[0] === '/') { + if (source[1] === '/') { + return 'scheme-relative'; } - const statePlagins = { - enabled: {}, - disabled: disabledPlugins || [], - }; - - Object.entries(plugins).forEach((plugin) => { - const [name, options] = plugin; - - if (options === false) { - statePlagins.disabled.push(name); - } else { - statePlagins.enabled[name] = options; - } - }); - - return pluginsProcessing( - loadPlugins(statePlagins.enabled, file), - file, - disabledPlugins - ); + return 'path-absolute'; } - return getPlugin(plugins); -} - -function getArrayPlugins(plugins, file, disabledPlugins, loaderContext) { - if (typeof plugins === 'function') { - if (isPostcssPlugin(plugins)) { - return [plugins]; - } - - return pluginsProcessing(plugins(loaderContext), file, disabledPlugins); + if (IS_NATIVE_WIN32_PATH.test(source)) { + return 'path-absolute'; } - return pluginsProcessing(plugins, file, disabledPlugins); + return ABSOLUTE_SCHEME.test(source) ? 'absolute' : 'path-relative'; } -// TODO Remove, when postcss 8 will be released -function normalizeSourceMap(map) { +function normalizeSourceMap(map, resourcePath) { let newMap = map; // Some loader emit source map as string @@ -335,66 +285,69 @@ function normalizeSourceMap(map) { newMap = JSON.parse(newMap); } - // Source maps should use forward slash because it is URLs (https://github.com/mozilla/source-map/issues/91) - // We should normalize path because previous loaders like `sass-loader` using backslash when generate source map - - if (newMap.file) { - delete newMap.file; - } + delete newMap.file; const { sourceRoot } = newMap; - if (newMap.sourceRoot) { - delete newMap.sourceRoot; - } + delete newMap.sourceRoot; if (newMap.sources) { newMap.sources = newMap.sources.map((source) => { - return !sourceRoot - ? normalizePath(source) - : normalizePath(path.resolve(sourceRoot, source)); + const sourceType = getURLType(source); + + // Do no touch `scheme-relative` and `absolute` URLs + if (sourceType === 'path-relative' || sourceType === 'path-absolute') { + const absoluteSource = + sourceType === 'path-relative' && sourceRoot + ? path.resolve(sourceRoot, path.normalize(source)) + : path.normalize(source); + + return path.relative(path.dirname(resourcePath), absoluteSource); + } + + return source; }); } return newMap; } -function getSourceMapRelativePath(file, from) { - if (file.indexOf('<') === 0) return file; - if (/^\w+:\/\//.test(file)) return file; +function normalizeSourceMapAfterPostcss(map, resourcePath) { + const newMap = map; - const result = path.relative(from, file); + // result.map.file is an optional property that provides the output filename. + // Since we don't know the final filename in the webpack build chain yet, it makes no sense to have it. + // eslint-disable-next-line no-param-reassign + delete newMap.file; - if (path.sep === '\\') { - return result.replace(/\\/g, '/'); - } - - return result; -} + // eslint-disable-next-line no-param-reassign + newMap.sourceRoot = ''; -function getSourceMapAbsolutePath(file, to) { - if (file.indexOf('<') === 0) return file; - if (/^\w+:\/\//.test(file)) return file; + // eslint-disable-next-line no-param-reassign + newMap.sources = newMap.sources.map((source) => { + if (source.indexOf('<') === 0) { + return source; + } - if (typeof to === 'undefined') return file; + const sourceType = getURLType(source); - const dirname = path.dirname(to); + // Do no touch `scheme-relative`, `path-absolute` and `absolute` types + if (sourceType === 'path-relative') { + const dirname = path.dirname(resourcePath); - const result = path.resolve(dirname, file); + return path.resolve(dirname, source); + } - if (path.sep === '\\') { - return result.replace(/\\/g, '/'); - } + return source; + }); - return result; + return newMap; } export { loadConfig, getPostcssOptions, exec, - getArrayPlugins, - getSourceMapAbsolutePath, - getSourceMapRelativePath, normalizeSourceMap, + normalizeSourceMapAfterPostcss, }; diff --git a/test/__snapshots__/loader.test.js.snap b/test/__snapshots__/loader.test.js.snap index cc439f7f..f90014bb 100644 --- a/test/__snapshots__/loader.test.js.snap +++ b/test/__snapshots__/loader.test.js.snap @@ -5,11 +5,12 @@ Array [ "ModuleBuildError: Module build failed (from \`replaced original path\`): SyntaxError -(1:3) Unexpected separator in property +(1:3) Unnecessary curly bracket -> 1 | a { color: black } +> 1 | a { | ^ - 2 | + 2 | color: black; + 3 | } ", ] `; @@ -21,7 +22,49 @@ exports[`loader should emit asset: errors 1`] = `Array []`; exports[`loader should emit asset: warnings 1`] = `Array []`; exports[`loader should emit warning: css 1`] = ` -"a { color: black } +"a { + color: black; +} + +a { + color: red; +} + +a { + color: green; +} + +a { + color: blue; +} + +.class { + -x-border-color: blue blue *; + -x-color: * #fafafa; +} + +.class-foo { + -z-border-color: blue blue *; + -z-color: * #fafafa; +} + +.phone { + &_title { + width: 500px; + + @media (max-width: 500px) { + width: auto; + } + + body.is_dark & { + color: white; + } + } + + img { + display: block; + } +} " `; @@ -32,12 +75,98 @@ Array [ "ModuleWarning: Module Warning (from \`replaced original path\`): Warning -(1:5) ", +(10:3) ", + "ModuleWarning: Module Warning (from \`replaced original path\`): +Warning + +(14:3) ", + "ModuleWarning: Module Warning (from \`replaced original path\`): +Warning + +(18:3) ", + "ModuleWarning: Module Warning (from \`replaced original path\`): +Warning + +(19:3) ", + "ModuleWarning: Module Warning (from \`replaced original path\`): +Warning + +(23:3) ", + "ModuleWarning: Module Warning (from \`replaced original path\`): +Warning + +(24:3) ", + "ModuleWarning: Module Warning (from \`replaced original path\`): +Warning + +(29:5) ", + "ModuleWarning: Module Warning (from \`replaced original path\`): +Warning + +(2:3) ", + "ModuleWarning: Module Warning (from \`replaced original path\`): +Warning + +(32:7) ", + "ModuleWarning: Module Warning (from \`replaced original path\`): +Warning + +(36:7) ", + "ModuleWarning: Module Warning (from \`replaced original path\`): +Warning + +(41:5) ", + "ModuleWarning: Module Warning (from \`replaced original path\`): +Warning + +(6:3) ", ] `; exports[`loader should work: css 1`] = ` -"a { color: black } +"a { + color: black; +} + +a { + color: red; +} + +a { + color: green; +} + +a { + color: blue; +} + +.class { + -x-border-color: blue blue *; + -x-color: * #fafafa; +} + +.class-foo { + -z-border-color: blue blue *; + -z-color: * #fafafa; +} + +.phone { + &_title { + width: 500px; + + @media (max-width: 500px) { + width: auto; + } + + body.is_dark & { + color: white; + } + } + + img { + display: block; + } +} " `; diff --git a/test/__snapshots__/validate-options.test.js.snap b/test/__snapshots__/validate-options.test.js.snap index 514d066c..ecbe34b2 100644 --- a/test/__snapshots__/validate-options.test.js.snap +++ b/test/__snapshots__/validate-options.test.js.snap @@ -3,37 +3,37 @@ exports[`validate options should throw an error on the "exec" option with "/test/" value 1`] = ` "Invalid options object. PostCSS Loader has been initialized using an options object that does not match the API schema. - options.exec should be a boolean. - -> Enable PostCSS Parser support in 'CSS-in-JS' (https://github.com/postcss/postcss-loader#exec)" + -> Enables/Disables PostCSS Parser support in 'CSS-in-JS' (https://github.com/postcss/postcss-loader#exec)" `; exports[`validate options should throw an error on the "exec" option with "[]" value 1`] = ` "Invalid options object. PostCSS Loader has been initialized using an options object that does not match the API schema. - options.exec should be a boolean. - -> Enable PostCSS Parser support in 'CSS-in-JS' (https://github.com/postcss/postcss-loader#exec)" + -> Enables/Disables PostCSS Parser support in 'CSS-in-JS' (https://github.com/postcss/postcss-loader#exec)" `; exports[`validate options should throw an error on the "exec" option with "{"foo":"bar"}" value 1`] = ` "Invalid options object. PostCSS Loader has been initialized using an options object that does not match the API schema. - options.exec should be a boolean. - -> Enable PostCSS Parser support in 'CSS-in-JS' (https://github.com/postcss/postcss-loader#exec)" + -> Enables/Disables PostCSS Parser support in 'CSS-in-JS' (https://github.com/postcss/postcss-loader#exec)" `; exports[`validate options should throw an error on the "exec" option with "{}" value 1`] = ` "Invalid options object. PostCSS Loader has been initialized using an options object that does not match the API schema. - options.exec should be a boolean. - -> Enable PostCSS Parser support in 'CSS-in-JS' (https://github.com/postcss/postcss-loader#exec)" + -> Enables/Disables PostCSS Parser support in 'CSS-in-JS' (https://github.com/postcss/postcss-loader#exec)" `; exports[`validate options should throw an error on the "exec" option with "1" value 1`] = ` "Invalid options object. PostCSS Loader has been initialized using an options object that does not match the API schema. - options.exec should be a boolean. - -> Enable PostCSS Parser support in 'CSS-in-JS' (https://github.com/postcss/postcss-loader#exec)" + -> Enables/Disables PostCSS Parser support in 'CSS-in-JS' (https://github.com/postcss/postcss-loader#exec)" `; exports[`validate options should throw an error on the "exec" option with "test" value 1`] = ` "Invalid options object. PostCSS Loader has been initialized using an options object that does not match the API schema. - options.exec should be a boolean. - -> Enable PostCSS Parser support in 'CSS-in-JS' (https://github.com/postcss/postcss-loader#exec)" + -> Enables/Disables PostCSS Parser support in 'CSS-in-JS' (https://github.com/postcss/postcss-loader#exec)" `; exports[`validate options should throw an error on the "postcssOptions" option with "{"config":[]}" value 1`] = ` @@ -109,14 +109,13 @@ exports[`validate options should throw an error on the "postcssOptions" option w -> Options to pass through to \`Postcss\`. Details: * options.postcssOptions.plugins should be one of these: - [any, ...] | object { … } | function - -> Set PostCSS Plugins (https://github.com/postcss/postcss-loader#plugins) + [any, ...] | object { … } + -> Sets PostCSS Plugins (https://github.com/postcss/postcss-loader#plugins) Details: * options.postcssOptions.plugins should be an array: [any, ...] * options.postcssOptions.plugins should be an object: - object { … } - * options.postcssOptions.plugins should be an instance of function." + object { … }" `; exports[`validate options should throw an error on the "postcssOptions" option with "{"plugins":1}" value 1`] = ` @@ -126,14 +125,13 @@ exports[`validate options should throw an error on the "postcssOptions" option w -> Options to pass through to \`Postcss\`. Details: * options.postcssOptions.plugins should be one of these: - [any, ...] | object { … } | function - -> Set PostCSS Plugins (https://github.com/postcss/postcss-loader#plugins) + [any, ...] | object { … } + -> Sets PostCSS Plugins (https://github.com/postcss/postcss-loader#plugins) Details: * options.postcssOptions.plugins should be an array: [any, ...] * options.postcssOptions.plugins should be an object: - object { … } - * options.postcssOptions.plugins should be an instance of function." + object { … }" `; exports[`validate options should throw an error on the "postcssOptions" option with "{"plugins":true}" value 1`] = ` @@ -143,14 +141,13 @@ exports[`validate options should throw an error on the "postcssOptions" option w -> Options to pass through to \`Postcss\`. Details: * options.postcssOptions.plugins should be one of these: - [any, ...] | object { … } | function - -> Set PostCSS Plugins (https://github.com/postcss/postcss-loader#plugins) + [any, ...] | object { … } + -> Sets PostCSS Plugins (https://github.com/postcss/postcss-loader#plugins) Details: * options.postcssOptions.plugins should be an array: [any, ...] * options.postcssOptions.plugins should be an object: - object { … } - * options.postcssOptions.plugins should be an instance of function." + object { … }" `; exports[`validate options should throw an error on the "postcssOptions" option with "{"stringifier":[]}" value 1`] = ` @@ -246,6 +243,22 @@ exports[`validate options should throw an error on the "postcssOptions" option w object { … }" `; +exports[`validate options should throw an error on the "postcssOptions" option with "{}" value 1`] = ` +"Invalid options object. PostCSS Loader has been initialized using an options object that does not match the API schema. + - options.postcssOptions should be one of these: + object { config?, parser?, syntax?, stringifier?, plugins?, … } | function + -> Options to pass through to \`Postcss\`. + Details: + * options.postcssOptions.plugins should be one of these: + [any, ...] | object { … } + -> Sets PostCSS Plugins (https://github.com/postcss/postcss-loader#plugins) + Details: + * options.postcssOptions.plugins should be an array: + [any, ...] + * options.postcssOptions.plugins should be an object: + object { … }" +`; + exports[`validate options should throw an error on the "sourceMap" option with "/test/" value 1`] = ` "Invalid options object. PostCSS Loader has been initialized using an options object that does not match the API schema. - options.sourceMap should be a boolean. diff --git a/test/fixtures/config-scope/config/plugin.js b/test/fixtures/config-scope/config/plugin.js index ac15c380..b8712504 100644 --- a/test/fixtures/config-scope/config/plugin.js +++ b/test/fixtures/config-scope/config/plugin.js @@ -8,7 +8,7 @@ module.exports = postcss.plugin('plugin', (options) => { return (css, result) => { css.walkDecls((decl) => { if (decl.value === 'black') { - decl.value = 'rgba(255, 0, 0, ' + options.alpha + ')' + decl.value = 'rgba(0, 0, 0, ' + options.alpha + ')' } }) } diff --git a/test/fixtures/config-scope/config/postcss.config.js b/test/fixtures/config-scope/config/postcss.config.js index 3e112aca..75904286 100644 --- a/test/fixtures/config-scope/config/postcss.config.js +++ b/test/fixtures/config-scope/config/postcss.config.js @@ -3,4 +3,3 @@ module.exports = (ctx) => ({ ctx.options.plugin ? require('./plugin')() : false ] }); - diff --git a/test/fixtures/css/index2.js b/test/fixtures/css/index2.js deleted file mode 100644 index ad0ddaa0..00000000 --- a/test/fixtures/css/index2.js +++ /dev/null @@ -1,3 +0,0 @@ -import style from './style2.css'; - -export default style; diff --git a/test/fixtures/css/plugins.config.js b/test/fixtures/css/plugins.config.js index fbdf6676..ed5d5a47 100644 --- a/test/fixtures/css/plugins.config.js +++ b/test/fixtures/css/plugins.config.js @@ -1,7 +1,7 @@ module.exports = { plugins: { - 'postcss-short': { prefix: 'x' }, 'postcss-import': {}, - 'postcss-nested': {}, + 'postcss-nested': { preserveEmpty: true }, + 'postcss-short': { prefix: 'x' }, } }; diff --git a/test/fixtures/css/style.css b/test/fixtures/css/style.css index fa33ad5f..09096643 100644 --- a/test/fixtures/css/style.css +++ b/test/fixtures/css/style.css @@ -1 +1,43 @@ -a { color: black } +a { + color: black; +} + +a { + color: red; +} + +a { + color: green; +} + +a { + color: blue; +} + +.class { + -x-border-color: blue blue *; + -x-color: * #fafafa; +} + +.class-foo { + -z-border-color: blue blue *; + -z-color: * #fafafa; +} + +.phone { + &_title { + width: 500px; + + @media (max-width: 500px) { + width: auto; + } + + body.is_dark & { + color: white; + } + } + + img { + display: block; + } +} diff --git a/test/fixtures/css/style2.css b/test/fixtures/css/style2.css deleted file mode 100644 index 3bab8470..00000000 --- a/test/fixtures/css/style2.css +++ /dev/null @@ -1,4 +0,0 @@ -a { - -x-border-color: blue blue *; - -x-color: * #fafafa; -} diff --git a/test/fixtures/plugin/other-plugin.js b/test/fixtures/plugin/other-plugin.js new file mode 100644 index 00000000..3742fad1 --- /dev/null +++ b/test/fixtures/plugin/other-plugin.js @@ -0,0 +1,15 @@ +'use strict'; + +const postcss = require('postcss'); + +module.exports = postcss.plugin('my-plugin', (options) => { + options = Object.assign({ alpha: '1.0' }, options); + + return (root, result) => { + root.walkDecls((decl) => { + if (decl.value === 'blue') { + decl.value = 'rgba(0, 0, 255, ' + options.alpha + ')' + } + }) + } +}); diff --git a/test/fixtures/plugin/plugin.js b/test/fixtures/plugin/plugin.js new file mode 100644 index 00000000..de0685b7 --- /dev/null +++ b/test/fixtures/plugin/plugin.js @@ -0,0 +1,15 @@ +'use strict'; + +const postcss = require('postcss'); + +module.exports = postcss.plugin('my-plugin', (options) => { + const myOptions = {...{ alpha: '1.0', color: 'black' }, ...options}; + + return (root, result) => { + root.walkDecls((decl) => { + if (decl.value === myOptions.color) { + decl.value = 'rgba(0, 0, 0, ' + myOptions.alpha + ')' + } + }) + } +}); diff --git a/test/helpers/getCodeFromBundle.js b/test/helpers/getCodeFromBundle.js index 990b1eef..83fc695a 100644 --- a/test/helpers/getCodeFromBundle.js +++ b/test/helpers/getCodeFromBundle.js @@ -1,6 +1,4 @@ -import normalizeMap from './normalizeMap'; - -export default (id, stats, processMap = true) => { +export default (id, stats) => { const { modules } = stats.compilation; const module = modules.find((m) => m.id.endsWith(id)); const { _source } = module; @@ -19,7 +17,7 @@ export default (id, stats, processMap = true) => { result = { css: code }; } - const { css, map } = result; + const { css, map: sourceMap } = result; - return { css, map: processMap ? normalizeMap(map) : map }; + return { css, sourceMap }; }; diff --git a/test/helpers/normalizeMap.js b/test/helpers/normalizeMap.js deleted file mode 100644 index 4c013be8..00000000 --- a/test/helpers/normalizeMap.js +++ /dev/null @@ -1,26 +0,0 @@ -export default (map) => { - if (typeof map === 'undefined') { - // eslint-disable-next-line no-undefined - return undefined; - } - - const result = map; - - if (result.sources) { - const replacedArray = new Array(result.sources.length); - - result.sources = replacedArray.fill('xxx'); - } - - if (result.file) { - [result.file] = 'x'; - } - - if (result.sourceRoot) { - [result.sourceRoot] = 'x'; - } else { - delete result.sourceRoot; - } - - return result; -}; diff --git a/test/loader.test.js b/test/loader.test.js index 0afed5d3..f11bddf9 100644 --- a/test/loader.test.js +++ b/test/loader.test.js @@ -14,7 +14,6 @@ describe('loader', () => { postcssOptions: { plugins: [], }, - config: false, }); const stats = await compile(compiler); @@ -38,7 +37,6 @@ describe('loader', () => { postcssOptions: { plugins: [postcssPlugin()], }, - config: false, }); const stats = await compile(compiler); @@ -54,7 +52,6 @@ describe('loader', () => { postcssOptions: { parser: 'sugarss', }, - config: false, }); const stats = await compile(compiler); @@ -78,7 +75,6 @@ describe('loader', () => { postcssOptions: { plugins: [postcssPlugin()], }, - config: false, }); const stats = await compile(compiler); diff --git a/test/options/__snapshots__/config.test.js.snap b/test/options/__snapshots__/config.test.js.snap index cff1e25d..3b9ff6bd 100644 --- a/test/options/__snapshots__/config.test.js.snap +++ b/test/options/__snapshots__/config.test.js.snap @@ -19,7 +19,7 @@ Error: No PostCSS Config found in: /test/fixtures/config-scope/css/unresolve.js" exports[`Config Options should emit error when unresolved config : warnings 1`] = `Array []`; exports[`Config Options should work "config" and "postcssOptions" options: css 1`] = ` -"a { color: rgba(255, 0, 0, 1.0) } +"a { color: rgba(0, 0, 0, 1.0) } [dir=ltr] .foo { float: right; } @@ -35,7 +35,7 @@ exports[`Config Options should work "config" and "postcssOptions" options: error exports[`Config Options should work "config" and "postcssOptions" options: warnings 1`] = `Array []`; exports[`Config Options should work Config - "string" with path directory: css 1`] = ` -"a { color: rgba(255, 0, 0, 1.0) } +"a { color: rgba(0, 0, 0, 1.0) } .foo { float: right; @@ -48,7 +48,7 @@ exports[`Config Options should work Config - "string" with path directory: error exports[`Config Options should work Config - "string" with path directory: warnings 1`] = `Array []`; exports[`Config Options should work Config - "string" with relative path: css 1`] = ` -"a { color: rgba(255, 0, 0, 1.0) } +"a { color: rgba(0, 0, 0, 1.0) } .foo { float: right; @@ -61,7 +61,7 @@ exports[`Config Options should work Config - "string" with relative path: errors exports[`Config Options should work Config - "string" with relative path: warnings 1`] = `Array []`; exports[`Config Options should work Config - "string": css 1`] = ` -"a { color: rgba(255, 0, 0, 1.0) } +"a { color: rgba(0, 0, 0, 1.0) } .foo { float: right; @@ -74,7 +74,7 @@ exports[`Config Options should work Config - "string": errors 1`] = `Array []`; exports[`Config Options should work Config - "string": warnings 1`] = `Array []`; exports[`Config Options should work Config - {Object}: css 1`] = ` -"a { color: rgba(255, 0, 0, 1.0) } +"a { color: rgba(0, 0, 0, 1.0) } .foo { float: right; @@ -87,7 +87,7 @@ exports[`Config Options should work Config - {Object}: errors 1`] = `Array []`; exports[`Config Options should work Config - {Object}: warnings 1`] = `Array []`; exports[`Config Options should work Config - Context - {Object}: css 1`] = ` -"a { color: rgba(255, 0, 0, 1.0) } +"a { color: rgba(0, 0, 0, 1.0) } .foo { float: right; @@ -100,7 +100,7 @@ exports[`Config Options should work Config - Context - {Object}: errors 1`] = `A exports[`Config Options should work Config - Context - {Object}: warnings 1`] = `Array []`; exports[`Config Options should work Config - Object - path file: css 1`] = ` -"a { color: rgba(255, 0, 0, 1.0) } +"a { color: rgba(0, 0, 0, 1.0) } .foo { float: right; @@ -139,7 +139,7 @@ exports[`Config Options should work Config - false: errors 1`] = `Array []`; exports[`Config Options should work Config - false: warnings 1`] = `Array []`; exports[`Config Options should work Config - true: css 1`] = ` -"a { color: rgba(255, 0, 0, 1.0) } +"a { color: rgba(0, 0, 0, 1.0) } .foo { float: right; @@ -156,7 +156,49 @@ exports[`Config Options should work Config – Context – Loader {Object}: erro exports[`Config Options should work Config – Context – Loader {Object}: warnings 1`] = `Array []`; exports[`Config Options should work if Config not found: css 1`] = ` -"a { color: black } +"a { + color: black; +} + +a { + color: red; +} + +a { + color: green; +} + +a { + color: blue; +} + +.class { + -x-border-color: blue blue *; + -x-color: * #fafafa; +} + +.class-foo { + -z-border-color: blue blue *; + -z-color: * #fafafa; +} + +.phone { + &_title { + width: 500px; + + @media (max-width: 500px) { + width: auto; + } + + body.is_dark & { + color: white; + } + } + + img { + display: block; + } +} " `; diff --git a/test/options/__snapshots__/plugins.test.js.snap b/test/options/__snapshots__/plugins.test.js.snap index 8e0a7874..9d814674 100644 --- a/test/options/__snapshots__/plugins.test.js.snap +++ b/test/options/__snapshots__/plugins.test.js.snap @@ -1,130 +1,330 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Options Plugins should disables plugin from config: css 1`] = ` +exports[`"plugins" option should throw an error on the unresolved plugin: errors 1`] = ` +Array [ + "ModuleError: Module Error (from \`replaced original path\`): +Loading PostCSS Plugin failed: Cannot find module 'postcss-unresolved' from 'src/utils.js'", +] +`; + +exports[`"plugins" option should throw an error on the unresolved plugin: warnings 1`] = `Array []`; + +exports[`"plugins" option should work with "Array" and not throw an error on falsy plugin: errors 1`] = `Array []`; + +exports[`"plugins" option should work with "Array" and not throw an error on falsy plugin: warnings 1`] = `Array []`; + +exports[`"plugins" option should work with "Array": css 1`] = ` "a { + color: rgba(0, 0, 0, 1.0); +} + +a { + color: rgba(255, 0, 0, 1.0); +} + +a { + color: rgba(0, 255, 0, 1.0); +} + +a { + color: rgba(0, 0, 255, 1.0); +} + +.class { -x-border-color: blue blue *; -x-color: * #fafafa; } -" -`; -exports[`Options Plugins should disables plugin from config: errors 1`] = `Array []`; +.class-foo { + border-top-color: rgba(0, 0, 255, 1.0); + border-right-color: rgba(0, 0, 255, 1.0); + border-left-color: rgba(0, 0, 255, 1.0); + background-color: #fafafa; +} + +.phone_title { + width: 500px; + } -exports[`Options Plugins should disables plugin from config: warnings 1`] = `Array []`; +@media (max-width: 500px) { -exports[`Options Plugins should emit error on load plugin: errors 1`] = ` -Array [ - "ModuleError: Module Error (from \`replaced original path\`): -Loading PostCSS Plugin failed: Cannot find module 'postcss-unresolved' from 'src/utils.js'", -] +.phone_title { + width: auto + } + } + +body.is_dark .phone_title { + color: rgba(0, 0, 0, 0); + } + +.phone img { + display: block; + } +" `; -exports[`Options Plugins should emit error on load plugin: warnings 1`] = `Array []`; +exports[`"plugins" option should work with "Array": errors 1`] = `Array []`; + +exports[`"plugins" option should work with "Array": warnings 1`] = `Array []`; -exports[`Options Plugins should work Plugins - { Array } Array + options: css 1`] = ` +exports[`"plugins" option should work with "Object" and only disabled plugins: css 1`] = ` "a { + color: black; +} + +a { + color: red; +} + +a { + color: green; +} + +a { + color: blue; +} + +.class { -x-border-color: blue blue *; -x-color: * #fafafa; } + +.class-foo { + -z-border-color: blue blue *; + -z-color: * #fafafa; +} + +.phone { + &_title { + width: 500px; + + @media (max-width: 500px) { + width: auto; + } + + body.is_dark & { + color: white; + } + } + + img { + display: block; + } +} " `; -exports[`Options Plugins should work Plugins - { Array } Array + options: errors 1`] = `Array []`; +exports[`"plugins" option should work with "Object" and only disabled plugins: errors 1`] = `Array []`; -exports[`Options Plugins should work Plugins - { Array } Array + options: warnings 1`] = `Array []`; +exports[`"plugins" option should work with "Object" and only disabled plugins: warnings 1`] = `Array []`; -exports[`Options Plugins should work Plugins - {Array} + options: css 1`] = ` +exports[`"plugins" option should work with "Object" and support disabling plugins from the configuration: css 1`] = ` "a { - border-top-color: blue; - border-right-color: blue; - border-left-color: blue; - background-color: #fafafa; + color: black; +} + +a { + color: red; } -" -`; -exports[`Options Plugins should work Plugins - {Array} + options: errors 1`] = `Array []`; +a { + color: green; +} -exports[`Options Plugins should work Plugins - {Array} + options: warnings 1`] = `Array []`; +a { + color: blue; +} -exports[`Options Plugins should work Plugins - {Array}: css 1`] = ` -"a { color: rgba(255, 0, 0, 1.0) } -" -`; +.class { + -x-border-color: blue blue *; + -x-color: * #fafafa; +} -exports[`Options Plugins should work Plugins - {Array}: errors 1`] = `Array []`; +.class-foo { + -z-border-color: blue blue *; + -z-color: * #fafafa; +} -exports[`Options Plugins should work Plugins - {Array}: warnings 1`] = `Array []`; +.phone { +} -exports[`Options Plugins should work Plugins - {Function} - {Array}: css 1`] = ` -"a { color: rgba(255, 0, 0, 1.0) } -" -`; +.phone_title { + width: 500px + } + +@media (max-width: 500px) { -exports[`Options Plugins should work Plugins - {Function} - {Array}: errors 1`] = `Array []`; +.phone_title { + width: auto + } + } -exports[`Options Plugins should work Plugins - {Function} - {Array}: warnings 1`] = `Array []`; +body.is_dark .phone_title { + color: white; + } -exports[`Options Plugins should work Plugins - {Function} - {Object}: css 1`] = ` -"a { color: rgba(255, 0, 0, 1.0) } +.phone img { + display: block; + } " `; -exports[`Options Plugins should work Plugins - {Function} - {Object}: errors 1`] = `Array []`; +exports[`"plugins" option should work with "Object" and support disabling plugins from the configuration: errors 1`] = `Array []`; -exports[`Options Plugins should work Plugins - {Function} - {Object}: warnings 1`] = `Array []`; +exports[`"plugins" option should work with "Object" and support disabling plugins from the configuration: warnings 1`] = `Array []`; -exports[`Options Plugins should work Plugins - {Object without require} + options: css 1`] = ` +exports[`"plugins" option should work with "Object": css 1`] = ` "a { - border-top-color: blue; - border-right-color: blue; - border-left-color: blue; + color: black; +} + +a { + color: red; +} + +a { + color: green; +} + +a { + color: rgba(0, 0, 255, 1.0); +} + +.class { + border-top-color: rgba(0, 0, 255, 1.0); + border-right-color: rgba(0, 0, 255, 1.0); + border-left-color: rgba(0, 0, 255, 1.0); background-color: #fafafa; } -" -`; -exports[`Options Plugins should work Plugins - {Object without require} + options: errors 1`] = `Array []`; +.class-foo { + -z-border-color: blue blue *; + -z-color: * #fafafa; +} + +.phone_title { + width: 500px; + } -exports[`Options Plugins should work Plugins - {Object without require} + options: warnings 1`] = `Array []`; +@media (max-width: 500px) { -exports[`Options Plugins should work Plugins - {Object without require}: css 1`] = ` -"a { color: black } +.phone_title { + width: auto + } + } + +body.is_dark .phone_title { + color: white; + } + +.phone img { + display: block; + } " `; -exports[`Options Plugins should work Plugins - {Object without require}: errors 1`] = `Array []`; +exports[`"plugins" option should work with "Object": errors 1`] = `Array []`; -exports[`Options Plugins should work Plugins - {Object without require}: warnings 1`] = `Array []`; +exports[`"plugins" option should work with "Object": warnings 1`] = `Array []`; -exports[`Options Plugins should work Plugins - {Object} + options: css 1`] = ` +exports[`"plugins" option should work with empty "Array": css 1`] = ` "a { - border-top-color: blue; - border-right-color: blue; - border-left-color: blue; - background-color: #fafafa; + color: black; +} + +a { + color: red; +} + +a { + color: green; +} + +a { + color: blue; +} + +.class { + -x-border-color: blue blue *; + -x-color: * #fafafa; +} + +.class-foo { + -z-border-color: blue blue *; + -z-color: * #fafafa; } -" -`; -exports[`Options Plugins should work Plugins - {Object} + options: errors 1`] = `Array []`; +.phone { + &_title { + width: 500px; -exports[`Options Plugins should work Plugins - {Object} + options: warnings 1`] = `Array []`; + @media (max-width: 500px) { + width: auto; + } -exports[`Options Plugins should work Plugins - {Object}: css 1`] = ` -"a { color: rgba(255, 0, 0, 1.0) } + body.is_dark & { + color: white; + } + } + + img { + display: block; + } +} " `; -exports[`Options Plugins should work Plugins - {Object}: errors 1`] = `Array []`; +exports[`"plugins" option should work with empty "Array": errors 1`] = `Array []`; + +exports[`"plugins" option should work with empty "Array": warnings 1`] = `Array []`; + +exports[`"plugins" option should work with empty "Object": css 1`] = ` +"a { + color: black; +} + +a { + color: red; +} + +a { + color: green; +} + +a { + color: blue; +} + +.class { + -x-border-color: blue blue *; + -x-color: * #fafafa; +} + +.class-foo { + -z-border-color: blue blue *; + -z-color: * #fafafa; +} -exports[`Options Plugins should work Plugins - {Object}: warnings 1`] = `Array []`; +.phone { + &_title { + width: 500px; -exports[`Options Plugins should work Plugins - {empty Object}: css 1`] = ` -"a { color: black } + @media (max-width: 500px) { + width: auto; + } + + body.is_dark & { + color: white; + } + } + + img { + display: block; + } +} " `; -exports[`Options Plugins should work Plugins - {empty Object}: errors 1`] = `Array []`; +exports[`"plugins" option should work with empty "Object": errors 1`] = `Array []`; -exports[`Options Plugins should work Plugins - {empty Object}: warnings 1`] = `Array []`; +exports[`"plugins" option should work with empty "Object": warnings 1`] = `Array []`; diff --git a/test/options/__snapshots__/postcssOptins.test.js.snap b/test/options/__snapshots__/postcssOptins.test.js.snap index ae2e6a87..81a514fb 100644 --- a/test/options/__snapshots__/postcssOptins.test.js.snap +++ b/test/options/__snapshots__/postcssOptins.test.js.snap @@ -1,9 +1,51 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`"postcssOptions" option should work the the "map" option and generate inlined source maps: css 1`] = ` -"a { color: black } +"a { + color: black; +} + +a { + color: red; +} + +a { + color: green; +} + +a { + color: blue; +} + +.class { + -x-border-color: blue blue *; + -x-color: * #fafafa; +} + +.class-foo { + -z-border-color: blue blue *; + -z-color: * #fafafa; +} + +.phone { + &_title { + width: 500px; + + @media (max-width: 500px) { + width: auto; + } -/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInN0eWxlLmNzcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxJQUFJLGFBQWEiLCJmaWxlIjoic3R5bGUuY3NzIiwic291cmNlc0NvbnRlbnQiOlsiYSB7IGNvbG9yOiBibGFjayB9XG4iXX0= */" + body.is_dark & { + color: white; + } + } + + img { + display: block; + } +} + +/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInN0eWxlLmNzcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTtFQUNFLFlBQVk7QUFDZDs7QUFFQTtFQUNFLFVBQVU7QUFDWjs7QUFFQTtFQUNFLFlBQVk7QUFDZDs7QUFFQTtFQUNFLFdBQVc7QUFDYjs7QUFFQTtFQUNFLDRCQUE0QjtFQUM1QixtQkFBbUI7QUFDckI7O0FBRUE7RUFDRSw0QkFBNEI7RUFDNUIsbUJBQW1CO0FBQ3JCOztBQUVBO0VBQ0U7SUFDRSxZQUFZOztJQUVaO01BQ0UsV0FBVztJQUNiOztJQUVBO01BQ0UsWUFBWTtJQUNkO0VBQ0Y7O0VBRUE7SUFDRSxjQUFjO0VBQ2hCO0FBQ0YiLCJmaWxlIjoic3R5bGUuY3NzIiwic291cmNlc0NvbnRlbnQiOlsiYSB7XG4gIGNvbG9yOiBibGFjaztcbn1cblxuYSB7XG4gIGNvbG9yOiByZWQ7XG59XG5cbmEge1xuICBjb2xvcjogZ3JlZW47XG59XG5cbmEge1xuICBjb2xvcjogYmx1ZTtcbn1cblxuLmNsYXNzIHtcbiAgLXgtYm9yZGVyLWNvbG9yOiBibHVlIGJsdWUgKjtcbiAgLXgtY29sb3I6ICogI2ZhZmFmYTtcbn1cblxuLmNsYXNzLWZvbyB7XG4gIC16LWJvcmRlci1jb2xvcjogYmx1ZSBibHVlICo7XG4gIC16LWNvbG9yOiAqICNmYWZhZmE7XG59XG5cbi5waG9uZSB7XG4gICZfdGl0bGUge1xuICAgIHdpZHRoOiA1MDBweDtcblxuICAgIEBtZWRpYSAobWF4LXdpZHRoOiA1MDBweCkge1xuICAgICAgd2lkdGg6IGF1dG87XG4gICAgfVxuXG4gICAgYm9keS5pc19kYXJrICYge1xuICAgICAgY29sb3I6IHdoaXRlO1xuICAgIH1cbiAgfVxuXG4gIGltZyB7XG4gICAgZGlzcGxheTogYmxvY2s7XG4gIH1cbn1cbiJdfQ== */" `; exports[`"postcssOptions" option should work the the "map" option and generate inlined source maps: errors 1`] = `Array []`; @@ -12,27 +54,208 @@ exports[`"postcssOptions" option should work the the "map" option and generate i exports[`"postcssOptions" option should work the the "map" option and generate inlined source maps: warnings 1`] = `Array []`; -exports[`"postcssOptions" option should work with "from", "to" and "map" options: css 1`] = ` -"a { color: black } +exports[`"postcssOptions" option should work when the "postcssOptions" option is "Function" and the "plugins" option is "Array": css 1`] = ` +"a { + color: rgba(0, 0, 0, 1.0); +} + +a { + color: red; +} + +a { + color: green; +} + +a { + color: blue; +} + +.class { + -x-border-color: blue blue *; + -x-color: * #fafafa; +} + +.class-foo { + -z-border-color: blue blue *; + -z-color: * #fafafa; +} + +.phone { + &_title { + width: 500px; + + @media (max-width: 500px) { + width: auto; + } + + body.is_dark & { + color: white; + } + } + + img { + display: block; + } +} " `; -exports[`"postcssOptions" option should work with "from", "to" and "map" options: errors 1`] = `Array []`; +exports[`"postcssOptions" option should work when the "postcssOptions" option is "Function" and the "plugins" option is "Array": errors 1`] = `Array []`; + +exports[`"postcssOptions" option should work when the "postcssOptions" option is "Function" and the "plugins" option is "Array": warnings 1`] = `Array []`; + +exports[`"postcssOptions" option should work when the "postcssOptions" option is "Function" and the "plugins" option is "Object": css 1`] = ` +"a { + color: rgba(0, 0, 0, 1.0); +} -exports[`"postcssOptions" option should work with "from", "to" and "map" options: map 1`] = ` -Object { - "file": "x", - "mappings": "AAAA,IAAI,aAAa", - "names": Array [], - "sources": Array [ - "xxx", - ], - "sourcesContent": Array [ - "a { color: black } -", - ], - "version": 3, +a { + color: red; } + +a { + color: green; +} + +a { + color: blue; +} + +.class { + -x-border-color: blue blue *; + -x-color: * #fafafa; +} + +.class-foo { + -z-border-color: blue blue *; + -z-color: * #fafafa; +} + +.phone { + &_title { + width: 500px; + + @media (max-width: 500px) { + width: auto; + } + + body.is_dark & { + color: white; + } + } + + img { + display: block; + } +} +" +`; + +exports[`"postcssOptions" option should work when the "postcssOptions" option is "Function" and the "plugins" option is "Object": errors 1`] = `Array []`; + +exports[`"postcssOptions" option should work when the "postcssOptions" option is "Function" and the "plugins" option is "Object": warnings 1`] = `Array []`; + +exports[`"postcssOptions" option should work when the "postcssOptions" option is "Function": css 1`] = ` +"a { + color: rgba(0, 0, 0, 1.0); +} + +a { + color: red; +} + +a { + color: green; +} + +a { + color: blue; +} + +.class { + -x-border-color: blue blue *; + -x-color: * #fafafa; +} + +.class-foo { + -z-border-color: blue blue *; + -z-color: * #fafafa; +} + +.phone { + &_title { + width: 500px; + + @media (max-width: 500px) { + width: auto; + } + + body.is_dark & { + color: white; + } + } + + img { + display: block; + } +} +" +`; + +exports[`"postcssOptions" option should work when the "postcssOptions" option is "Function": errors 1`] = `Array []`; + +exports[`"postcssOptions" option should work when the "postcssOptions" option is "Function": warnings 1`] = `Array []`; + +exports[`"postcssOptions" option should work with "from", "to" and "map" options: css 1`] = ` +"a { + color: black; +} + +a { + color: red; +} + +a { + color: green; +} + +a { + color: blue; +} + +.class { + -x-border-color: blue blue *; + -x-color: * #fafafa; +} + +.class-foo { + -z-border-color: blue blue *; + -z-color: * #fafafa; +} + +.phone { + &_title { + width: 500px; + + @media (max-width: 500px) { + width: auto; + } + + body.is_dark & { + color: white; + } + } + + img { + display: block; + } +} +" `; +exports[`"postcssOptions" option should work with "from", "to" and "map" options: errors 1`] = `Array []`; + +exports[`"postcssOptions" option should work with "from", "to" and "map" options: map 1`] = `undefined`; + exports[`"postcssOptions" option should work with "from", "to" and "map" options: warnings 1`] = `Array []`; diff --git a/test/options/__snapshots__/sourceMap.test.js.snap b/test/options/__snapshots__/sourceMap.test.js.snap index afc988f0..73563aee 100644 --- a/test/options/__snapshots__/sourceMap.test.js.snap +++ b/test/options/__snapshots__/sourceMap.test.js.snap @@ -1,109 +1,644 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Options Sourcemap should generated absolute paths in sourcemap: css 1`] = ` -"a { color: black } +exports[`"sourceMap" option should generate source maps when previous loader return source maps ("less-loader"): css 1`] = ` +"a { + color: coral; +} " `; -exports[`Options Sourcemap should generated absolute paths in sourcemap: errors 1`] = `Array []`; +exports[`"sourceMap" option should generate source maps when previous loader return source maps ("less-loader"): errors 1`] = `Array []`; + +exports[`"sourceMap" option should generate source maps when previous loader return source maps ("less-loader"): source map 1`] = ` +Object { + "mappings": "AAAA;EAAI,YAAA;AAEJ", + "names": Array [], + "sourceRoot": "", + "sources": Array [ + "fixtures/less/style.less", + ], + "sourcesContent": Array [ + "a { color: coral } +", + ], + "version": 3, +} +`; + +exports[`"sourceMap" option should generate source maps when previous loader return source maps ("less-loader"): warnings 1`] = `Array []`; + +exports[`"sourceMap" option should generate source maps when previous loader return source maps ("sass-loader"): css 1`] = ` +"a { + color: coral; +}" +`; + +exports[`"sourceMap" option should generate source maps when previous loader return source maps ("sass-loader"): errors 1`] = `Array []`; -exports[`Options Sourcemap should generated absolute paths in sourcemap: map 1`] = ` +exports[`"sourceMap" option should generate source maps when previous loader return source maps ("sass-loader"): source map 1`] = ` Object { - "mappings": "AAAA,IAAI,aAAa", + "mappings": "AAAA;EAAI,YAAA;AAEJ", "names": Array [], + "sourceRoot": "", "sources": Array [ - "xxx", + "fixtures/scss/style.scss", ], "sourcesContent": Array [ - "a { color: black } + "a { color: coral } ", ], "version": 3, } `; -exports[`Options Sourcemap should generated absolute paths in sourcemap: warnings 1`] = `Array []`; +exports[`"sourceMap" option should generate source maps when previous loader return source maps ("sass-loader"): warnings 1`] = `Array []`; + +exports[`"sourceMap" option should generate source maps when value has "false" value, but the "postcssOptions.map" has the "true" value: css 1`] = ` +"a { + color: black; +} + +a { + color: red; +} + +a { + color: green; +} + +a { + color: blue; +} + +.class { + -x-border-color: blue blue *; + -x-color: * #fafafa; +} + +.class-foo { + -z-border-color: blue blue *; + -z-color: * #fafafa; +} -exports[`Options Sourcemap should work Sourcemap - {Boolean}: css 1`] = ` -"a { color: black } +.phone { + &_title { + width: 500px; + + @media (max-width: 500px) { + width: auto; + } + + body.is_dark & { + color: white; + } + } + + img { + display: block; + } +} " `; -exports[`Options Sourcemap should work Sourcemap - {Boolean}: errors 1`] = `Array []`; +exports[`"sourceMap" option should generate source maps when value has "false" value, but the "postcssOptions.map" has the "true" value: errors 1`] = `Array []`; -exports[`Options Sourcemap should work Sourcemap - {Boolean}: map 1`] = ` +exports[`"sourceMap" option should generate source maps when value has "false" value, but the "postcssOptions.map" has the "true" value: source map 1`] = ` Object { - "mappings": "AAAA,IAAI,aAAa", + "file": "style.css", + "mappings": "AAAA;EACE,YAAY;AACd;;AAEA;EACE,UAAU;AACZ;;AAEA;EACE,YAAY;AACd;;AAEA;EACE,WAAW;AACb;;AAEA;EACE,4BAA4B;EAC5B,mBAAmB;AACrB;;AAEA;EACE,4BAA4B;EAC5B,mBAAmB;AACrB;;AAEA;EACE;IACE,YAAY;;IAEZ;MACE,WAAW;IACb;;IAEA;MACE,YAAY;IACd;EACF;;EAEA;IACE,cAAc;EAChB;AACF", "names": Array [], + "sourceRoot": "", "sources": Array [ - "xxx", + "../style.css", ], "sourcesContent": Array [ - "a { color: black } + "a { + color: black; +} + +a { + color: red; +} + +a { + color: green; +} + +a { + color: blue; +} + +.class { + -x-border-color: blue blue *; + -x-color: * #fafafa; +} + +.class-foo { + -z-border-color: blue blue *; + -z-color: * #fafafa; +} + +.phone { + &_title { + width: 500px; + + @media (max-width: 500px) { + width: auto; + } + + body.is_dark & { + color: white; + } + } + + img { + display: block; + } +} ", ], "version": 3, } `; -exports[`Options Sourcemap should work Sourcemap - {Boolean}: warnings 1`] = `Array []`; +exports[`"sourceMap" option should generate source maps when value has "false" value, but the "postcssOptions.map" has the "true" value: warnings 1`] = `Array []`; + +exports[`"sourceMap" option should generate source maps when value has "true" value and the "devtool" option has "false" value: css 1`] = ` +"a { + color: black; +} + +a { + color: red; +} + +a { + color: green; +} + +a { + color: blue; +} -exports[`Options Sourcemap should work disable Sourcemap - {Boolean}: css 1`] = ` -"a { color: black } +.class { + -x-border-color: blue blue *; + -x-color: * #fafafa; +} + +.class-foo { + -z-border-color: blue blue *; + -z-color: * #fafafa; +} + +.phone { + &_title { + width: 500px; + + @media (max-width: 500px) { + width: auto; + } + + body.is_dark & { + color: white; + } + } + + img { + display: block; + } +} " `; -exports[`Options Sourcemap should work disable Sourcemap - {Boolean}: errors 1`] = `Array []`; +exports[`"sourceMap" option should generate source maps when value has "true" value and the "devtool" option has "false" value: errors 1`] = `Array []`; + +exports[`"sourceMap" option should generate source maps when value has "true" value and the "devtool" option has "false" value: source map 1`] = ` +Object { + "mappings": "AAAA;EACE,YAAY;AACd;;AAEA;EACE,UAAU;AACZ;;AAEA;EACE,YAAY;AACd;;AAEA;EACE,WAAW;AACb;;AAEA;EACE,4BAA4B;EAC5B,mBAAmB;AACrB;;AAEA;EACE,4BAA4B;EAC5B,mBAAmB;AACrB;;AAEA;EACE;IACE,YAAY;;IAEZ;MACE,WAAW;IACb;;IAEA;MACE,YAAY;IACd;EACF;;EAEA;IACE,cAAc;EAChB;AACF", + "names": Array [], + "sourceRoot": "", + "sources": Array [ + "fixtures/css/style.css", + ], + "sourcesContent": Array [ + "a { + color: black; +} + +a { + color: red; +} + +a { + color: green; +} + +a { + color: blue; +} + +.class { + -x-border-color: blue blue *; + -x-color: * #fafafa; +} + +.class-foo { + -z-border-color: blue blue *; + -z-color: * #fafafa; +} + +.phone { + &_title { + width: 500px; + + @media (max-width: 500px) { + width: auto; + } -exports[`Options Sourcemap should work disable Sourcemap - {Boolean}: warnings 1`] = `Array []`; + body.is_dark & { + color: white; + } + } -exports[`Options Sourcemap should work with prev sourceMap (less-loader): css 1`] = ` + img { + display: block; + } +} +", + ], + "version": 3, +} +`; + +exports[`"sourceMap" option should generate source maps when value has "true" value and the "devtool" option has "false" value: warnings 1`] = `Array []`; + +exports[`"sourceMap" option should generate source maps when value has "true" value and the "devtool" option has "source-map" value: css 1`] = ` "a { - color: coral; + color: black; +} + +a { + color: red; +} + +a { + color: green; +} + +a { + color: blue; +} + +.class { + -x-border-color: blue blue *; + -x-color: * #fafafa; +} + +.class-foo { + -z-border-color: blue blue *; + -z-color: * #fafafa; +} + +.phone { + &_title { + width: 500px; + + @media (max-width: 500px) { + width: auto; + } + + body.is_dark & { + color: white; + } + } + + img { + display: block; + } } " `; -exports[`Options Sourcemap should work with prev sourceMap (less-loader): errors 1`] = `Array []`; +exports[`"sourceMap" option should generate source maps when value has "true" value and the "devtool" option has "source-map" value: errors 1`] = `Array []`; -exports[`Options Sourcemap should work with prev sourceMap (less-loader): map 1`] = ` +exports[`"sourceMap" option should generate source maps when value has "true" value and the "devtool" option has "source-map" value: source map 1`] = ` Object { - "mappings": "AAAA;EAAI,YAAA;AAEJ", + "mappings": "AAAA;EACE,YAAY;AACd;;AAEA;EACE,UAAU;AACZ;;AAEA;EACE,YAAY;AACd;;AAEA;EACE,WAAW;AACb;;AAEA;EACE,4BAA4B;EAC5B,mBAAmB;AACrB;;AAEA;EACE,4BAA4B;EAC5B,mBAAmB;AACrB;;AAEA;EACE;IACE,YAAY;;IAEZ;MACE,WAAW;IACb;;IAEA;MACE,YAAY;IACd;EACF;;EAEA;IACE,cAAc;EAChB;AACF", "names": Array [], + "sourceRoot": "", "sources": Array [ - "xxx", + "fixtures/css/style.css", ], "sourcesContent": Array [ - "a { color: coral } + "a { + color: black; +} + +a { + color: red; +} + +a { + color: green; +} + +a { + color: blue; +} + +.class { + -x-border-color: blue blue *; + -x-color: * #fafafa; +} + +.class-foo { + -z-border-color: blue blue *; + -z-color: * #fafafa; +} + +.phone { + &_title { + width: 500px; + + @media (max-width: 500px) { + width: auto; + } + + body.is_dark & { + color: white; + } + } + + img { + display: block; + } +} ", ], "version": 3, } `; -exports[`Options Sourcemap should work with prev sourceMap (less-loader): warnings 1`] = `Array []`; +exports[`"sourceMap" option should generate source maps when value has "true" value and the "devtool" option has "source-map" value: warnings 1`] = `Array []`; -exports[`Options Sourcemap should work with prev sourceMap (sass-loader): css 1`] = ` +exports[`"sourceMap" option should generate source maps when value is not specified and the "devtool" option has "source-map" value: css 1`] = ` "a { - color: coral; -}" + color: black; +} + +a { + color: red; +} + +a { + color: green; +} + +a { + color: blue; +} + +.class { + -x-border-color: blue blue *; + -x-color: * #fafafa; +} + +.class-foo { + -z-border-color: blue blue *; + -z-color: * #fafafa; +} + +.phone { + &_title { + width: 500px; + + @media (max-width: 500px) { + width: auto; + } + + body.is_dark & { + color: white; + } + } + + img { + display: block; + } +} +" `; -exports[`Options Sourcemap should work with prev sourceMap (sass-loader): errors 1`] = `Array []`; +exports[`"sourceMap" option should generate source maps when value is not specified and the "devtool" option has "source-map" value: errors 1`] = `Array []`; -exports[`Options Sourcemap should work with prev sourceMap (sass-loader): map 1`] = ` +exports[`"sourceMap" option should generate source maps when value is not specified and the "devtool" option has "source-map" value: source map 1`] = ` Object { - "mappings": "AAAA;EAAI,YAAA;AAEJ", + "mappings": "AAAA;EACE,YAAY;AACd;;AAEA;EACE,UAAU;AACZ;;AAEA;EACE,YAAY;AACd;;AAEA;EACE,WAAW;AACb;;AAEA;EACE,4BAA4B;EAC5B,mBAAmB;AACrB;;AAEA;EACE,4BAA4B;EAC5B,mBAAmB;AACrB;;AAEA;EACE;IACE,YAAY;;IAEZ;MACE,WAAW;IACb;;IAEA;MACE,YAAY;IACd;EACF;;EAEA;IACE,cAAc;EAChB;AACF", "names": Array [], + "sourceRoot": "", "sources": Array [ - "xxx", + "fixtures/css/style.css", ], "sourcesContent": Array [ - "a { color: coral } + "a { + color: black; +} + +a { + color: red; +} + +a { + color: green; +} + +a { + color: blue; +} + +.class { + -x-border-color: blue blue *; + -x-color: * #fafafa; +} + +.class-foo { + -z-border-color: blue blue *; + -z-color: * #fafafa; +} + +.phone { + &_title { + width: 500px; + + @media (max-width: 500px) { + width: auto; + } + + body.is_dark & { + color: white; + } + } + + img { + display: block; + } +} ", ], "version": 3, } `; -exports[`Options Sourcemap should work with prev sourceMap (sass-loader): warnings 1`] = `Array []`; +exports[`"sourceMap" option should generate source maps when value is not specified and the "devtool" option has "source-map" value: warnings 1`] = `Array []`; + +exports[`"sourceMap" option should not generate source maps when value has "false" value and the "devtool" option has "false" value: css 1`] = ` +"a { + color: black; +} + +a { + color: red; +} + +a { + color: green; +} + +a { + color: blue; +} + +.class { + -x-border-color: blue blue *; + -x-color: * #fafafa; +} + +.class-foo { + -z-border-color: blue blue *; + -z-color: * #fafafa; +} + +.phone { + &_title { + width: 500px; + + @media (max-width: 500px) { + width: auto; + } + + body.is_dark & { + color: white; + } + } + + img { + display: block; + } +} +" +`; + +exports[`"sourceMap" option should not generate source maps when value has "false" value and the "devtool" option has "false" value: errors 1`] = `Array []`; + +exports[`"sourceMap" option should not generate source maps when value has "false" value and the "devtool" option has "false" value: warnings 1`] = `Array []`; + +exports[`"sourceMap" option should not generate source maps when value has "false" value and the "devtool" option has "source-map" value: css 1`] = ` +"a { + color: black; +} + +a { + color: red; +} + +a { + color: green; +} + +a { + color: blue; +} + +.class { + -x-border-color: blue blue *; + -x-color: * #fafafa; +} + +.class-foo { + -z-border-color: blue blue *; + -z-color: * #fafafa; +} + +.phone { + &_title { + width: 500px; + + @media (max-width: 500px) { + width: auto; + } + + body.is_dark & { + color: white; + } + } + + img { + display: block; + } +} +" +`; + +exports[`"sourceMap" option should not generate source maps when value has "false" value and the "devtool" option has "source-map" value: errors 1`] = `Array []`; + +exports[`"sourceMap" option should not generate source maps when value has "false" value and the "devtool" option has "source-map" value: warnings 1`] = `Array []`; + +exports[`"sourceMap" option should not generate source maps when value is not specified and the "devtool" option has "source-map" value: css 1`] = ` +"a { + color: black; +} + +a { + color: red; +} + +a { + color: green; +} + +a { + color: blue; +} + +.class { + -x-border-color: blue blue *; + -x-color: * #fafafa; +} + +.class-foo { + -z-border-color: blue blue *; + -z-color: * #fafafa; +} + +.phone { + &_title { + width: 500px; + + @media (max-width: 500px) { + width: auto; + } + + body.is_dark & { + color: white; + } + } + + img { + display: block; + } +} +" +`; + +exports[`"sourceMap" option should not generate source maps when value is not specified and the "devtool" option has "source-map" value: errors 1`] = `Array []`; + +exports[`"sourceMap" option should not generate source maps when value is not specified and the "devtool" option has "source-map" value: warnings 1`] = `Array []`; diff --git a/test/options/__snapshots__/stringifier.test.js.snap b/test/options/__snapshots__/stringifier.test.js.snap index d1381c82..2c041de3 100644 --- a/test/options/__snapshots__/stringifier.test.js.snap +++ b/test/options/__snapshots__/stringifier.test.js.snap @@ -12,7 +12,49 @@ Loading PostCSS \\"unresolved\\" stringifier failed: Cannot find module 'unresol exports[`Options Stringifier should emit error Stringifier: warnings 1`] = `Array []`; exports[`Options Stringifier should work Stringifier - {Function}: css 1`] = ` -"
a { color: black }
+"
a {
+  color: black;
+}
+
+a {
+  color: red;
+}
+
+a {
+  color: green;
+}
+
+a {
+  color: blue;
+}
+
+.class {
+  -x-border-color: blue blue *;
+  -x-color: * #fafafa;
+}
+
+.class-foo {
+  -z-border-color: blue blue *;
+  -z-color: * #fafafa;
+}
+
+.phone {
+  _title {
+    width: 500px;
+
+    @media (max-width: 500px) {
+      width: auto;
+    }
+
+    body.is_dark  {
+      color: white;
+    }
+  }
+
+  img {
+    display: block;
+  }
+}
 
" `; @@ -21,7 +63,38 @@ exports[`Options Stringifier should work Stringifier - {Function}: errors 1`] = exports[`Options Stringifier should work Stringifier - {Function}: warnings 1`] = `Array []`; exports[`Options Stringifier should work Stringifier - {Object}: css 1`] = ` -"a color: black +"a + color: black + +a + color: red + +a + color: green + +a + color: blue + +.class + -x-border-color: blue blue * + -x-color: * #fafafa + +.class-foo + -z-border-color: blue blue * + -z-color: * #fafafa + +.phone + &_title + width: 500px + + @media (max-width: 500px) + width: auto + + body.is_dark & + color: white + + img + display: block " `; @@ -30,7 +103,38 @@ exports[`Options Stringifier should work Stringifier - {Object}: errors 1`] = `A exports[`Options Stringifier should work Stringifier - {Object}: warnings 1`] = `Array []`; exports[`Options Stringifier should work Stringifier - {String}: css 1`] = ` -"a color: black +"a + color: black + +a + color: red + +a + color: green + +a + color: blue + +.class + -x-border-color: blue blue * + -x-color: * #fafafa + +.class-foo + -z-border-color: blue blue * + -z-color: * #fafafa + +.phone + &_title + width: 500px + + @media (max-width: 500px) + width: auto + + body.is_dark & + color: white + + img + display: block " `; diff --git a/test/options/plugins.test.js b/test/options/plugins.test.js index 244d258a..8566d904 100644 --- a/test/options/plugins.test.js +++ b/test/options/plugins.test.js @@ -6,16 +6,51 @@ import { getWarnings, } from '../helpers/index'; -describe('Options Plugins', () => { - it('should work Plugins - {Array}', async () => { +import myPostcssPlugin from '../fixtures/plugin/plugin'; + +describe('"plugins" option', () => { + it('should work with "Array"', async () => { const compiler = getCompiler('./css/index.js', { postcssOptions: { - // eslint-disable-next-line global-require - plugins: [require('../fixtures/config-scope/config/plugin')()], + plugins: [ + 'postcss-nested', + ['postcss-short', { prefix: 'x' }], + myPostcssPlugin, + // Like: + // ` + // import myPlugin from './path/to/plugin.mjs'; + // + // const initPlugin = myPlugin(); + // ` + (root) => { + root.walkDecls((decl) => { + if (decl.value === 'red') { + // eslint-disable-next-line no-param-reassign + decl.value = 'rgba(255, 0, 0, 1.0)'; + } + }); + }, + // Like: + // ` + // import myPlugin from './path/to/plugin.mjs'; + // ` + { + postcss: (root) => { + root.walkDecls((decl) => { + if (decl.value === 'green') { + // eslint-disable-next-line no-param-reassign + decl.value = 'rgba(0, 255, 0, 1.0)'; + } + }); + }, + }, + require.resolve('../fixtures/plugin/other-plugin'), + myPostcssPlugin({ color: 'white', alpha: 0 }), + { 'postcss-short': { prefix: 'z' } }, + ], }, }); const stats = await compile(compiler); - const codeFromBundle = getCodeFromBundle('style.css', stats); expect(codeFromBundle.css).toMatchSnapshot('css'); @@ -23,11 +58,15 @@ describe('Options Plugins', () => { expect(getErrors(stats)).toMatchSnapshot('errors'); }); - it('should work Plugins - {Object}', async () => { + it('should work with "Object"', async () => { const compiler = getCompiler('./css/index.js', { postcssOptions: { - // eslint-disable-next-line global-require - plugins: require('../fixtures/config-scope/config/plugin'), + plugins: { + 'postcss-import': {}, + 'postcss-nested': {}, + 'postcss-short': { prefix: 'x' }, + [require.resolve('../fixtures/plugin/other-plugin')]: {}, + }, }, }); const stats = await compile(compiler); @@ -39,11 +78,10 @@ describe('Options Plugins', () => { expect(getErrors(stats)).toMatchSnapshot('errors'); }); - it('should work Plugins - {Function} - {Array}', async () => { + it('should work with empty "Array"', async () => { const compiler = getCompiler('./css/index.js', { postcssOptions: { - // eslint-disable-next-line global-require - plugins: () => [require('../fixtures/config-scope/config/plugin')()], + plugins: [], }, }); const stats = await compile(compiler); @@ -55,11 +93,10 @@ describe('Options Plugins', () => { expect(getErrors(stats)).toMatchSnapshot('errors'); }); - it('should work Plugins - {Function} - {Object}', async () => { + it('should work with empty "Object"', async () => { const compiler = getCompiler('./css/index.js', { postcssOptions: { - // eslint-disable-next-line global-require - plugins: () => require('../fixtures/config-scope/config/plugin')(), + plugins: {}, }, }); const stats = await compile(compiler); @@ -71,12 +108,12 @@ describe('Options Plugins', () => { expect(getErrors(stats)).toMatchSnapshot('errors'); }); - it('should work Plugins - {Object without require}', async () => { + it('should work with "Object" and support disabling plugins from the configuration', async () => { const compiler = getCompiler('./css/index.js', { postcssOptions: { + config: 'test/fixtures/css/plugins.config.js', plugins: { - 'postcss-import': {}, - 'postcss-nested': {}, + 'postcss-short': false, }, }, }); @@ -89,111 +126,42 @@ describe('Options Plugins', () => { expect(getErrors(stats)).toMatchSnapshot('errors'); }); - it('should work Plugins - {empty Object}', async () => { + it('should work with "Object" and only disabled plugins', async () => { const compiler = getCompiler('./css/index.js', { - postcssOptions: { - plugins: {}, - }, - }); - const stats = await compile(compiler); - - const codeFromBundle = getCodeFromBundle('style.css', stats); - - expect(codeFromBundle.css).toMatchSnapshot('css'); - expect(getWarnings(stats)).toMatchSnapshot('warnings'); - expect(getErrors(stats)).toMatchSnapshot('errors'); - }); - - it('should work Plugins - {Object without require} + options', async () => { - const compiler = getCompiler('./css/index2.js', { postcssOptions: { plugins: { - 'postcss-short': { prefix: 'x' }, + 'postcss-import': false, + 'postcss-nested': false, + 'postcss-short': false, }, }, }); const stats = await compile(compiler); - const codeFromBundle = getCodeFromBundle('style2.css', stats); - - expect(codeFromBundle.css).toMatchSnapshot('css'); - expect(getWarnings(stats)).toMatchSnapshot('warnings'); - expect(getErrors(stats)).toMatchSnapshot('errors'); - }); - - it('should work Plugins - {Object} + options', async () => { - const compiler = getCompiler('./css/index2.js', { - postcssOptions: { - // eslint-disable-next-line global-require - plugins: require('postcss-short')({ prefix: 'x' }), - }, - }); - const stats = await compile(compiler); - - const codeFromBundle = getCodeFromBundle('style2.css', stats); - - expect(codeFromBundle.css).toMatchSnapshot('css'); - expect(getWarnings(stats)).toMatchSnapshot('warnings'); - expect(getErrors(stats)).toMatchSnapshot('errors'); - }); - - it('should work Plugins - { Array } Array + options', async () => { - const compiler = getCompiler('./css/index2.js', { - plugins: [ - 'postcss-import', - ['postcss-nested'], - ['postcss-short', { prefix: 'x' }], - ], - }); - const stats = await compile(compiler); - - const codeFromBundle = getCodeFromBundle('style2.css', stats); - - expect(codeFromBundle.css).toMatchSnapshot('css'); - expect(getWarnings(stats)).toMatchSnapshot('warnings'); - expect(getErrors(stats)).toMatchSnapshot('errors'); - }); - - it('should work Plugins - {Array} + options', async () => { - const compiler = getCompiler('./css/index2.js', { - postcssOptions: { - // eslint-disable-next-line global-require - plugins: [require('postcss-short')({ prefix: 'x' })], - }, - }); - const stats = await compile(compiler); - - const codeFromBundle = getCodeFromBundle('style2.css', stats); + const codeFromBundle = getCodeFromBundle('style.css', stats); expect(codeFromBundle.css).toMatchSnapshot('css'); expect(getWarnings(stats)).toMatchSnapshot('warnings'); expect(getErrors(stats)).toMatchSnapshot('errors'); }); - it('should disables plugin from config', async () => { - const compiler = getCompiler('./css/index2.js', { - config: 'test/fixtures/css/plugins.config.js', + it('should throw an error on the unresolved plugin', async () => { + const compiler = getCompiler('./css/index.js', { postcssOptions: { - plugins: { - 'postcss-short': false, - }, + plugins: ['postcss-unresolved'], }, }); const stats = await compile(compiler); - const codeFromBundle = getCodeFromBundle('style2.css', stats); - - expect(codeFromBundle.css).toMatchSnapshot('css'); expect(getWarnings(stats)).toMatchSnapshot('warnings'); - expect(getErrors(stats)).toMatchSnapshot('errors'); + expect(getErrors(stats, true)).toMatchSnapshot('errors'); }); - it('should emit error on load plugin', async () => { - const compiler = getCompiler('./css/index2.js', { + it('should work with "Array" and not throw an error on falsy plugin', async () => { + const compiler = getCompiler('./css/index.js', { postcssOptions: { - plugins: { - 'postcss-unresolved': {}, - }, + // eslint-disable-next-line no-undefined + plugins: [undefined, null, '', 0], }, }); const stats = await compile(compiler); diff --git a/test/options/postcssOptins.test.js b/test/options/postcssOptins.test.js index e9905437..77492ea4 100644 --- a/test/options/postcssOptins.test.js +++ b/test/options/postcssOptins.test.js @@ -24,9 +24,11 @@ describe('"postcssOptions" option', () => { false ); - const toIsWork = notNormalizecodeFromBundle.map.file.endsWith('to.css'); + const toIsWork = notNormalizecodeFromBundle.sourceMap.file.endsWith( + 'to.css' + ); const fromIsWork = - notNormalizecodeFromBundle.map.sources.filter((i) => + notNormalizecodeFromBundle.sourceMap.sources.filter((i) => i.endsWith('from.css') ).length > 0; @@ -53,4 +55,57 @@ describe('"postcssOptions" option', () => { expect(getWarnings(stats)).toMatchSnapshot('warnings'); expect(getErrors(stats)).toMatchSnapshot('errors'); }); + + it('should work when the "postcssOptions" option is "Function"', async () => { + const compiler = getCompiler('./css/index.js', { + postcssOptions: () => { + return { + // eslint-disable-next-line global-require + plugins: [require('../fixtures/config-scope/config/plugin')()], + }; + }, + }); + const stats = await compile(compiler); + + const codeFromBundle = getCodeFromBundle('style.css', stats); + + expect(codeFromBundle.css).toMatchSnapshot('css'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + }); + + it('should work when the "postcssOptions" option is "Function" and the "plugins" option is "Array"', async () => { + const compiler = getCompiler('./css/index.js', { + postcssOptions: () => { + return { + // eslint-disable-next-line global-require + plugins: [require('../fixtures/config-scope/config/plugin')()], + }; + }, + }); + const stats = await compile(compiler); + const codeFromBundle = getCodeFromBundle('style.css', stats); + + expect(codeFromBundle.css).toMatchSnapshot('css'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + }); + + it('should work when the "postcssOptions" option is "Function" and the "plugins" option is "Object"', async () => { + const compiler = getCompiler('./css/index.js', { + postcssOptions: () => { + return { + // eslint-disable-next-line global-require + plugins: [require('../fixtures/config-scope/config/plugin')()], + }; + }, + }); + const stats = await compile(compiler); + + const codeFromBundle = getCodeFromBundle('style.css', stats); + + expect(codeFromBundle.css).toMatchSnapshot('css'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + }); }); diff --git a/test/options/sourceMap.test.js b/test/options/sourceMap.test.js index f37683ca..231e6971 100644 --- a/test/options/sourceMap.test.js +++ b/test/options/sourceMap.test.js @@ -3,6 +3,7 @@ */ import path from 'path'; +import fs from 'fs'; import { compile, @@ -12,40 +13,141 @@ import { getWarnings, } from '../helpers/index'; -describe('Options Sourcemap', () => { - it('should work Sourcemap - {Boolean}', async () => { +describe('"sourceMap" option', () => { + it('should generate source maps when value has "true" value and the "devtool" option has "false" value', async () => { const compiler = getCompiler( './css/index.js', - { sourceMap: true }, - { devtool: 'source-map' } + { + sourceMap: true, + }, + { + devtool: false, + } + ); + const stats = await compile(compiler); + const { css, sourceMap } = getCodeFromBundle('style.css', stats); + + sourceMap.sourceRoot = ''; + sourceMap.sources = sourceMap.sources.map((source) => { + expect(path.isAbsolute(source)).toBe(true); + expect(source).toBe(path.normalize(source)); + expect(fs.existsSync(path.resolve(sourceMap.sourceRoot, source))).toBe( + true + ); + + return path + .relative(path.resolve(__dirname, '..'), source) + .replace(/\\/g, '/'); + }); + + expect(css).toMatchSnapshot('css'); + expect(sourceMap).toMatchSnapshot('source map'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + }); + + it('should generate source maps when value has "true" value and the "devtool" option has "source-map" value', async () => { + const compiler = getCompiler( + './css/index.js', + { + sourceMap: true, + }, + { + devtool: 'source-map', + } ); const stats = await compile(compiler); + const { css, sourceMap } = getCodeFromBundle('style.css', stats); + + sourceMap.sourceRoot = ''; + sourceMap.sources = sourceMap.sources.map((source) => { + expect(path.isAbsolute(source)).toBe(true); + expect(source).toBe(path.normalize(source)); + expect(fs.existsSync(path.resolve(sourceMap.sourceRoot, source))).toBe( + true + ); - const codeFromBundle = getCodeFromBundle('style.css', stats); + return path + .relative(path.resolve(__dirname, '..'), source) + .replace(/\\/g, '/'); + }); - expect(codeFromBundle.css).toMatchSnapshot('css'); - expect(codeFromBundle.map).toMatchSnapshot('map'); + expect(css).toMatchSnapshot('css'); + expect(sourceMap).toMatchSnapshot('source map'); expect(getWarnings(stats)).toMatchSnapshot('warnings'); expect(getErrors(stats)).toMatchSnapshot('errors'); }); - it('should work disable Sourcemap - {Boolean}', async () => { + it('should generate source maps when value is not specified and the "devtool" option has "source-map" value', async () => { const compiler = getCompiler( './css/index.js', - { sourceMap: false }, - { devtool: 'source-map' } + {}, + { + devtool: 'source-map', + } ); const stats = await compile(compiler); + const { css, sourceMap } = getCodeFromBundle('style.css', stats); - const codeFromBundle = getCodeFromBundle('style.css', stats); + sourceMap.sourceRoot = ''; + sourceMap.sources = sourceMap.sources.map((source) => { + expect(path.isAbsolute(source)).toBe(true); + expect(source).toBe(path.normalize(source)); + expect(fs.existsSync(path.resolve(sourceMap.sourceRoot, source))).toBe( + true + ); - expect(codeFromBundle.css).toMatchSnapshot('css'); - expect(codeFromBundle.map).toBeUndefined(); + return path + .relative(path.resolve(__dirname, '..'), source) + .replace(/\\/g, '/'); + }); + + expect(css).toMatchSnapshot('css'); + expect(sourceMap).toMatchSnapshot('source map'); expect(getWarnings(stats)).toMatchSnapshot('warnings'); expect(getErrors(stats)).toMatchSnapshot('errors'); }); - it('should work with prev sourceMap (sass-loader)', async () => { + it('should generate source maps when value has "false" value, but the "postcssOptions.map" has the "true" value', async () => { + const compiler = getCompiler( + './css/index.js', + { + postcssOptions: { + map: { + inline: false, + annotation: false, + prev: false, + sourcesContent: true, + }, + }, + }, + { + devtool: false, + } + ); + const stats = await compile(compiler); + const { css, sourceMap } = getCodeFromBundle('style.css', stats); + + sourceMap.sourceRoot = ''; + sourceMap.sources = sourceMap.sources.map((source) => { + expect(path.isAbsolute(source)).toBe(false); + expect(source).toBe(path.normalize(source)); + expect( + fs.existsSync(path.resolve(__dirname, '../fixtures/css', source)) + ).toBe(true); + + return path + .relative(path.resolve(__dirname, '..'), source) + .replace(/\\/g, '/'); + }); + + expect(css).toMatchSnapshot('css'); + expect(sourceMap).toMatchSnapshot('source map'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + }); + + it('should generate source maps when previous loader return source maps ("sass-loader")', async () => { const compiler = getCompiler( './scss/index.js', {}, @@ -78,16 +180,28 @@ describe('Options Sourcemap', () => { } ); const stats = await compile(compiler); + const { css, sourceMap } = getCodeFromBundle('style.scss', stats); + + sourceMap.sourceRoot = ''; + sourceMap.sources = sourceMap.sources.map((source) => { + expect(path.isAbsolute(source)).toBe(true); + expect(source).toBe(path.normalize(source)); + expect(fs.existsSync(path.resolve(sourceMap.sourceRoot, source))).toBe( + true + ); - const codeFromBundle = getCodeFromBundle('style.scss', stats); + return path + .relative(path.resolve(__dirname, '..'), source) + .replace(/\\/g, '/'); + }); - expect(codeFromBundle.css).toMatchSnapshot('css'); - expect(codeFromBundle.map).toMatchSnapshot('map'); + expect(css).toMatchSnapshot('css'); + expect(sourceMap).toMatchSnapshot('source map'); expect(getWarnings(stats)).toMatchSnapshot('warnings'); expect(getErrors(stats)).toMatchSnapshot('errors'); }); - it('should work with prev sourceMap (less-loader)', async () => { + it('should generate source maps when previous loader return source maps ("less-loader")', async () => { const compiler = getCompiler( './less/index.js', { @@ -117,46 +231,78 @@ describe('Options Sourcemap', () => { } ); const stats = await compile(compiler); + const { css, sourceMap } = getCodeFromBundle('style.less', stats); - const codeFromBundle = getCodeFromBundle('style.less', stats); + sourceMap.sourceRoot = ''; + sourceMap.sources = sourceMap.sources.map((source) => { + expect(path.isAbsolute(source)).toBe(true); + expect(source).toBe(path.normalize(source)); + expect(fs.existsSync(path.resolve(sourceMap.sourceRoot, source))).toBe( + true + ); + + return path + .relative(path.resolve(__dirname, '..'), source) + .replace(/\\/g, '/'); + }); - expect(codeFromBundle.css).toMatchSnapshot('css'); - expect(codeFromBundle.map).toMatchSnapshot('map'); + expect(css).toMatchSnapshot('css'); + expect(sourceMap).toMatchSnapshot('source map'); expect(getWarnings(stats)).toMatchSnapshot('warnings'); expect(getErrors(stats)).toMatchSnapshot('errors'); }); - it('should generated absolute paths in sourcemap', async () => { - const compiler = getCompiler('./css/index.js', { - sourceMap: true, - }); + it('should not generate source maps when value has "false" value and the "devtool" option has "false" value', async () => { + const compiler = getCompiler( + './css/index.js', + { + sourceMap: false, + }, + { + devtool: false, + } + ); const stats = await compile(compiler); + const { css, sourceMap } = getCodeFromBundle('style.css', stats); - const codeFromBundle = getCodeFromBundle('style.css', stats); - const notNormalizecodeFromBundle = getCodeFromBundle( - 'style.css', - stats, - false - ); + expect(css).toMatchSnapshot('css'); + expect(sourceMap).toBeUndefined(); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + }); - const { sources } = notNormalizecodeFromBundle.map; - const expectedFile = path.resolve( - __dirname, - '..', - 'fixtures', - 'css', - 'style.css' + it('should not generate source maps when value has "false" value and the "devtool" option has "source-map" value', async () => { + const compiler = getCompiler( + './css/index.js', + { + sourceMap: false, + }, + { + devtool: 'source-map', + } ); + const stats = await compile(compiler); + const { css, sourceMap } = getCodeFromBundle('style.css', stats); - const normalizePath = (src) => - path.sep === '\\' ? src.replace(/\\/g, '/') : src; + expect(css).toMatchSnapshot('css'); + expect(sourceMap).toBeUndefined(); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + }); - sources.forEach((source) => - expect(source).toEqual(normalizePath(expectedFile)) + it('should not generate source maps when value is not specified and the "devtool" option has "source-map" value', async () => { + const compiler = getCompiler( + './css/index.js', + {}, + { + devtool: false, + } ); + const stats = await compile(compiler); + const { css, sourceMap } = getCodeFromBundle('style.css', stats); - expect(codeFromBundle.css).toMatchSnapshot('css'); - expect(codeFromBundle.map).toMatchSnapshot('map'); + expect(css).toMatchSnapshot('css'); + expect(sourceMap).toBeUndefined(); 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 8269cc18..18248c66 100644 --- a/test/validate-options.test.js +++ b/test/validate-options.test.js @@ -19,12 +19,17 @@ describe('validate options', () => { { stringifier: 'sugarss' }, { stringifier: require('sugarss') }, { stringifier: require('sugarss').stringify }, - { plugins: () => require('./fixtures/config-scope/config/plugin')() }, - { plugins: () => [require('./fixtures/config-scope/config/plugin')()] }, { plugins: [ - require('./fixtures/config-scope/config/plugin')(), - require('./fixtures/config-scope/config/plugin'), + require('./fixtures/plugin/plugin')(), + require('./fixtures/plugin/plugin'), + ['postcss-short', { prefix: 'x' }], + ], + }, + { + plugins: [ + require('./fixtures/plugin/plugin')(), + require('./fixtures/plugin/plugin'), { 'postcss-short': { prefix: 'x' } }, ], }, @@ -57,6 +62,11 @@ describe('validate options', () => { { plugins: 1 }, { plugins: true }, { plugins: 'postcss-short' }, + { + plugins: () => { + return []; + }, + }, { config: [] }, ], },