diff --git a/test/autoprefixer.test.js b/test/autoprefixer.test.js new file mode 100644 index 0000000..af1da78 --- /dev/null +++ b/test/autoprefixer.test.js @@ -0,0 +1,1404 @@ +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('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()