let { readFileSync } = require('fs') let { restoreAll, spyOn } = require('nanospy') let { join } = require('path') let postcss = require('postcss') let { test } = require('uvu') let { equal, match, not, ok, throws, type } = require('uvu/assert') let autoprefixer = require('..') let grider = autoprefixer({ cascade: false, grid: 'autoplace', overrideBrowserslist: ['Chrome 25', 'Edge 12', 'IE 10'] }) let cleaner = autoprefixer({ overrideBrowserslist: [] }) let compiler = autoprefixer({ overrideBrowserslist: ['Chrome 25', 'Opera 12'] }) let filterer = autoprefixer({ overrideBrowserslist: ['Chrome 25', 'Safari 9', 'Firefox 39'] }) let borderer = autoprefixer({ overrideBrowserslist: ['Safari 4', 'Firefox 3.6'] }) let cascader = autoprefixer({ cascade: true, overrideBrowserslist: ['Chrome > 19', 'Firefox 21', 'IE 10'] }) let keyframer = autoprefixer({ overrideBrowserslist: ['Chrome > 19', 'Opera 12'] }) let flexboxer = autoprefixer({ overrideBrowserslist: ['Chrome > 19', 'Firefox 21', 'IE 10'] }) let without3d = autoprefixer({ overrideBrowserslist: ['Opera 12', 'IE > 0'] }) let supporter = autoprefixer({ overrideBrowserslist: ['Chrome 25', 'Chrome 28', 'IE > 0'] }) let uncascader = autoprefixer({ overrideBrowserslist: ['Firefox 15'] }) let gradienter = autoprefixer({ overrideBrowserslist: ['Chrome 25', 'Opera 12', 'Android 2.3'] }) let grouping = autoprefixer({ grid: 'autoplace', overrideBrowserslist: ['Chrome 25', 'Firefox > 17', 'IE 10', 'Edge 12'] }) let ffgradienter = autoprefixer({ overrideBrowserslist: ['Chrome 25', 'Opera 12', 'Firefox 6'] }) let selectorer = autoprefixer({ overrideBrowserslist: ['Chrome 25', 'Firefox > 17', 'IE 10', 'Edge 12'] }) let fileSelectorButtoner = autoprefixer({ overrideBrowserslist: ['Chrome > 25', 'Firefox >= 82'] }) let backdroper = autoprefixer({ overrideBrowserslist: ['IE >= 11', 'Chrome < 32', 'Safari >= 15.4'] }) let placeholderShowner = autoprefixer({ overrideBrowserslist: ['IE >= 10'] }) let transitionSpec = autoprefixer({ overrideBrowserslist: ['Chrome > 19', 'Firefox 14', 'IE 10', 'Opera 12'] }) let intrinsicer = autoprefixer({ overrideBrowserslist: ['Chrome 25', 'Firefox 22', 'Safari 10'] }) let imagerender = autoprefixer({ overrideBrowserslist: ['iOS 8', 'iOS 6.1', 'FF 22', 'IE 11', 'Opera 12'] }) let backgrounder = autoprefixer({ overrideBrowserslist: ['Firefox 3.6', 'Android 2.3'] }) let resolutioner = autoprefixer({ overrideBrowserslist: ['Safari 7', 'Opera 12', 'Firefox 15'] }) let letterer = autoprefixer({ overrideBrowserslist: ['Safari 15'] }) let overscroller = autoprefixer({ overrideBrowserslist: ['Edge 17'] }) let clipper = autoprefixer({ overrideBrowserslist: ['Safari 7', 'Edge 14'] }) let example = autoprefixer({ overrideBrowserslist: ['defaults'] }) let autofiller = autoprefixer({ overrideBrowserslist: ['Chrome > 90', 'Firefox >= 82'] }) let textDecorator = autoprefixer({ overrideBrowserslist: ['Chrome >= 57', 'Firefox >= 36', 'Safari >= 12.1'] }) let content = autoprefixer({ overrideBrowserslist: [ '> 2%', 'last 2 years', 'ie 11', 'not ie_mob > 0', 'not dead' ] }) function prefixer(name) { if ( name === 'grid' || name === 'grid-gap' || name === 'grid-area' || name === 'grid-template' || name === 'grid-template-areas' ) { return grider } else if ( name === 'filter' || name === 'advanced-filter' || name === 'element' ) { return filterer } else if ( name === 'vendor-hack' || name === 'value-hack' || name === 'mistakes' ) { return cleaner } else if ( name === 'flexbox' || name === 'flex-rewrite' || name === 'double' || name === 'viewport' || name === 'appearance' ) { return flexboxer } else if ( name === 'intrinsic' || name === 'multicolumn' || name === 'logical' || name === 'text-decoration' || name === 'at-rules' ) { return intrinsicer } else if (name === 'text-decoration-shorthand') { return textDecorator } else if (name === 'selectors' || name === 'placeholder') { return selectorer } else if (name === 'selectors' || name === 'file-selector-button') { return fileSelectorButtoner } else if (name === 'selectors' || name === 'backdrop') { return backdroper } else if ( name === 'selectors' || name === 'autofill' || name === 'print-color-adjust' ) { return autofiller } else if (name === 'placeholder-shown') { return placeholderShowner } else if (name === 'backdrop-filter' || name === 'overscroll-behavior') { return overscroller } else if (name === 'background-clip' || name === 'user-select') { return clipper } else if (name === 'image-rendering' || name === 'writing-mode') { return imagerender } else if (name === 'keyframes') { return keyframer } else if (name === 'border-radius') { return borderer } else if (name === 'gradient') { return gradienter } else if (name === 'gradient-fix') { return ffgradienter } else if (name === 'grouping-rule') { return grouping } else if (name === 'cascade') { return cascader } else if (name === '3d-transform') { return without3d } else if (name === 'background-size') { return backgrounder } else if (name === 'uncascade') { return uncascader } else if (name === 'example') { return example } else if (name === 'resolution') { return resolutioner } else if (name === 'initial-letter') { return letterer } else if (name === 'supports') { return supporter } else if (name === 'transition-spec') { return transitionSpec } else if (name === 'content') { return content } else { return compiler } } function read(name) { let file = join(__dirname, '/cases/' + name + '.css') return readFileSync(file).toString() } function universalizer(string) { return string.replace(/\r/g, '') } function check(from, instance = prefixer(from)) { let input = read(from) let output = read(from + '.out') let result = postcss([instance]).process(input) equal(result.warnings().length, 0) equal(universalizer(result.css), universalizer(output)) } const COMMONS = [ 'transition', 'values', 'keyframes', 'gradient', 'flex-rewrite', 'flexbox', 'filter', 'border-image', 'border-radius', 'notes', 'selectors', 'placeholder', 'placeholder-shown', 'fullscreen', 'intrinsic', 'mistakes', 'custom-prefix', 'cascade', 'double', 'multicolumn', '3d-transform', 'background-size', 'supports', 'viewport', 'resolution', 'logical', 'appearance', 'advanced-filter', 'element', 'image-set', 'image-rendering', 'mask-border', 'writing-mode', 'cross-fade', 'gradient-fix', 'text-emphasis-position', 'grid', 'grid-area', 'grid-template', 'grid-template-areas', 'grid-gap', 'print-color-adjust', 'initial-letter' ] test.after.each(() => { delete process.env.AUTOPREFIXER_GRID restoreAll() }) test('throws on wrong options', () => { throws(() => { autoprefixer({ browser: ['chrome 25', 'opera 12'] }) }, /overrideBrowserslist/) throws(() => { autoprefixer({ browserslist: ['chrome 25', 'opera 12'] }) }, /overrideBrowserslist/) }) let options = { cascade: false, grid: false } let browsers = ['chrome 25', 'opera 12'] test('sets options via options object', () => { let allOptions = Object.assign(options, { overrideBrowserslist: browsers }) let instance = autoprefixer(allOptions) equal(instance.options, allOptions) equal(instance.browsers, browsers) }) test('sets options via array of browsers as first argument and object', () => { let instance = autoprefixer(browsers, options) equal(instance.options, options) equal(instance.browsers, browsers) }) test('sets options via browsers as arguments and options object', () => { let instance = autoprefixer(...browsers, options) equal(instance.options, options) equal(instance.browsers, browsers) }) test('has default browsers', () => { type(autoprefixer.defaults.length, 'number') }) test('shows warning on browsers option', () => { let consoleWarn = spyOn(console, 'warn', () => {}) let instance = autoprefixer({ browsers: ['last 1 version'] }) equal(instance.browsers, ['last 1 version']) equal(consoleWarn.callCount, 1) match(consoleWarn.calls[0][0], 'overrideBrowserslist') }) test('passes statistics to Browserslist', () => { let stats = { chrome: { 10: 10, 11: 40 }, ie: { 10: 10, 11: 40 } } match( autoprefixer({ overrideBrowserslist: '> 20% in my stats', stats }).info(), /Browsers:\n\s\sChrome: 11\n\s\sIE: 11\n/ ) }) test('prefixes values', () => { check('values') }) test('prefixes @keyframes', () => { check('keyframes') }) test('prefixes @viewport', () => { check('viewport') }) test('prefixes selectors', () => { check('selectors') }) test('prefixes uppercase pseudo-elements', () => { let css = 'input::PLACEHOLDER { color: red }' let plugin = autoprefixer({ overrideBrowserslist: ['Chrome 40'] }) equal( postcss([plugin]).process(css, { from: undefined }).css, 'input::-webkit-input-placeholder { color: red }\n' + css ) }) test('prefixes mixed-case pseudo-classes without changing selector case', () => { let css = '.Field#Input[type="TEXT"]:Read-Only { color: red }' let plugin = autoprefixer({ overrideBrowserslist: ['Firefox 39'] }) equal( postcss([plugin]).process(css, { from: undefined }).css, '.Field#Input[type="TEXT"]:-moz-read-only { color: red }\n' + css ) }) test('prefixes every matching selector regardless of case', () => { let css = 'input::placeholder, textarea::PlaceHolder { color: red }' let plugin = autoprefixer({ overrideBrowserslist: ['Chrome 40'] }) equal( postcss([plugin]).process(css, { from: undefined }).css, 'input::-webkit-input-placeholder, ' + 'textarea::-webkit-input-placeholder { color: red }\n' + css ) }) test('prefixes resolution query', () => { check('resolution') }) test('does not hang on long resolution query', () => { let css = `@media (min-resolution: ${'9'.repeat(128000)}z) { a { color: red } }` let start = Date.now() let out = postcss([resolutioner]).process(css, { from: undefined }).css equal(out, css) ok(Date.now() - start < 2000) }) test('removes common mistakes', () => { check('mistakes') }) test('reads notes for prefixes', () => { check('notes') }) test('keeps vendor-specific hacks', () => { check('vendor-hack') }) test('keeps values with vendor hacks', () => { check('value-hack') }) test('works with comments', () => { check('comments') }) test('uses visual cascade', () => { check('cascade') }) test('works with properties near', () => { check('double') }) test('checks prefixed in hacks', () => { check('check-down') }) test('normalize cascade after remove', () => { check('uncascade') }) test('prefix decls in @supports', () => { check('supports') }) test('saves declaration style', () => { check('style') }) test('uses ignore next control comments', () => { check('ignore-next') }) test('uses block control comments', () => { check('disabled') }) test('has actual example in docs', () => { check('example') }) test('process grouping rules correctly', () => { check('grouping-rule') }) test('transition on vendor specific rule', () => { check('transition-spec') }) test('ignore prefix in vendor at rules', () => { check('at-rules') }) test('ignore content property', () => { let input = read('content') let result = postcss([prefixer('scope')]).process(input) equal(result.css, input) }) test('uses control comments to whole scope', () => { let input = read('scope') let output = read('scope.out') let result = postcss([prefixer('scope')]).process(input) equal(result.css, output) equal( result.warnings().map(i => i.toString()), [ 'autoprefixer: :5:3: Second Autoprefixer control comment ' + 'was ignored. Autoprefixer applies control comment to whole block, ' + 'not to next rules.' ] ) }) test('sets grid option via comment', () => { let input = read('grid-status') let output = read('grid-status.out') let ap = autoprefixer({ overrideBrowserslist: ['last 2 versions', 'IE 11'] }) let result = postcss([ap]).process(input) equal(result.css, output) equal( result.warnings().map(i => i.toString()), [ 'autoprefixer: :2:1: Second Autoprefixer grid control ' + 'comment was ignored. Autoprefixer applies control comments ' + 'to the whole block, not to the next rules.', 'autoprefixer: :20:3: Second Autoprefixer grid control ' + 'comment was ignored. Autoprefixer applies control comments ' + 'to the whole block, not to the next rules.', 'autoprefixer: :47:3: Second Autoprefixer grid control ' + 'comment was ignored. Autoprefixer applies control comments ' + 'to the whole block, not to the next rules.' ] ) }) test('prefixes transition', () => { let input = read('transition') let output = read('transition.out') let result = postcss([prefixer('transition')]).process(input) equal(result.css, output) equal( result.warnings().map(i => i.toString()), [ 'autoprefixer: :23:3: Replace transition-property ' + 'to transition, because Autoprefixer could not support any cases ' + 'of transition-property and other transition-*' ] ) }) test('does not raise unnecessary warnings when prefixing transition', () => { check('transition-no-warning') }) test('works with broken transition', () => { let input = 'a{transition:,,}' let output = 'a{-webkit-transition:;-o-transition:;transition:}' let result = postcss([prefixer('transition')]).process(input) equal(result.css, output) }) test('should ignore spaces inside values', () => { let css = read('trim') equal(postcss([flexboxer]).process(css).css, css) }) test('removes unnecessary prefixes', () => { let processor = postcss([cleaner]) for (let i of COMMONS) { if (i === 'gradient-fix') continue if (i === 'cascade') continue if (i === 'mistakes') continue if (i === 'flex-rewrite') continue if (i === 'grid') continue if (i === 'grid-gap') continue if (i === 'grid-area') continue if (i === 'grid-template') continue if (i === 'grid-template-areas') continue let input = read(i + '.out') let output = read(i) equal(processor.process(input).css, output) } }) test('media does not should nested', () => { let processor = postcss([grider]) let input = read('grid-media-rules') let output = read('grid-media-rules.out') equal(processor.process(input).css, output) }) test('does not remove unnecessary prefixes on request', () => { for (let i of ['transition', 'values', 'fullscreen']) { let keeper = autoprefixer({ overrideBrowserslist: [], remove: false }) let css = read(i + '.out') equal(postcss([keeper]).process(css).css, css) } }) test('does not add prefixes on request', () => { for (let i of ['transition', 'values', 'fullscreen']) { let remover = autoprefixer({ add: false, overrideBrowserslist: ['Opera 12'] }) let unprefixed = read(i) equal(postcss([remover]).process(unprefixed).css, unprefixed) } }) test('prevents doubling prefixes', () => { for (let i of COMMONS) { let processor = postcss([prefixer(i)]) let input = read(i) let output = read(i + '.out') let result = processor.process(processor.process(input)).css equal(universalizer(result), universalizer(output)) } }) function isContainerNode(node) { return 'nodes' in node } test('does not broke AST', () => { function checkParent(node) { node.walk(child => { type(child.parent, 'object') if (isContainerNode(child)) checkParent(child) }) } for (let i of COMMONS) { let processor = postcss([prefixer(i)]) let input = read(i) checkParent(processor.process(input).root) } }) test('parses difficult files', () => { let input = read('syntax') let result = postcss([cleaner]).process(input) equal(result.css, input) }) test('marks parsing errors', () => { throws(() => { postcss([cleaner]).process('a {').css }, ':1:1: Unclosed block') }) test('shows file name in parse error', () => { throws(() => { postcss([cleaner]).process('a {', { from: 'a.css' }).css }, /a.css:1:1: /) }) test('uses browserslist config', () => { let from = join(__dirname, 'cases/config/test.css') let input = read('config/test') let output = read('config/test.out') let processor = postcss([autoprefixer]) equal(processor.process(input, { from }).css, output) }) test('sets browserslist environment', () => { let from = join(__dirname, 'cases/config/test.css') let input = read('config/test') let output = read('config/test.production') let processor = postcss([autoprefixer({ env: 'development' })]) equal(processor.process(input, { from }).css, output) }) test('takes values from other PostCSS plugins', () => { function plugin(root) { root.walkDecls(i => { i.value = 'calc(0)' }) } let result = postcss([plugin, compiler]).process('a{width:0/**/0}') equal(result.css, 'a{width:-webkit-calc(0);width:calc(0)}') }) test('has option to disable @supports support', () => { let css = '@supports (cursor: grab) {}' let instance = autoprefixer({ overrideBrowserslist: ['Chrome 28'], supports: false }) let result = postcss([instance]).process(css) equal(result.css, css) }) test('has disabled grid options by default', () => { let ap = autoprefixer({ overrideBrowserslist: ['Edge 12', 'IE 10'] }) let input = read('grid') let output = read('grid.disabled') let result = postcss([ap]).process(input) equal(result.css, output) }) test('has different outputs for different grid options', () => { function ap(gridValue) { return autoprefixer({ grid: gridValue, overrideBrowserslist: ['Edge 12', 'IE 10'] }) } let input = read('grid-options') let outputAutoplace = read('grid-options.autoplace.out') let outputNoAutoplace = read('grid-options.no-autoplace.out') let outputDisabled = read('grid-options.disabled.out') let resultAutoplace = postcss([ap('autoplace')]).process(input).css let resultNoAutoplace = postcss([ap('no-autoplace')]).process(input).css let resultEnabled = postcss([ap(true)]).process(input).css let resultDisabled = postcss([ap(false)]).process(input).css // output for grid: 'autoplace' equal(resultAutoplace, outputAutoplace) // output for grid: 'no-autoplace' equal(resultNoAutoplace, outputNoAutoplace) // output for grid: true is the same as for 'no-autoplace' equal(resultEnabled, outputNoAutoplace) // output for grid: false equal(resultDisabled, outputDisabled) }) test('has different outputs for different grid environment variables', () => { function ap(gridValue) { process.env.AUTOPREFIXER_GRID = gridValue return autoprefixer({ overrideBrowserslist: ['Edge 12', 'IE 10'] }) } let input = read('grid-options') let outputAutoplace = read('grid-options.autoplace.out') let outputNoAutoplace = read('grid-options.no-autoplace.out') let resultAutoplace = postcss([ap('autoplace')]).process(input).css equal(resultAutoplace, outputAutoplace) let resultNoAutoplace = postcss([ap('no-autoplace')]).process(input).css equal(resultNoAutoplace, outputNoAutoplace) }) test('has option to disable flexbox support', () => { let css = read('flexbox') let instance = autoprefixer({ flexbox: false, overrideBrowserslist: ['IE 10'] }) let result = postcss([instance]).process(css) equal(result.css, css) }) test('has option to disable 2009 flexbox support', () => { let ap = autoprefixer({ flexbox: 'no-2009', overrideBrowserslist: ['Chrome > 19'] }) let css = 'a{flex:1;transition:flex}' let result = postcss([ap]).process(css) equal( result.css, 'a{' + '-webkit-flex:1;flex:1;' + '-webkit-transition:-webkit-flex;transition:-webkit-flex;' + 'transition:flex;transition:flex, -webkit-flex' + '}' ) }) test('returns inspect string', () => { match( autoprefixer({ overrideBrowserslist: ['chrome 25'] }).info(), /Browsers:\s+Chrome: 25/ ) }) test('uses browserslist config in inspect', () => { let from = join(__dirname, 'cases/config') match(autoprefixer().info({ from }), /Browsers:\s+IE: 10/) }) test('ignores unknown versions on request', () => { throws(() => { autoprefixer({ overrideBrowserslist: ['ie 100'] }).info() }, /Unknown version 100 of ie/) not.throws(() => { autoprefixer({ ignoreUnknownVersions: true, overrideBrowserslist: ['ie 100'] }).info() }) }) test('works with CSS Modules', () => { postcss([autoprefixer()]).process(':export { selectors: _1q6ho_2 }').css }) test('ignores prefix IE filter', () => { check('filter') }) test('supports webkit filters', () => { check('advanced-filter') }) test('changes border image syntax', () => { check('border-image') }) test('supports old Mozilla prefixes', () => { check('border-radius') }) test('supports all flexbox syntaxes', () => { check('flexbox') }) test('supports map flexbox props', () => { check('flex-rewrite') }) test('supports all fullscreens', () => { check('fullscreen') }) test('supports file-selector-button', () => { check('file-selector-button') }) test('supports ::backdrop', () => { check('backdrop') }) test('supports custom prefixes', () => { check('custom-prefix') }) test('fixes break properties', () => { check('multicolumn') }) test('ignores some 3D transforms', () => { check('3d-transform') }) test('supports background-size', () => { check('background-size') }) test('supports background-clip', () => { check('background-clip') }) test('supports logical properties', () => { check('logical') }) test('supports appearance', () => { check('appearance') }) test('supports all placeholders', () => { check('placeholder') }) test('supports placeholder-shown', () => { check('placeholder-shown') }) test('supports image-rendering', () => { check('image-rendering') }) test('supports border-box mask', () => { check('mask-border') }) test('supports mask-composite', () => { check('mask-composite') }) test('supports mask-position-x and mask-position-y', () => { check('mask-position') }) test('supports image-set()', () => { check('image-set') }) test('supports writing-mode', () => { check('writing-mode') }) test('supports cross-fade()', () => { check('cross-fade') }) test('ignores modern direction', () => { check('animation') }) test('supports overscroll-behavior', () => { check('overscroll-behavior') }) test('supports print-color-adjust', () => { let input = read('print-color-adjust') let output = read('print-color-adjust.out') let result = postcss([prefixer('print-color-adjust')]).process(input) equal(result.css, output) equal( result.warnings().map(i => i.toString()), [ 'autoprefixer: :2:3: Replace color-adjust ' + 'to print-color-adjust. The color-adjust shorthand ' + 'is currently deprecated.' ] ) }) test('supports backdrop-filter', () => { check('backdrop-filter') }) test('supports initial-letter', () => { check('initial-letter') }) test('supports user-select hack for IE', () => { check('user-select') }) test('supports appearance for IE', () => { let instance = autoprefixer({ overrideBrowserslist: 'Edge 15' }) let result = postcss([instance]).process('a { appearance: none }') equal(result.css, 'a { -webkit-appearance: none; appearance: none }') }) test('changes angle in gradient', () => { let input = read('gradient') let output = read('gradient.out') let result = postcss([prefixer('gradient')]).process(input) equal(result.css, output) equal( result.warnings().map(i => i.toString()), [ 'autoprefixer: :18:3: Gradient has outdated direction ' + 'syntax. New syntax is like `closest-side at 0 0` instead of ' + '`0 0, closest-side`.', 'autoprefixer: :38:3: Gradient has outdated direction ' + 'syntax. New syntax is like `to left` instead of `right`.', 'autoprefixer: :100:3: Gradient has outdated ' + 'direction syntax. Replace `cover` to `farthest-corner`.', 'autoprefixer: :104:3: Gradient has outdated ' + 'direction syntax. Replace `contain` to `closest-side`.' ] ) check('gradient-fix') }) test('skips old webkit gradient for CSS variables', () => { let input = 'a { background: linear-gradient(var(--direction), red, blue); }' let result = postcss([gradienter]).process(input) // Should not generate -webkit-gradient() when CSS variables are present equal(result.css.includes('-webkit-gradient('), false) // Should still generate -webkit-linear-gradient() equal(result.css.includes('-webkit-linear-gradient('), true) }) test('skips old webkit gradients with color hints', () => { let values = [ 'linear-gradient(red 10%, 30%, blue 90%)', 'linear-gradient(to top, red 10%, 30%, blue 90%)', 'linear-gradient(red, 0, blue)', 'linear-gradient(red, 2em, blue)', 'linear-gradient(red, calc(20% + 10%), blue)', 'linear-gradient(red, min(20%, 30%), blue)', 'linear-gradient(red, max(20%, 30%), blue)', 'linear-gradient(red, clamp(20%, 30%, 40%), blue)', 'linear-gradient(red, /* hint */ 30% /* end */, blue)' ] for (let value of values) { let input = `a { background: ${value}; }` let result = postcss([gradienter]).process(input) equal(result.css.includes('-webkit-gradient('), false) equal(result.css.includes('-webkit-linear-gradient('), true) equal(result.css.includes(value), true) } }) test('keeps old webkit gradients with ordinary color stops', () => { let input = 'a { background: linear-gradient(to top, rgba(0,0,0,0.5) 10%, ' + 'hsl(0,50%,50%) 30%, blue 90%); }' let result = postcss([gradienter]).process(input) equal( result.root.first.first.value, '-webkit-gradient(linear, left bottom, left top, ' + 'color-stop(10%, rgba(0,0,0,0.5)), ' + 'color-stop(30%, hsl(0,50%,50%)), color-stop(90%, blue))' ) }) test('skips old webkit gradients when a background layer has a color hint', () => { let values = [ 'linear-gradient(red, blue), linear-gradient(red, 30%, blue)', 'linear-gradient(red, 30%, blue), linear-gradient(red, blue)' ] for (let value of values) { let input = `a { background: ${value}; }` let result = postcss([gradienter]).process(input) equal(result.css.includes('-webkit-gradient('), false) equal(result.css.includes(value), true) equal(result.css.includes('-webkit-linear-gradient('), true) } }) test('warns about color hints in old webkit gradients', () => { let values = [ 'linear-gradient(red 10%, 30%, blue 90%)', 'linear-gradient(red, calc(20% + 10%), blue)', 'linear-gradient(red, blue), linear-gradient(red, 30%, blue)' ] for (let value of values) { let input = `a { background: ${value} }` let result = postcss([gradienter]).process(input) equal( result.warnings().map(i => i.toString()), [ 'autoprefixer: :1:5: Gradient color hints are not ' + 'supported by old WebKit' ] ) } }) test('does not warn about gradients without color hints', () => { let values = [ 'linear-gradient(red, blue)', 'linear-gradient(to top, red 10%, blue 90%)', 'linear-gradient(red, var(--middle), blue)' ] for (let value of values) { let input = `a { background: ${value} }` let result = postcss([gradienter]).process(input) equal(result.warnings().length, 0) } }) test('does not warn about color hints without old webkit', () => { let instance = autoprefixer({ overrideBrowserslist: ['Chrome 25'] }) let input = 'a { background: linear-gradient(red, 30%, blue) }' let result = postcss([instance]).process(input) equal(result.css.includes('-webkit-linear-gradient('), true) equal(result.warnings().length, 0) }) test('does not throw on empty or malformed gradient arguments', () => { let inputs = [ 'a { background-image: linear-gradient() }', 'a { background-image: radial-gradient() }', 'a { background-image: repeating-linear-gradient() }', 'a { background-image: repeating-radial-gradient() }', 'a { background-image: linear-gradient(red,) }', 'a { background-image: linear-gradient(,red) }', 'a { background-image: linear-gradient(red,,blue) }', 'a { background-image: linear-gradient(to right, red,) }' ] for (let input of inputs) { // Would throw an uncaught TypeError before the empty-argument guards let result = postcss([gradienter]).process(input, { from: undefined }) type(result.css, 'string') } }) test('warns on old flexbox display', () => { let result = postcss([flexboxer]).process('a{ display: box; }') equal(result.css, 'a{ display: box; }') equal( result.warnings().map(i => i.toString()), [ 'autoprefixer: :1:4: You should write display: flex ' + 'by final spec instead of display: box' ] ) }) test('supports intrinsic sizing', () => { let input = read('intrinsic') let output = read('intrinsic.out') let result = postcss([prefixer('intrinsic')]).process(input) equal(result.css, output) equal( result.warnings().map(i => i.toString()), [ 'autoprefixer: :15:3: Replace fill to stretch, ' + 'because spec had been changed', 'autoprefixer: :19:3: Replace fill-available ' + 'to stretch, because spec had been changed' ] ) }) test('supports text-emphasis', () => { let input = read('text-emphasis-position') let output = read('text-emphasis-position.out') let instance = prefixer('text-emphasis-position') let result = postcss([instance]).process(input) equal(result.css, output) equal( result.warnings().map(i => i.toString()), [ 'autoprefixer: :14:3: You should use 2 values ' + 'for text-emphasis-position For example, `under left` ' + 'instead of just `under`.' ] ) }) test('supports grid layout', () => { let input = read('grid') let output = read('grid.out') let instance = prefixer('grid') let result = postcss([instance]).process(input) equal(result.css, output) equal( result.warnings().map(i => i.toString()), [ 'autoprefixer: :3:3: Autoplacement does not work ' + 'without grid-template-rows property', 'autoprefixer: :12:3: Autoplacement does not work ' + 'without grid-template-columns property', 'autoprefixer: :36:3: Can not prefix grid-column-end ' + '(grid-column-start is not found)', 'autoprefixer: :37:3: IE does not support subgrid', 'autoprefixer: :39:3: Can not implement grid-gap ' + 'without grid-template-columns', 'autoprefixer: :39:3: Can not find grid areas: ' + 'head, nav, main, foot', 'autoprefixer: :57:3: Can not implement grid-gap ' + 'without grid-template-columns', 'autoprefixer: :57:3: Can not find grid areas: a', 'autoprefixer: :65:3: Can not implement grid-gap ' + 'without grid-template-columns', 'autoprefixer: :65:3: Can not find grid areas: b', 'autoprefixer: :73:3: Can not find grid areas: c', 'autoprefixer: :81:3: Can not find grid areas: d', 'autoprefixer: :116:3: grid-column-span is not part ' + 'of final Grid Layout. Use grid-column.', 'autoprefixer: :117:3: grid-row-span is not part ' + 'of final Grid Layout. Use grid-row.', 'autoprefixer: :118:3: grid-auto-columns is not ' + 'supported by IE', 'autoprefixer: :119:3: grid-auto-rows is not ' + 'supported by IE', 'autoprefixer: :121:33: auto-fill value is not ' + 'supported by IE', 'autoprefixer: :122:30: auto-fit value is not ' + 'supported by IE', 'autoprefixer: :138:3: Please do not use ' + 'display: contents; if you have grid setting enabled', 'autoprefixer: :142:3: IE does not support align-items ' + 'on grid containers. Try using align-self on child elements instead: ' + '.warn_ie_align > * { align-self: center }', 'autoprefixer: :147:3: IE does not support justify-items ' + 'on grid containers. Try using justify-self on child elements ' + 'instead: .warn_ie_justify > * { justify-self: center }', 'autoprefixer: :152:3: IE does not support justify-content ' + 'on grid containers', 'autoprefixer: :157:3: IE does not support place-items ' + 'on grid containers. Try using place-self on child elements ' + 'instead: .warn_place_items > * { place-self: start end }', 'autoprefixer: :181:3: grid-auto-flow is not supported by IE', 'autoprefixer: :203:26: Autoprefixer currently does not ' + 'support line names. Try using grid-template-areas instead.' ] ) let input2 = read('grid-template') let output2 = read('grid-template.out') let instance2 = prefixer('grid-template') let result2 = postcss([instance2]).process(input2) equal(result2.css, output2) }) test('supports grid autoplacement', () => { let input = read('grid-autoplacement') let output = read('grid-autoplacement.out') let instance = prefixer('grid') let result = postcss([instance]).process(input) equal(result.css, output) equal( result.warnings().map(i => i.toString()), [ 'autoprefixer: :47:3: grid-auto-flow: dense ' + 'is not supported by IE', 'autoprefixer: :48:3: Autoplacement does not work ' + 'without grid-template-rows property', 'autoprefixer: :53:3: grid-auto-flow works only if grid-temp' + 'late-rows and grid-template-columns are present in the same rule', 'autoprefixer: :60:3: grid-gap only works if grid-temp' + 'late(-areas) is being used', 'autoprefixer: :64:3: Autoplacement does not work ' + 'without grid-template-rows property', 'autoprefixer: :65:3: grid-gap only works if grid-temp' + 'late(-areas) is being used or both rows and columns have been ' + 'declared and cells have not been ' + 'manually placed inside the explicit grid' ] ) }) test('shows Grid warnings only for IE', () => { let input = 'a { grid-template-rows: repeat(auto-fit, 1px) }' let instance = autoprefixer({ grid: true, overrideBrowserslist: 'chrome 27' }) let result = postcss([instance]).process(input) equal(result.warnings().length, 0) }) test('warns if rule has both grid-area and grid-(row|column) decls', () => { let input = read('grid-area') let instance = prefixer('grid-area') let result = postcss([instance]).process(input) equal( result.warnings().map(i => i.toString()), [ 'autoprefixer: :28:3: You already have a grid-area ' + 'declaration present in the rule. You should use either ' + 'grid-area or grid-row, not both', 'autoprefixer: :29:3: You already have a grid-area ' + 'declaration present in the rule. You should use either ' + 'grid-area or grid-column, not both', 'autoprefixer: :34:3: You already have a grid-area ' + 'declaration present in the rule. You should use either ' + 'grid-area or grid-column, not both' ] ) }) test('warns if rule with grid-area has no parent with grid-template', () => { let input = read('grid-template-areas') let instance = prefixer('grid-area') let result = postcss([instance]).process(input) equal( result .warnings() .map(i => i.toString()) .filter(str => str.includes('grid-template')), [ 'autoprefixer: :144:3: Autoprefixer cannot find ' + 'a grid-template containing the duplicate grid-area ' + '"child" with full selector matching: .uncle', 'autoprefixer: :149:3: Autoprefixer cannot find ' + 'a grid-template containing the duplicate grid-area ' + '"child" with full selector matching: .uncle', 'autoprefixer: :154:3: Autoprefixer cannot find ' + 'a grid-template containing the duplicate grid-area ' + '"child" with full selector matching: .grand-parent .uncle-second', 'autoprefixer: :159:3: Autoprefixer cannot find ' + 'a grid-template containing the duplicate grid-area ' + '"child" with full selector matching: .grand-parent .uncle-second', 'autoprefixer: :164:3: Autoprefixer cannot find ' + 'a grid-template containing the duplicate grid-area ' + '"child" with full selector matching: .grand-parent .father.uncle', 'autoprefixer: :169:3: Autoprefixer cannot find ' + 'a grid-template containing the duplicate grid-area ' + '"child" with full selector matching: .grand-parent.uncle .father' ] ) }) test('should preserve @media rules with grid-area', () => { let input = read('grid-area-media-sequence') let output = read('grid-area-media-sequence.out') let instance = prefixer('grid-area') let result = postcss([instance]).process(input) equal(result.css, output) }) test('should merge complex duplicate grid-area rules successfully', () => { let input = read('grid-areas-duplicate-complex') let output = read('grid-areas-duplicate-complex.out') let instance = prefixer('grid-area') let result = postcss([instance]).process(input) equal(result.css, output) }) test('resets grid-area spans when overriding a broader area', () => { let input = `/* autoprefixer grid: autoplace */ body { display: grid; grid-template: "other other" "one two"; } body > * { grid-area: other; } body > .one { grid-area: one; } body > .two { grid-area: two; }` let output = `/* autoprefixer grid: autoplace */ body { display: -ms-grid; display: grid; -ms-grid-rows: auto; grid-template: "other other" "one two"; } body > * { -ms-grid-row: 1; -ms-grid-column: 1; -ms-grid-column-span: 2; grid-area: other; } body > .one { -ms-grid-row: 2; -ms-grid-column: 1; -ms-grid-column-span: 1; grid-area: one; } body > .two { -ms-grid-row: 2; -ms-grid-column: 2; -ms-grid-column-span: 1; grid-area: two; }` let result = postcss([prefixer('grid-area')]).process(input) equal(result.css, output) }) test('sets source for grid nodes', () => { for (let name of [ 'grid-template-areas', 'grid-media-rules', 'grid-autoplacement' ]) { let result = postcss([prefixer('grid')]).process(read(name), { from: name + '.css', map: { inline: false } }) equal(result.map.toJSON().sources, [name + '.css']) } }) test('ignores values for CSS3PIE props', () => { let css = read('pie') equal(postcss([compiler]).process(css).css, css) }) test('add prefix for backface-visibility for Safari 9', () => { let input = 'a{ backface-visibility: hidden; transform-style: preserve-3d }' let ap = autoprefixer({ flexbox: false, overrideBrowserslist: ['Safari 9'] }) equal( postcss([ap]).process(input).css, 'a{ ' + '-webkit-backface-visibility: hidden; ' + 'backface-visibility: hidden; ' + 'transform-style: preserve-3d }' ) }) test('supports text-decoration', () => { let input = read('text-decoration') let output = read('text-decoration.out') let instance = prefixer('text-decoration') let result = postcss([instance]).process(input) equal(universalizer(result.css), universalizer(output)) equal( result.warnings().map(i => i.toString()), [ 'autoprefixer: :32:3: Replace text-decoration-skip: ink ' + 'to text-decoration-skip-ink: auto, because spec had been changed' ] ) }) test('supports text-decoration shorthand', () => { let input = read('text-decoration') let output = read('text-decoration.shorthand.out') let instance = prefixer('text-decoration-shorthand') let result = postcss([instance]).process(input) equal(universalizer(result.css), universalizer(output)) }) test('supports -webkit-line-clamp', () => { let input = read('webkit-line-clamp') let output = read('webkit-line-clamp.out') let result = postcss([cleaner]).process(input) equal(universalizer(result.css), universalizer(output)) equal(result.warnings().length, 0) }) test('supports latest Firefox stretch', () => { let input = read('intrinsic') let output = read('intrinsic.ff') let result = postcss([ autoprefixer({ overrideBrowserslist: 'firefox 90' }) ]).process(input) equal(result.css, output) equal(result.warnings().length, 2) }) test.run()