UNPKG

@mjhenkes/postcss-rtl

Version:

PostCSS plugin for RTL-optimizations

336 lines (265 loc) 13.9 kB
import postcss from 'postcss'; import postcssImport from 'postcss-import'; import test from 'ava'; import plugin from './index'; const normalize = cssString => cssString .replace(/} /g, '}'); /* fix extra space added by `postcss-import` */ const run = (t, input, output, opts) => postcss([ postcssImport, plugin(opts), ]) .process(input, {from: undefined}) .then((result) => { t.is(normalize(result.css), normalize(output)); t.is(result.warnings().length, 0); }); test('Should NOT add [dir] prefix to symmetric rules', t => run(t, 'a { font-size: 1em }', 'a { font-size: 1em }')); test('Should ONLY create LTR & RTL rules to asymmetric rules', t => run(t, 'a { font-size: 1em; text-align: left }', 'a { font-size: 1em }' + '[dir=ltr] a { text-align: left }' + '[dir=rtl] a { text-align: right }')); test('Should add [dir] prefix to symmetric rules with direction related declarations', t => run(t, 'a { text-align: center }', '[dir] a { text-align: center }')); test('Should add [dir] prefix to symmetric rules with direction related declarations (2)', t => run(t, 'a { font-size: 1em; text-align: center }', 'a { font-size: 1em }' + '[dir] a { text-align: center }')); test('Should add [dir] prefix to symmetric rules with direction related declarations (3)', t => run(t, 'a { text-align: left }' + 'a { text-align: center }', '[dir=ltr] a { text-align: left }' + '[dir=rtl] a { text-align: right }' + '[dir] a { text-align: center }')); test('Should add [dir] prefix to symmetric rules with direction related declarations (4)', t => run(t, 'a { margin: 0 10px 0 0 }' + 'a { margin-top: 20px }', '[dir=ltr] a { margin: 0 10px 0 0 }' + '[dir=rtl] a { margin: 0 0 0 10px }' + '[dir] a { margin-top: 20px }')); test('Creates both LTR & RTL rules for asymmetric declarations', t => run(t, 'a { text-align: left }', '[dir=ltr] a { text-align: left }' + '[dir=rtl] a { text-align: right }')); test('Removes original rule without symmetric declarations', t => run(t, 'a { text-align: left }', '[dir=ltr] a { text-align: left }' + '[dir=rtl] a { text-align: right }')); test('Adds prefix to the html element', t => run(t, 'html { text-align: left }', 'html[dir=ltr] { text-align: left }' + 'html[dir=rtl] { text-align: right }')); test('Adds prefix to the html element with class', t => run(t, 'html.foo { text-align: left }', 'html[dir=ltr].foo { text-align: left }' + 'html[dir=rtl].foo { text-align: right }')); test('Adds prefix to the :root element', t => run(t, ':root { text-align: left }', '[dir=ltr]:root { text-align: left }' + '[dir=rtl]:root { text-align: right }')); test('Adds prefix to the :root element with class', t => run(t, ':root.foo { text-align: left }', '[dir=ltr]:root.foo { text-align: left }' + '[dir=rtl]:root.foo { text-align: right }')); test('Use custom `addPrefixToSelector` function', t => run(t, 'a { text-align: left }', '[dir=ltr] > a { text-align: left }' + '[dir=rtl] > a { text-align: right }', { addPrefixToSelector(selector, prefix) { return `${prefix} > ${selector}`; }, })); test('Should correctly process values containing commas', t => run(t, 'div { background: url(\'http://placecage.com/400/400\') 0 0 }', '[dir=ltr] div { background: url(\'http://placecage.com/400/400\') 0 0 }' + '[dir=rtl] div { background: url(\'http://placecage.com/400/400\') 100% 0 }')); test('Should correctly process values containing !important', t => run(t, '.test { margin-left: 0 !important; padding-left: 0 !important }', '[dir=ltr] .test { margin-left: 0 !important; padding-left: 0 !important }' + '[dir=rtl] .test { margin-right: 0 !important; padding-right: 0 !important }')); test('Shouldn not create unnecessary duplications with !important', t => run(t, '.test { display: none !important }', '.test { display: none !important }')); test('Should correctly process values containing _display', t => run(t, '.test { float: left; _display: inline }', '.test { _display: inline }' + '[dir=ltr] .test { float: left }' + '[dir=rtl] .test { float: right }')); test('Should ignore declarations prefixed with /* rtl:ignore */', t => run(t, '/* rtl:ignore */ .test { margin-left:0; padding-left:0 }', '.test { margin-left:0; padding-left:0 }')); test('/* rtl:ignore */: Should leave other selectors alone', t => run(t, '/* rtl:ignore */ .test { margin-left:0 } ' + '.rtled { margin-left:0; padding-left:0 }', '.test { margin-left:0 } ' + '[dir=ltr] .rtled { margin-left:0; padding-left:0 } ' + '[dir=rtl] .rtled { margin-right:0; padding-right:0 }')); test('/* rtl:ignore */: should understand overrides', t => run(t, '.x { left: 0 } ' + '/* rtl:ignore */' + '.x { direction: ltr }', '[dir=ltr] .x { left: 0 }' + '[dir=rtl] .x { right: 0 }' + '.x { direction: ltr }')); test('/* rtl:begin:ignore */ starts ignore mode', t => run(t, '/* rtl:begin:ignore */' + '.foo { padding-left: 0 }' + '.bar { direction: ltr }', '.foo { padding-left: 0 }' + '.bar { direction: ltr }')); test('/* rtl:end:ignore */ stops ignore mode', t => run(t, '/* rtl:begin:ignore */' + '.foo { padding-left: 0 }' + '/* rtl:end:ignore */' + '.bar { direction: ltr }', '.foo { padding-left: 0 }' + '[dir=ltr] .bar { direction: ltr }' + '[dir=rtl] .bar { direction: rtl }')); test('/* rtl:ignore */ can be used inside /* rtl:begin:ignore */ and /* rtl:end:ignore */', t => run(t, '/* rtl:begin:ignore */' + '.foo { padding-left: 0 }' + '/* rtl:ignore */' + '.bar { direction: ltr }' + '.baz { left: 0 }' + '/* rtl:end:ignore */', '.foo { padding-left: 0 }' + '.bar { direction: ltr }' + '.baz { left: 0 }')); test('that it ignores normal comments ', t => run(t, '/* some comment */ .foo { padding-left: 0 }', '/* some comment */ [dir=ltr] .foo { padding-left: 0 } [dir=rtl] .foo { padding-right: 0 }')); test('Value based ignore comments are honored', t => run(t, '.foo { margin-left: 12px; padding-left: 12px /* rtl:ignore */; }', '.foo { padding-left: 12px /* rtl:ignore */; }' + '[dir=ltr] .foo { margin-left: 12px; }' + '[dir=rtl] .foo { margin-right: 12px; }')); test('/*! rtl:ignore */ should consider as a valid directive', t => run(t, '/*! rtl:ignore */ .test { margin-left:0; padding-left:0 }', '.test { margin-left:0; padding-left:0 }')); test('/*! rtl:begin:ignore */ and /*! rtl:end:ignore */ should consider as a valid directive', t => run(t, '/*! rtl:begin:ignore */' + '.foo { padding-left: 0 }' + '/*! rtl:end:ignore */' + '.bar { direction: ltr }', '.foo { padding-left: 0 }' + '[dir=ltr] .bar { direction: ltr }' + '[dir=rtl] .bar { direction: rtl }')); test('Should add direction to flippable keyframes-animations', t => run(t, '@keyframes bar { 100% { transform: rotate(360deg); } }', '@keyframes bar-ltr { 100% { transform: rotate(360deg); } }' + '@keyframes bar-rtl { 100% { transform: rotate(-360deg); } }')); test('Should handle multiple keyframes-animations', t => run(t, '.loader {animation: load6 1.7s infinite ease, spinner 1.7s infinite ease;}' + '@keyframes load6 { 100% { transform: rotate(1deg) } }' + '@keyframes spinner { 100% { transform: rotate(-1deg) } }', '[dir=ltr] .loader {animation: load6-ltr 1.7s infinite ease, spinner-ltr 1.7s infinite ease;}' + '[dir=rtl] .loader {animation: load6-rtl 1.7s infinite ease, spinner-rtl 1.7s infinite ease;}' + '@keyframes load6-ltr { 100% { transform: rotate(1deg) } }' + '@keyframes load6-rtl { 100% { transform: rotate(-1deg) } }' + '@keyframes spinner-ltr { 100% { transform: rotate(-1deg) } }' + '@keyframes spinner-rtl { 100% { transform: rotate(1deg) } }')); test('Should ignore keyframes-animation prefixed with /* rtl:ignore */', t => run(t, '/* rtl:ignore */ @keyframes bar { 100% { transform: rotate(360deg); } }', '@keyframes bar { 100% { transform: rotate(360deg); } }')); test('/* rtl:begin:ignore */ starts ignore mode for both keyframes and rules', t => run(t, '/* rtl:begin:ignore */ @keyframes bar { 100% { transform: rotate(360deg); } } .foo { left: 5px }', '@keyframes bar { 100% { transform: rotate(360deg); } } .foo { left: 5px }')); test('/* rtl:end:ignore */ stops ignore mode for keyframes', t => run(t, '/* rtl:begin:ignore */ @keyframes bar { 100% { transform: rotate(360deg); } } /* rtl:end:ignore */' + '.foo { left: 5px }', '@keyframes bar { 100% { transform: rotate(360deg); } }' + '[dir=ltr] .foo { left: 5px }' + '[dir=rtl] .foo { right: 5px }')); test('Should create only LTR version', t => run(t, 'a { font-size: 1em; text-align: left }' + '@keyframes bar { 100% { transform: rotate(360deg); } }', 'a { font-size: 1em }' + '[dir=ltr] a { text-align: left }' + '@keyframes bar-ltr { 100% { transform: rotate(360deg); } }', {onlyDirection: 'ltr'})); test('Should create only RTL version', t => run(t, 'a { font-size: 1em; text-align: left }' + '@keyframes bar { 100% { transform: rotate(360deg); } }', 'a { font-size: 1em }' + '[dir=rtl] a { text-align: right }' + '@keyframes bar-rtl { 100% { transform: rotate(-360deg); } }', {onlyDirection: 'rtl'})); test('Value replacement directives are honored', t => run(t, '.foo { font-weight: bold; flex-direction: row/* rtl:row-reverse */; }', '.foo { font-weight: bold; }[dir=ltr] .foo { flex-direction: row/* rtl:row-reverse */; }[dir=rtl] .foo { flex-direction: row-reverse; }')); test('Value prepend directives are honored', t => run(t, '.foo { font-weight: bold; font-family: "Droid Sans", "Helvetica Neue", Arial, sans-serif/*rtl:prepend:"Droid Arabic Kufi",*/; }', '.foo { font-weight: bold; }[dir=ltr] .foo { font-family: "Droid Sans", "Helvetica Neue", Arial, sans-serif/*rtl:prepend:"Droid Arabic Kufi",*/; }[dir=rtl] .foo { font-family: "Droid Arabic Kufi", "Droid Sans", "Helvetica Neue", Arial, sans-serif; }')); test('Value append directives are honored', t => run(t, '.foo { font-weight: bold; transform: rotate(45deg)/* rtl:append: scaleX(-1) */; }', '.foo { font-weight: bold; }[dir=ltr] .foo { transform: rotate(45deg)/* rtl:append: scaleX(-1) */; }[dir=rtl] .foo { transform: rotate(45deg) scaleX(-1); }')); test('Value based ignore important comments are honored', t => run(t, '.foo { margin-left: 12px; padding-left: 12px /*! rtl:ignore */; }', '.foo { padding-left: 12px /*! rtl:ignore */; }' + '[dir=ltr] .foo { margin-left: 12px; }' + '[dir=rtl] .foo { margin-right: 12px; }')); test('Value replacement directives with important comments are honored', t => run(t, '.foo { font-weight: bold; flex-direction: row/*! rtl:row-reverse */; }', '.foo { font-weight: bold; }[dir=ltr] .foo { flex-direction: row/*! rtl:row-reverse */; }[dir=rtl] .foo { flex-direction: row-reverse; }')); test('Value prepend directives with important comments are honored', t => run(t, '.foo { font-weight: bold; font-family: "Droid Sans", "Helvetica Neue", Arial, sans-serif/*!rtl:prepend:"Droid Arabic Kufi",*/; }', '.foo { font-weight: bold; }[dir=ltr] .foo { font-family: "Droid Sans", "Helvetica Neue", Arial, sans-serif/*!rtl:prepend:"Droid Arabic Kufi",*/; }[dir=rtl] .foo { font-family: "Droid Arabic Kufi", "Droid Sans", "Helvetica Neue", Arial, sans-serif; }')); test('Value append directives with important comments are honored', t => run(t, '.foo { font-weight: bold; transform: rotate(45deg)/*! rtl:append: scaleX(-1) */; }', '.foo { font-weight: bold; }[dir=ltr] .foo { transform: rotate(45deg)/*! rtl:append: scaleX(-1) */; }[dir=rtl] .foo { transform: rotate(45deg) scaleX(-1); }')); test('Should keep comments', t => run(t, '/* rtl:ignore */ a { text-align: left }', '/* rtl:ignore */ a { text-align: left }', {removeComments: false})); test('Should respect custom prefix (attribute)', t => run(t, 'a { text-align: left }', '[custom-dir-prefix=ltr] a { text-align: left }' + '[custom-dir-prefix=rtl] a { text-align: right }', {prefix: 'custom-dir-prefix'})); test('Should respect custom prefix (class)', t => run(t, 'a { text-align: left }', '.custom-dir-prefix-ltr a { text-align: left }' + '.custom-dir-prefix-rtl a { text-align: right }', {prefix: 'custom-dir-prefix', prefixType: 'class'})); test('Should not swap "left" and "right" subparts of selectors', t => run(t, '.arrowLeft { margin-right: -3px }', '[dir=ltr] .arrowLeft { margin-right: -3px }' + '[dir=rtl] .arrowLeft { margin-left: -3px }')); test('Should respect multiline values', t => run(t, `.multiline { background: rgba(0, 0, 0, 0, .5), linear-gradient(to right, transparent, #000); }`, `[dir=ltr] .multiline { background: rgba(0, 0, 0, 0, .5), linear-gradient(to right, transparent, #000); }` + `[dir=rtl] .multiline { background: rgba(0, 0, 0, 0, .5), linear-gradient(to left, transparent, #000); }`)); test('rtl:as: directive', t => run(t, ':root { --padding /* rtl:as:padding */: 1px 2px 3px 4px }', '[dir=ltr]:root { --padding /* rtl:as:padding */: 1px 2px 3px 4px }' + '[dir=rtl]:root { --padding /* rtl:as:padding */: 1px 4px 3px 2px }')); test('should ignore ignored @import', t => run(t, '/* rtl:begin:ignore */' + `@import "${__dirname}/test-import.css";` + '/* rtl:end:ignore */', '.test-import { padding-left: 1rem }')); test('should ignore blacklist properties', t => run(t, '.test {padding-left: 1rem;padding: 1rem 2rem 3rem 4rem}', '.test {padding: 1rem 2rem 3rem 4rem}' + '[dir=ltr] .test {padding-left: 1rem}' + '[dir=rtl] .test {padding-right: 1rem}', {blacklist: ['padding']})); test('should process whitelist properties only', t => run(t, '.test {padding-left: 1rem;padding: 1rem 2rem 3rem 4rem}', '.test {padding-left: 1rem}' + '[dir=ltr] .test {padding: 1rem 2rem 3rem 4rem}' + '[dir=rtl] .test {padding: 1rem 4rem 3rem 2rem}', {whitelist: ['padding']}));