2017-11-22 16:46:52 -05:00
|
|
|
import postcss from 'postcss'
|
|
|
|
|
import plugin from '../src/lib/substituteVariantsAtRules'
|
2018-06-22 20:57:40 -04:00
|
|
|
import processPlugins from '../src/util/processPlugins'
|
2020-10-17 20:27:21 -04:00
|
|
|
import resolveConfig from '../src/util/resolveConfig'
|
2019-03-14 15:51:21 -05:00
|
|
|
import config from '../stubs/defaultConfig.stub.js'
|
2017-11-22 16:46:52 -05:00
|
|
|
|
2018-06-22 20:52:59 -04:00
|
|
|
function run(input, opts = config) {
|
2020-10-17 20:27:21 -04:00
|
|
|
const resolved = resolveConfig([opts])
|
|
|
|
|
return postcss([plugin(resolved, processPlugins(resolved.plugins, resolved))]).process(input, {
|
2019-01-11 12:29:33 -05:00
|
|
|
from: undefined,
|
|
|
|
|
})
|
2017-11-22 16:46:52 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test('it can generate hover variants', () => {
|
|
|
|
|
const input = `
|
|
|
|
|
@variants hover {
|
|
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
const output = `
|
2019-07-20 11:24:03 -04:00
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
.hover\\:banana:hover { color: yellow; }
|
|
|
|
|
.hover\\:chocolate:hover { color: brown; }
|
2017-11-22 16:46:52 -05:00
|
|
|
`
|
|
|
|
|
|
2020-10-16 15:39:44 -04:00
|
|
|
return run(input).then((result) => {
|
2017-11-22 16:46:52 -05:00
|
|
|
expect(result.css).toMatchCss(output)
|
|
|
|
|
expect(result.warnings().length).toBe(0)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
2019-03-07 22:37:44 -06:00
|
|
|
test('it can generate disabled variants', () => {
|
|
|
|
|
const input = `
|
|
|
|
|
@variants disabled {
|
|
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
const output = `
|
2019-07-20 11:24:03 -04:00
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
.disabled\\:banana:disabled { color: yellow; }
|
|
|
|
|
.disabled\\:chocolate:disabled { color: brown; }
|
2019-03-07 22:37:44 -06:00
|
|
|
`
|
|
|
|
|
|
2020-10-16 15:39:44 -04:00
|
|
|
return run(input).then((result) => {
|
2019-03-07 22:37:44 -06:00
|
|
|
expect(result.css).toMatchCss(output)
|
|
|
|
|
expect(result.warnings().length).toBe(0)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
2019-12-27 10:11:04 -06:00
|
|
|
test('it can generate checked variants', () => {
|
|
|
|
|
const input = `
|
|
|
|
|
@variants checked {
|
|
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
const output = `
|
|
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
.checked\\:banana:checked { color: yellow; }
|
|
|
|
|
.checked\\:chocolate:checked { color: brown; }
|
|
|
|
|
`
|
|
|
|
|
|
2020-10-16 15:39:44 -04:00
|
|
|
return run(input).then((result) => {
|
2019-03-07 22:37:44 -06:00
|
|
|
expect(result.css).toMatchCss(output)
|
|
|
|
|
expect(result.warnings().length).toBe(0)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
2018-02-09 20:20:24 +01:00
|
|
|
test('it can generate active variants', () => {
|
|
|
|
|
const input = `
|
|
|
|
|
@variants active {
|
|
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
const output = `
|
2019-07-20 11:24:03 -04:00
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
.active\\:banana:active { color: yellow; }
|
|
|
|
|
.active\\:chocolate:active { color: brown; }
|
2018-02-09 20:20:24 +01:00
|
|
|
`
|
|
|
|
|
|
2020-10-16 15:39:44 -04:00
|
|
|
return run(input).then((result) => {
|
2018-02-09 20:20:24 +01:00
|
|
|
expect(result.css).toMatchCss(output)
|
|
|
|
|
expect(result.warnings().length).toBe(0)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
2019-06-18 07:27:40 -04:00
|
|
|
test('it can generate visited variants', () => {
|
|
|
|
|
const input = `
|
|
|
|
|
@variants visited {
|
|
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
const output = `
|
2019-07-20 11:24:03 -04:00
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
.visited\\:banana:visited { color: yellow; }
|
|
|
|
|
.visited\\:chocolate:visited { color: brown; }
|
2019-06-18 07:27:40 -04:00
|
|
|
`
|
|
|
|
|
|
2020-10-16 15:39:44 -04:00
|
|
|
return run(input).then((result) => {
|
2019-06-18 07:27:40 -04:00
|
|
|
expect(result.css).toMatchCss(output)
|
2018-02-09 20:20:24 +01:00
|
|
|
expect(result.warnings().length).toBe(0)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
2017-11-22 16:46:52 -05:00
|
|
|
test('it can generate focus variants', () => {
|
|
|
|
|
const input = `
|
|
|
|
|
@variants focus {
|
|
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
const output = `
|
2019-07-20 11:24:03 -04:00
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
.focus\\:banana:focus { color: yellow; }
|
|
|
|
|
.focus\\:chocolate:focus { color: brown; }
|
2017-11-22 16:46:52 -05:00
|
|
|
`
|
|
|
|
|
|
2020-10-16 15:39:44 -04:00
|
|
|
return run(input).then((result) => {
|
2017-11-22 16:46:52 -05:00
|
|
|
expect(result.css).toMatchCss(output)
|
|
|
|
|
expect(result.warnings().length).toBe(0)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
2018-05-04 14:47:28 +02:00
|
|
|
test('it can generate focus-within variants', () => {
|
|
|
|
|
const input = `
|
|
|
|
|
@variants focus-within {
|
|
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
const output = `
|
2019-07-20 11:24:03 -04:00
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
.focus-within\\:banana:focus-within { color: yellow; }
|
|
|
|
|
.focus-within\\:chocolate:focus-within { color: brown; }
|
2018-05-04 14:47:28 +02:00
|
|
|
`
|
|
|
|
|
|
2020-10-16 15:39:44 -04:00
|
|
|
return run(input).then((result) => {
|
2018-05-04 14:47:28 +02:00
|
|
|
expect(result.css).toMatchCss(output)
|
2019-07-12 12:53:52 -04:00
|
|
|
expect(result.warnings().length).toBe(0)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
2020-05-26 11:10:03 -07:00
|
|
|
test('it can generate focus-visible variants', () => {
|
|
|
|
|
const input = `
|
|
|
|
|
@variants focus-visible {
|
|
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
const output = `
|
|
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
.focus-visible\\:banana:focus-visible { color: yellow; }
|
|
|
|
|
.focus-visible\\:chocolate:focus-visible { color: brown; }
|
|
|
|
|
`
|
|
|
|
|
|
2020-10-16 15:39:44 -04:00
|
|
|
return run(input).then((result) => {
|
2020-05-26 11:10:03 -07:00
|
|
|
expect(result.css).toMatchCss(output)
|
|
|
|
|
expect(result.warnings().length).toBe(0)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
2020-07-28 10:15:50 -04:00
|
|
|
test('it can generate motion-reduce variants', () => {
|
2020-07-24 15:26:55 -04:00
|
|
|
const input = `
|
2020-07-28 10:15:50 -04:00
|
|
|
@variants motion-reduce {
|
2020-07-24 15:26:55 -04:00
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
const output = `
|
|
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
2020-07-28 10:15:50 -04:00
|
|
|
.motion-reduce\\:banana { color: yellow; }
|
|
|
|
|
.motion-reduce\\:chocolate { color: brown; }
|
2020-07-27 14:36:10 -04:00
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
|
2020-10-16 15:39:44 -04:00
|
|
|
return run(input).then((result) => {
|
2020-07-27 14:36:10 -04:00
|
|
|
expect(result.css).toMatchCss(output)
|
|
|
|
|
expect(result.warnings().length).toBe(0)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('it can generate motion-safe variants', () => {
|
|
|
|
|
const input = `
|
|
|
|
|
@variants motion-safe {
|
|
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
const output = `
|
|
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
@media (prefers-reduced-motion: no-preference) {
|
|
|
|
|
.motion-safe\\:banana { color: yellow; }
|
|
|
|
|
.motion-safe\\:chocolate { color: brown; }
|
2020-07-24 15:26:55 -04:00
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
|
2020-10-16 15:39:44 -04:00
|
|
|
return run(input).then((result) => {
|
2020-07-24 15:26:55 -04:00
|
|
|
expect(result.css).toMatchCss(output)
|
|
|
|
|
expect(result.warnings().length).toBe(0)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
2020-07-28 10:15:50 -04:00
|
|
|
test('it can generate motion-safe and motion-reduce variants', () => {
|
2020-07-27 15:23:56 -04:00
|
|
|
const input = `
|
2020-07-28 10:15:50 -04:00
|
|
|
@variants motion-safe, motion-reduce {
|
2020-07-27 15:23:56 -04:00
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
const output = `
|
|
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
@media (prefers-reduced-motion: no-preference) {
|
|
|
|
|
.motion-safe\\:banana { color: yellow; }
|
|
|
|
|
.motion-safe\\:chocolate { color: brown; }
|
|
|
|
|
}
|
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
2020-07-28 10:15:50 -04:00
|
|
|
.motion-reduce\\:banana { color: yellow; }
|
|
|
|
|
.motion-reduce\\:chocolate { color: brown; }
|
2020-07-27 15:23:56 -04:00
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
|
2020-10-16 15:39:44 -04:00
|
|
|
return run(input).then((result) => {
|
2020-07-27 15:23:56 -04:00
|
|
|
expect(result.css).toMatchCss(output)
|
|
|
|
|
expect(result.warnings().length).toBe(0)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
2020-07-28 10:15:50 -04:00
|
|
|
test('motion-reduce variants stack with basic variants', () => {
|
2020-07-27 15:23:56 -04:00
|
|
|
const input = `
|
2020-08-18 09:03:32 -04:00
|
|
|
@variants motion-reduce, hover, group-hover {
|
2020-07-27 15:23:56 -04:00
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
const output = `
|
|
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
.hover\\:banana:hover { color: yellow; }
|
|
|
|
|
.hover\\:chocolate:hover { color: brown; }
|
2020-08-18 09:03:32 -04:00
|
|
|
.group:hover .group-hover\\:banana { color: yellow; }
|
|
|
|
|
.group:hover .group-hover\\:chocolate { color: brown; }
|
2020-07-27 15:23:56 -04:00
|
|
|
@media (prefers-reduced-motion: reduce) {
|
2020-07-28 10:15:50 -04:00
|
|
|
.motion-reduce\\:banana { color: yellow; }
|
|
|
|
|
.motion-reduce\\:chocolate { color: brown; }
|
|
|
|
|
.motion-reduce\\:hover\\:banana:hover { color: yellow; }
|
|
|
|
|
.motion-reduce\\:hover\\:chocolate:hover { color: brown; }
|
2020-08-18 09:03:32 -04:00
|
|
|
.group:hover .motion-reduce\\:group-hover\\:banana { color: yellow; }
|
|
|
|
|
.group:hover .motion-reduce\\:group-hover\\:chocolate { color: brown; }
|
2020-07-27 15:23:56 -04:00
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
|
2020-10-16 15:39:44 -04:00
|
|
|
return run(input).then((result) => {
|
2020-07-27 15:23:56 -04:00
|
|
|
expect(result.css).toMatchCss(output)
|
|
|
|
|
expect(result.warnings().length).toBe(0)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('motion-safe variants stack with basic variants', () => {
|
|
|
|
|
const input = `
|
2020-08-18 09:03:32 -04:00
|
|
|
@variants motion-safe, hover, group-hover {
|
2020-07-27 15:23:56 -04:00
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
const output = `
|
|
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
.hover\\:banana:hover { color: yellow; }
|
|
|
|
|
.hover\\:chocolate:hover { color: brown; }
|
2020-08-18 09:03:32 -04:00
|
|
|
.group:hover .group-hover\\:banana { color: yellow; }
|
|
|
|
|
.group:hover .group-hover\\:chocolate { color: brown; }
|
2020-07-27 15:23:56 -04:00
|
|
|
@media (prefers-reduced-motion: no-preference) {
|
|
|
|
|
.motion-safe\\:banana { color: yellow; }
|
|
|
|
|
.motion-safe\\:chocolate { color: brown; }
|
|
|
|
|
.motion-safe\\:hover\\:banana:hover { color: yellow; }
|
|
|
|
|
.motion-safe\\:hover\\:chocolate:hover { color: brown; }
|
2020-08-18 09:03:32 -04:00
|
|
|
.group:hover .motion-safe\\:group-hover\\:banana { color: yellow; }
|
|
|
|
|
.group:hover .motion-safe\\:group-hover\\:chocolate { color: brown; }
|
2020-07-27 15:23:56 -04:00
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
|
2020-10-16 15:39:44 -04:00
|
|
|
return run(input).then((result) => {
|
2020-07-27 15:23:56 -04:00
|
|
|
expect(result.css).toMatchCss(output)
|
|
|
|
|
expect(result.warnings().length).toBe(0)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
2020-07-28 10:15:50 -04:00
|
|
|
test('motion-safe and motion-reduce variants stack with basic variants', () => {
|
2020-07-27 15:23:56 -04:00
|
|
|
const input = `
|
2020-07-28 10:15:50 -04:00
|
|
|
@variants motion-reduce, motion-safe, hover {
|
2020-07-27 15:23:56 -04:00
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
const output = `
|
|
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
.hover\\:banana:hover { color: yellow; }
|
|
|
|
|
.hover\\:chocolate:hover { color: brown; }
|
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
2020-07-28 10:15:50 -04:00
|
|
|
.motion-reduce\\:banana { color: yellow; }
|
|
|
|
|
.motion-reduce\\:chocolate { color: brown; }
|
|
|
|
|
.motion-reduce\\:hover\\:banana:hover { color: yellow; }
|
|
|
|
|
.motion-reduce\\:hover\\:chocolate:hover { color: brown; }
|
2020-07-27 15:23:56 -04:00
|
|
|
}
|
|
|
|
|
@media (prefers-reduced-motion: no-preference) {
|
|
|
|
|
.motion-safe\\:banana { color: yellow; }
|
|
|
|
|
.motion-safe\\:chocolate { color: brown; }
|
|
|
|
|
.motion-safe\\:hover\\:banana:hover { color: yellow; }
|
|
|
|
|
.motion-safe\\:hover\\:chocolate:hover { color: brown; }
|
|
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
|
2020-10-16 15:39:44 -04:00
|
|
|
return run(input).then((result) => {
|
2020-07-27 15:23:56 -04:00
|
|
|
expect(result.css).toMatchCss(output)
|
|
|
|
|
expect(result.warnings().length).toBe(0)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
2019-07-12 12:53:52 -04:00
|
|
|
test('it can generate first-child variants', () => {
|
|
|
|
|
const input = `
|
2019-07-20 11:14:25 -04:00
|
|
|
@variants first {
|
2019-07-12 12:53:52 -04:00
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
const output = `
|
2019-07-20 11:24:03 -04:00
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
.first\\:banana:first-child { color: yellow; }
|
|
|
|
|
.first\\:chocolate:first-child { color: brown; }
|
2019-07-12 12:53:52 -04:00
|
|
|
`
|
|
|
|
|
|
2020-10-16 15:39:44 -04:00
|
|
|
return run(input).then((result) => {
|
2019-07-12 12:53:52 -04:00
|
|
|
expect(result.css).toMatchCss(output)
|
|
|
|
|
expect(result.warnings().length).toBe(0)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
2019-07-20 11:22:09 -04:00
|
|
|
test('it can generate odd variants', () => {
|
|
|
|
|
const input = `
|
|
|
|
|
@variants odd {
|
2019-07-14 00:10:08 -05:00
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
const output = `
|
|
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
2019-07-20 11:22:09 -04:00
|
|
|
.odd\\:banana:nth-child(odd) { color: yellow; }
|
|
|
|
|
.odd\\:chocolate:nth-child(odd) { color: brown; }
|
|
|
|
|
`
|
|
|
|
|
|
2020-10-16 15:39:44 -04:00
|
|
|
return run(input).then((result) => {
|
2019-07-20 11:22:09 -04:00
|
|
|
expect(result.css).toMatchCss(output)
|
|
|
|
|
expect(result.warnings().length).toBe(0)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
2019-07-12 12:53:52 -04:00
|
|
|
test('it can generate last-child variants', () => {
|
|
|
|
|
const input = `
|
2019-07-20 11:14:25 -04:00
|
|
|
@variants last {
|
2019-07-12 12:53:52 -04:00
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
const output = `
|
2019-07-20 11:24:03 -04:00
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
.last\\:banana:last-child { color: yellow; }
|
|
|
|
|
.last\\:chocolate:last-child { color: brown; }
|
2019-07-12 12:53:52 -04:00
|
|
|
`
|
|
|
|
|
|
2020-10-16 15:39:44 -04:00
|
|
|
return run(input).then((result) => {
|
2019-07-12 12:53:52 -04:00
|
|
|
expect(result.css).toMatchCss(output)
|
2019-07-14 00:10:08 -05:00
|
|
|
expect(result.warnings().length).toBe(0)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
2019-07-20 11:22:09 -04:00
|
|
|
test('it can generate even variants', () => {
|
2019-07-14 00:10:08 -05:00
|
|
|
const input = `
|
2019-07-20 11:22:09 -04:00
|
|
|
@variants even {
|
2019-07-14 00:10:08 -05:00
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
const output = `
|
|
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
2019-07-20 11:22:09 -04:00
|
|
|
.even\\:banana:nth-child(even) { color: yellow; }
|
|
|
|
|
.even\\:chocolate:nth-child(even) { color: brown; }
|
2019-07-14 00:10:08 -05:00
|
|
|
`
|
|
|
|
|
|
2020-10-16 15:39:44 -04:00
|
|
|
return run(input).then((result) => {
|
2019-07-14 00:10:08 -05:00
|
|
|
expect(result.css).toMatchCss(output)
|
2018-05-04 14:47:28 +02:00
|
|
|
expect(result.warnings().length).toBe(0)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
2021-01-30 17:59:57 -06:00
|
|
|
test('it can generate empty variants', () => {
|
|
|
|
|
const input = `
|
|
|
|
|
@variants empty {
|
|
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
const output = `
|
|
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
.empty\\:banana:empty { color: yellow; }
|
|
|
|
|
.empty\\:chocolate:empty { color: brown; }
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
return run(input).then((result) => {
|
|
|
|
|
expect(result.css).toMatchCss(output)
|
|
|
|
|
expect(result.warnings().length).toBe(0)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
2017-12-01 08:14:58 -05:00
|
|
|
test('it can generate group-hover variants', () => {
|
2017-11-28 14:44:43 -05:00
|
|
|
const input = `
|
2017-12-01 08:14:58 -05:00
|
|
|
@variants group-hover {
|
2017-11-28 14:44:43 -05:00
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
const output = `
|
2019-07-20 11:24:03 -04:00
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
.group:hover .group-hover\\:banana { color: yellow; }
|
|
|
|
|
.group:hover .group-hover\\:chocolate { color: brown; }
|
2017-11-28 14:44:43 -05:00
|
|
|
`
|
|
|
|
|
|
2020-10-16 15:39:44 -04:00
|
|
|
return run(input).then((result) => {
|
2017-11-28 14:44:43 -05:00
|
|
|
expect(result.css).toMatchCss(output)
|
|
|
|
|
expect(result.warnings().length).toBe(0)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
2019-11-10 11:46:25 -05:00
|
|
|
test('group-hover variants respect any configured prefix', () => {
|
|
|
|
|
const input = `
|
|
|
|
|
@variants group-hover {
|
|
|
|
|
.tw-banana { color: yellow; }
|
|
|
|
|
.tw-chocolate { color: brown; }
|
|
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
const output = `
|
|
|
|
|
.tw-banana { color: yellow; }
|
|
|
|
|
.tw-chocolate { color: brown; }
|
|
|
|
|
.tw-group:hover .group-hover\\:tw-banana { color: yellow; }
|
|
|
|
|
.tw-group:hover .group-hover\\:tw-chocolate { color: brown; }
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
return run(input, {
|
|
|
|
|
...config,
|
|
|
|
|
prefix: 'tw-',
|
2020-10-16 15:39:44 -04:00
|
|
|
}).then((result) => {
|
2019-11-10 11:46:25 -05:00
|
|
|
expect(result.css).toMatchCss(output)
|
|
|
|
|
expect(result.warnings().length).toBe(0)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
2020-04-16 12:51:12 -04:00
|
|
|
test('it can generate group-focus variants', () => {
|
|
|
|
|
const input = `
|
|
|
|
|
@variants group-focus {
|
|
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
const output = `
|
|
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
.group:focus .group-focus\\:banana { color: yellow; }
|
|
|
|
|
.group:focus .group-focus\\:chocolate { color: brown; }
|
|
|
|
|
`
|
|
|
|
|
|
2020-10-16 15:39:44 -04:00
|
|
|
return run(input).then((result) => {
|
2020-04-16 12:51:12 -04:00
|
|
|
expect(result.css).toMatchCss(output)
|
|
|
|
|
expect(result.warnings().length).toBe(0)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('group-focus variants respect any configured prefix', () => {
|
|
|
|
|
const input = `
|
|
|
|
|
@variants group-focus {
|
|
|
|
|
.tw-banana { color: yellow; }
|
|
|
|
|
.tw-chocolate { color: brown; }
|
|
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
const output = `
|
|
|
|
|
.tw-banana { color: yellow; }
|
|
|
|
|
.tw-chocolate { color: brown; }
|
|
|
|
|
.tw-group:focus .group-focus\\:tw-banana { color: yellow; }
|
|
|
|
|
.tw-group:focus .group-focus\\:tw-chocolate { color: brown; }
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
return run(input, {
|
|
|
|
|
...config,
|
|
|
|
|
prefix: 'tw-',
|
2020-10-16 15:39:44 -04:00
|
|
|
}).then((result) => {
|
2020-04-16 12:51:12 -04:00
|
|
|
expect(result.css).toMatchCss(output)
|
|
|
|
|
expect(result.warnings().length).toBe(0)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
2018-02-09 20:20:24 +01:00
|
|
|
test('it can generate hover, active and focus variants', () => {
|
2017-11-22 16:46:52 -05:00
|
|
|
const input = `
|
2018-06-22 10:35:47 -04:00
|
|
|
@variants group-hover, hover, focus, active {
|
2017-11-22 16:46:52 -05:00
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
const output = `
|
2019-07-20 11:24:03 -04:00
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
.group:hover .group-hover\\:banana { color: yellow; }
|
|
|
|
|
.group:hover .group-hover\\:chocolate { color: brown; }
|
|
|
|
|
.hover\\:banana:hover { color: yellow; }
|
|
|
|
|
.hover\\:chocolate:hover { color: brown; }
|
|
|
|
|
.focus\\:banana:focus { color: yellow; }
|
|
|
|
|
.focus\\:chocolate:focus { color: brown; }
|
|
|
|
|
.active\\:banana:active { color: yellow; }
|
|
|
|
|
.active\\:chocolate:active { color: brown; }
|
2017-11-22 16:46:52 -05:00
|
|
|
`
|
|
|
|
|
|
2020-10-16 15:39:44 -04:00
|
|
|
return run(input).then((result) => {
|
2017-11-22 16:46:52 -05:00
|
|
|
expect(result.css).toMatchCss(output)
|
|
|
|
|
expect(result.warnings().length).toBe(0)
|
|
|
|
|
})
|
|
|
|
|
})
|
2017-11-23 09:58:42 -05:00
|
|
|
|
2018-09-15 09:01:30 -04:00
|
|
|
test('it can generate hover, active and focus variants for multiple classes in one rule', () => {
|
|
|
|
|
const input = `
|
|
|
|
|
@variants hover, focus, active {
|
|
|
|
|
.banana, .lemon { color: yellow; }
|
|
|
|
|
.chocolate, .coconut { color: brown; }
|
|
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
const output = `
|
2019-07-20 11:24:03 -04:00
|
|
|
.banana, .lemon { color: yellow; }
|
|
|
|
|
.chocolate, .coconut { color: brown; }
|
|
|
|
|
.hover\\:banana:hover, .hover\\:lemon:hover { color: yellow; }
|
|
|
|
|
.hover\\:chocolate:hover, .hover\\:coconut:hover { color: brown; }
|
|
|
|
|
.focus\\:banana:focus, .focus\\:lemon:focus { color: yellow; }
|
|
|
|
|
.focus\\:chocolate:focus, .focus\\:coconut:focus { color: brown; }
|
|
|
|
|
.active\\:banana:active, .active\\:lemon:active { color: yellow; }
|
|
|
|
|
.active\\:chocolate:active, .active\\:coconut:active { color: brown; }
|
2018-09-15 09:01:30 -04:00
|
|
|
`
|
|
|
|
|
|
2020-10-16 15:39:44 -04:00
|
|
|
return run(input).then((result) => {
|
2018-09-15 09:01:30 -04:00
|
|
|
expect(result.css).toMatchCss(output)
|
|
|
|
|
expect(result.warnings().length).toBe(0)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
2017-11-23 09:58:42 -05:00
|
|
|
test('it wraps the output in a responsive at-rule if responsive is included as a variant', () => {
|
|
|
|
|
const input = `
|
|
|
|
|
@variants responsive, hover, focus {
|
|
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
const output = `
|
|
|
|
|
@responsive {
|
|
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
2017-11-30 15:55:06 -05:00
|
|
|
.hover\\:banana:hover { color: yellow; }
|
|
|
|
|
.hover\\:chocolate:hover { color: brown; }
|
2018-03-12 12:21:51 -04:00
|
|
|
.focus\\:banana:focus { color: yellow; }
|
|
|
|
|
.focus\\:chocolate:focus { color: brown; }
|
2017-11-23 09:58:42 -05:00
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
|
2020-10-16 15:39:44 -04:00
|
|
|
return run(input).then((result) => {
|
2017-11-23 09:58:42 -05:00
|
|
|
expect(result.css).toMatchCss(output)
|
|
|
|
|
expect(result.warnings().length).toBe(0)
|
|
|
|
|
})
|
|
|
|
|
})
|
2018-06-22 10:35:47 -04:00
|
|
|
|
2018-10-31 15:52:32 -04:00
|
|
|
test('variants are generated in the order specified', () => {
|
2018-06-22 10:35:47 -04:00
|
|
|
const input = `
|
|
|
|
|
@variants focus, active, hover {
|
|
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
const output = `
|
2019-07-20 11:24:03 -04:00
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
.focus\\:banana:focus { color: yellow; }
|
|
|
|
|
.focus\\:chocolate:focus { color: brown; }
|
|
|
|
|
.active\\:banana:active { color: yellow; }
|
|
|
|
|
.active\\:chocolate:active { color: brown; }
|
|
|
|
|
.hover\\:banana:hover { color: yellow; }
|
|
|
|
|
.hover\\:chocolate:hover { color: brown; }
|
2019-02-15 10:52:01 -05:00
|
|
|
`
|
|
|
|
|
|
|
|
|
|
return run(input, {
|
|
|
|
|
...config,
|
2020-10-16 15:39:44 -04:00
|
|
|
}).then((result) => {
|
2019-02-15 10:52:01 -05:00
|
|
|
expect(result.css).toMatchCss(output)
|
|
|
|
|
expect(result.warnings().length).toBe(0)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
2019-06-12 08:57:47 -04:00
|
|
|
test('the built-in variant pseudo-selectors are appended before any pseudo-elements', () => {
|
|
|
|
|
const input = `
|
2020-05-26 11:10:03 -07:00
|
|
|
@variants hover, focus-within, focus-visible, focus, active, group-hover {
|
2019-06-12 08:57:47 -04:00
|
|
|
.placeholder-yellow::placeholder { color: yellow; }
|
|
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
const output = `
|
|
|
|
|
.placeholder-yellow::placeholder { color: yellow; }
|
|
|
|
|
.hover\\:placeholder-yellow:hover::placeholder { color: yellow; }
|
|
|
|
|
.focus-within\\:placeholder-yellow:focus-within::placeholder { color: yellow; }
|
2020-05-26 11:10:03 -07:00
|
|
|
.focus-visible\\:placeholder-yellow:focus-visible::placeholder { color: yellow; }
|
2019-06-12 08:57:47 -04:00
|
|
|
.focus\\:placeholder-yellow:focus::placeholder { color: yellow; }
|
|
|
|
|
.active\\:placeholder-yellow:active::placeholder { color: yellow; }
|
2019-06-13 07:52:37 -04:00
|
|
|
.group:hover .group-hover\\:placeholder-yellow::placeholder { color: yellow; }
|
2019-06-12 08:57:47 -04:00
|
|
|
`
|
|
|
|
|
|
2020-10-16 15:39:44 -04:00
|
|
|
return run(input).then((result) => {
|
2019-06-12 08:57:47 -04:00
|
|
|
expect(result.css).toMatchCss(output)
|
|
|
|
|
expect(result.warnings().length).toBe(0)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
2019-02-15 10:52:01 -05:00
|
|
|
test('the default variant can be generated in a specified position', () => {
|
|
|
|
|
const input = `
|
2020-10-16 10:24:25 -04:00
|
|
|
@variants focus, active, DEFAULT, hover {
|
2019-02-15 10:52:01 -05:00
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
const output = `
|
2019-07-20 11:24:03 -04:00
|
|
|
.focus\\:banana:focus { color: yellow; }
|
|
|
|
|
.focus\\:chocolate:focus { color: brown; }
|
|
|
|
|
.active\\:banana:active { color: yellow; }
|
|
|
|
|
.active\\:chocolate:active { color: brown; }
|
|
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
.hover\\:banana:hover { color: yellow; }
|
|
|
|
|
.hover\\:chocolate:hover { color: brown; }
|
2018-06-22 10:35:47 -04:00
|
|
|
`
|
|
|
|
|
|
2018-07-11 12:13:50 -04:00
|
|
|
return run(input, {
|
|
|
|
|
...config,
|
2020-10-16 15:39:44 -04:00
|
|
|
}).then((result) => {
|
2018-06-22 10:35:47 -04:00
|
|
|
expect(result.css).toMatchCss(output)
|
|
|
|
|
expect(result.warnings().length).toBe(0)
|
|
|
|
|
})
|
|
|
|
|
})
|
2018-06-22 12:30:30 -04:00
|
|
|
|
2019-07-28 10:46:59 -04:00
|
|
|
test('nested rules are not modified', () => {
|
|
|
|
|
const input = `
|
|
|
|
|
@variants focus, active, hover {
|
|
|
|
|
.banana {
|
|
|
|
|
color: yellow;
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
const output = `
|
|
|
|
|
.banana {
|
|
|
|
|
color: yellow;
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
}
|
|
|
|
|
.focus\\:banana:focus {
|
|
|
|
|
color: yellow;
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
}
|
|
|
|
|
.active\\:banana:active {
|
|
|
|
|
color: yellow;
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
}
|
|
|
|
|
.hover\\:banana:hover {
|
|
|
|
|
color: yellow;
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
return run(input, {
|
|
|
|
|
...config,
|
2020-10-16 15:39:44 -04:00
|
|
|
}).then((result) => {
|
2019-07-28 10:46:59 -04:00
|
|
|
expect(result.css).toMatchCss(output)
|
|
|
|
|
expect(result.warnings().length).toBe(0)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
2018-10-31 15:52:32 -04:00
|
|
|
test('plugin variants can modify rules using the raw PostCSS API', () => {
|
2018-06-26 10:31:06 -04:00
|
|
|
const input = `
|
|
|
|
|
@variants important {
|
|
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
const output = `
|
2019-07-20 11:24:03 -04:00
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
.\\!banana { color: yellow !important; }
|
|
|
|
|
.\\!chocolate { color: brown !important; }
|
2018-06-26 10:31:06 -04:00
|
|
|
`
|
|
|
|
|
|
|
|
|
|
return run(input, {
|
|
|
|
|
...config,
|
|
|
|
|
plugins: [
|
|
|
|
|
...config.plugins,
|
2020-10-16 13:30:06 -04:00
|
|
|
function ({ addVariant }) {
|
2018-06-26 10:31:06 -04:00
|
|
|
addVariant('important', ({ container }) => {
|
2020-10-16 15:39:44 -04:00
|
|
|
container.walkRules((rule) => {
|
2018-06-26 10:31:06 -04:00
|
|
|
rule.selector = `.\\!${rule.selector.slice(1)}`
|
2020-10-16 15:39:44 -04:00
|
|
|
rule.walkDecls((decl) => {
|
2018-06-26 10:31:06 -04:00
|
|
|
decl.important = true
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
],
|
2020-10-16 15:39:44 -04:00
|
|
|
}).then((result) => {
|
2018-06-26 10:31:06 -04:00
|
|
|
expect(result.css).toMatchCss(output)
|
|
|
|
|
expect(result.warnings().length).toBe(0)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
2018-10-31 15:52:32 -04:00
|
|
|
test('plugin variants can modify selectors with a simplified API', () => {
|
2018-06-22 12:30:30 -04:00
|
|
|
const input = `
|
|
|
|
|
@variants first-child {
|
|
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
const output = `
|
2019-07-20 11:24:03 -04:00
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
.first-child\\:banana:first-child { color: yellow; }
|
|
|
|
|
.first-child\\:chocolate:first-child { color: brown; }
|
2018-06-22 12:30:30 -04:00
|
|
|
`
|
|
|
|
|
|
2018-06-22 20:52:59 -04:00
|
|
|
return run(input, {
|
2018-06-22 12:30:30 -04:00
|
|
|
...config,
|
|
|
|
|
plugins: [
|
|
|
|
|
...config.plugins,
|
2020-10-16 13:30:06 -04:00
|
|
|
function ({ addVariant, e }) {
|
Require plugin authors to manually escape variants
Not 100% convinced this is a net positive change, but I regret not having done things this way at the beginning.
In 0.x, we pass the `separator` and `className` values already escaped, so `:` comes through as `\:` for example, and `w-1/2` comes through as `w-1\/2`.
At first this sounds fine, less work for the plugin author right? But CSS escaping rules are kind of complicated and you have to escape characters differently depending on whether or not they are at the start of an identifier.
For example, it's totally fine for a class to contain a zero (`0` ), but it can't _start_ with a zero. For a class to start with a zero, it needs to be escaped like this: `\30 `
This means that as a general rule, trying to escape the individual segments of a class separately is a bad idea — you should escape the class as a whole so only the necessary escaping is applied. We break this rule when we pre-escape the separator and className for plugin authors who use the `modifySelectors` function.
We already require users to manually escape class names when they are using `addUtilities` or `addComponents`, so to me it feels more consistent for things to work this way and it's how they should have worked from day one.
Basically this code:
```js
function({ addVariant }) {
addVariant('first-child', ({ modifySelectors, separator }) => {
modifySelectors(({ className }) => {
return `.first-child${separator}${className}:first-child`
})
})
},
```
...would need to be re-written like this if I merge this change:
```js
function({ addVariant, e }) {
addVariant('first-child', ({ modifySelectors, separator }) => {
modifySelectors(({ className }) => {
return `.${e(`first-child${separator}${className}`)}:first-child`
})
})
},
```
Although I think this is the right way for this to work, I hesitate because it's a breaking change that makes any variant plugins authored for 0.x incompatible with 1.x. It's an easy fix on the plugin author's part, but it's still annoying.
I'm leaning towards merging so I don't regret this even more later when the plugin ecosystem is a lot bigger. Anyone have any thoughts?
2019-02-28 10:17:09 -05:00
|
|
|
addVariant('first-child', ({ modifySelectors, separator }) => {
|
|
|
|
|
modifySelectors(({ className }) => {
|
|
|
|
|
return `.${e(`first-child${separator}${className}`)}:first-child`
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
],
|
2020-10-16 15:39:44 -04:00
|
|
|
}).then((result) => {
|
Require plugin authors to manually escape variants
Not 100% convinced this is a net positive change, but I regret not having done things this way at the beginning.
In 0.x, we pass the `separator` and `className` values already escaped, so `:` comes through as `\:` for example, and `w-1/2` comes through as `w-1\/2`.
At first this sounds fine, less work for the plugin author right? But CSS escaping rules are kind of complicated and you have to escape characters differently depending on whether or not they are at the start of an identifier.
For example, it's totally fine for a class to contain a zero (`0` ), but it can't _start_ with a zero. For a class to start with a zero, it needs to be escaped like this: `\30 `
This means that as a general rule, trying to escape the individual segments of a class separately is a bad idea — you should escape the class as a whole so only the necessary escaping is applied. We break this rule when we pre-escape the separator and className for plugin authors who use the `modifySelectors` function.
We already require users to manually escape class names when they are using `addUtilities` or `addComponents`, so to me it feels more consistent for things to work this way and it's how they should have worked from day one.
Basically this code:
```js
function({ addVariant }) {
addVariant('first-child', ({ modifySelectors, separator }) => {
modifySelectors(({ className }) => {
return `.first-child${separator}${className}:first-child`
})
})
},
```
...would need to be re-written like this if I merge this change:
```js
function({ addVariant, e }) {
addVariant('first-child', ({ modifySelectors, separator }) => {
modifySelectors(({ className }) => {
return `.${e(`first-child${separator}${className}`)}:first-child`
})
})
},
```
Although I think this is the right way for this to work, I hesitate because it's a breaking change that makes any variant plugins authored for 0.x incompatible with 1.x. It's an easy fix on the plugin author's part, but it's still annoying.
I'm leaning towards merging so I don't regret this even more later when the plugin ecosystem is a lot bigger. Anyone have any thoughts?
2019-02-28 10:17:09 -05:00
|
|
|
expect(result.css).toMatchCss(output)
|
|
|
|
|
expect(result.warnings().length).toBe(0)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('plugin variants that use modify selectors need to manually escape the class name they are modifying', () => {
|
|
|
|
|
const input = `
|
|
|
|
|
@variants first-child {
|
|
|
|
|
.banana-1\\/2 { color: yellow; }
|
|
|
|
|
.chocolate-1\\.5 { color: brown; }
|
|
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
const output = `
|
2019-07-20 11:24:03 -04:00
|
|
|
.banana-1\\/2 { color: yellow; }
|
|
|
|
|
.chocolate-1\\.5 { color: brown; }
|
|
|
|
|
.first-child\\:banana-1\\/2:first-child { color: yellow; }
|
|
|
|
|
.first-child\\:chocolate-1\\.5:first-child { color: brown; }
|
Require plugin authors to manually escape variants
Not 100% convinced this is a net positive change, but I regret not having done things this way at the beginning.
In 0.x, we pass the `separator` and `className` values already escaped, so `:` comes through as `\:` for example, and `w-1/2` comes through as `w-1\/2`.
At first this sounds fine, less work for the plugin author right? But CSS escaping rules are kind of complicated and you have to escape characters differently depending on whether or not they are at the start of an identifier.
For example, it's totally fine for a class to contain a zero (`0` ), but it can't _start_ with a zero. For a class to start with a zero, it needs to be escaped like this: `\30 `
This means that as a general rule, trying to escape the individual segments of a class separately is a bad idea — you should escape the class as a whole so only the necessary escaping is applied. We break this rule when we pre-escape the separator and className for plugin authors who use the `modifySelectors` function.
We already require users to manually escape class names when they are using `addUtilities` or `addComponents`, so to me it feels more consistent for things to work this way and it's how they should have worked from day one.
Basically this code:
```js
function({ addVariant }) {
addVariant('first-child', ({ modifySelectors, separator }) => {
modifySelectors(({ className }) => {
return `.first-child${separator}${className}:first-child`
})
})
},
```
...would need to be re-written like this if I merge this change:
```js
function({ addVariant, e }) {
addVariant('first-child', ({ modifySelectors, separator }) => {
modifySelectors(({ className }) => {
return `.${e(`first-child${separator}${className}`)}:first-child`
})
})
},
```
Although I think this is the right way for this to work, I hesitate because it's a breaking change that makes any variant plugins authored for 0.x incompatible with 1.x. It's an easy fix on the plugin author's part, but it's still annoying.
I'm leaning towards merging so I don't regret this even more later when the plugin ecosystem is a lot bigger. Anyone have any thoughts?
2019-02-28 10:17:09 -05:00
|
|
|
`
|
|
|
|
|
|
|
|
|
|
return run(input, {
|
|
|
|
|
...config,
|
|
|
|
|
plugins: [
|
|
|
|
|
...config.plugins,
|
2020-10-16 13:30:06 -04:00
|
|
|
function ({ addVariant, e }) {
|
2018-06-26 10:31:06 -04:00
|
|
|
addVariant('first-child', ({ modifySelectors, separator }) => {
|
|
|
|
|
modifySelectors(({ className }) => {
|
Require plugin authors to manually escape variants
Not 100% convinced this is a net positive change, but I regret not having done things this way at the beginning.
In 0.x, we pass the `separator` and `className` values already escaped, so `:` comes through as `\:` for example, and `w-1/2` comes through as `w-1\/2`.
At first this sounds fine, less work for the plugin author right? But CSS escaping rules are kind of complicated and you have to escape characters differently depending on whether or not they are at the start of an identifier.
For example, it's totally fine for a class to contain a zero (`0` ), but it can't _start_ with a zero. For a class to start with a zero, it needs to be escaped like this: `\30 `
This means that as a general rule, trying to escape the individual segments of a class separately is a bad idea — you should escape the class as a whole so only the necessary escaping is applied. We break this rule when we pre-escape the separator and className for plugin authors who use the `modifySelectors` function.
We already require users to manually escape class names when they are using `addUtilities` or `addComponents`, so to me it feels more consistent for things to work this way and it's how they should have worked from day one.
Basically this code:
```js
function({ addVariant }) {
addVariant('first-child', ({ modifySelectors, separator }) => {
modifySelectors(({ className }) => {
return `.first-child${separator}${className}:first-child`
})
})
},
```
...would need to be re-written like this if I merge this change:
```js
function({ addVariant, e }) {
addVariant('first-child', ({ modifySelectors, separator }) => {
modifySelectors(({ className }) => {
return `.${e(`first-child${separator}${className}`)}:first-child`
})
})
},
```
Although I think this is the right way for this to work, I hesitate because it's a breaking change that makes any variant plugins authored for 0.x incompatible with 1.x. It's an easy fix on the plugin author's part, but it's still annoying.
I'm leaning towards merging so I don't regret this even more later when the plugin ecosystem is a lot bigger. Anyone have any thoughts?
2019-02-28 10:17:09 -05:00
|
|
|
return `.${e(`first-child${separator}${className}`)}:first-child`
|
2018-06-26 10:31:06 -04:00
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
],
|
2020-10-16 15:39:44 -04:00
|
|
|
}).then((result) => {
|
2018-06-26 10:31:06 -04:00
|
|
|
expect(result.css).toMatchCss(output)
|
|
|
|
|
expect(result.warnings().length).toBe(0)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
2018-10-31 15:52:32 -04:00
|
|
|
test('plugin variants can wrap rules in another at-rule using the raw PostCSS API', () => {
|
2018-06-26 10:31:06 -04:00
|
|
|
const input = `
|
|
|
|
|
@variants supports-grid {
|
|
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
const output = `
|
2019-07-20 11:24:03 -04:00
|
|
|
.banana { color: yellow; }
|
|
|
|
|
.chocolate { color: brown; }
|
|
|
|
|
@supports (display: grid) {
|
|
|
|
|
.supports-grid\\:banana { color: yellow; }
|
|
|
|
|
.supports-grid\\:chocolate { color: brown; }
|
|
|
|
|
}
|
2018-06-26 10:31:06 -04:00
|
|
|
`
|
|
|
|
|
|
|
|
|
|
return run(input, {
|
|
|
|
|
...config,
|
|
|
|
|
plugins: [
|
|
|
|
|
...config.plugins,
|
2020-10-16 13:30:06 -04:00
|
|
|
function ({ addVariant, e }) {
|
2018-06-26 10:31:06 -04:00
|
|
|
addVariant('supports-grid', ({ container, separator }) => {
|
2020-05-26 11:10:03 -07:00
|
|
|
const supportsRule = postcss.atRule({
|
|
|
|
|
name: 'supports',
|
|
|
|
|
params: '(display: grid)',
|
|
|
|
|
})
|
2018-06-26 10:31:06 -04:00
|
|
|
supportsRule.nodes = container.nodes
|
|
|
|
|
container.nodes = [supportsRule]
|
2020-10-16 15:39:44 -04:00
|
|
|
supportsRule.walkRules((rule) => {
|
Require plugin authors to manually escape variants
Not 100% convinced this is a net positive change, but I regret not having done things this way at the beginning.
In 0.x, we pass the `separator` and `className` values already escaped, so `:` comes through as `\:` for example, and `w-1/2` comes through as `w-1\/2`.
At first this sounds fine, less work for the plugin author right? But CSS escaping rules are kind of complicated and you have to escape characters differently depending on whether or not they are at the start of an identifier.
For example, it's totally fine for a class to contain a zero (`0` ), but it can't _start_ with a zero. For a class to start with a zero, it needs to be escaped like this: `\30 `
This means that as a general rule, trying to escape the individual segments of a class separately is a bad idea — you should escape the class as a whole so only the necessary escaping is applied. We break this rule when we pre-escape the separator and className for plugin authors who use the `modifySelectors` function.
We already require users to manually escape class names when they are using `addUtilities` or `addComponents`, so to me it feels more consistent for things to work this way and it's how they should have worked from day one.
Basically this code:
```js
function({ addVariant }) {
addVariant('first-child', ({ modifySelectors, separator }) => {
modifySelectors(({ className }) => {
return `.first-child${separator}${className}:first-child`
})
})
},
```
...would need to be re-written like this if I merge this change:
```js
function({ addVariant, e }) {
addVariant('first-child', ({ modifySelectors, separator }) => {
modifySelectors(({ className }) => {
return `.${e(`first-child${separator}${className}`)}:first-child`
})
})
},
```
Although I think this is the right way for this to work, I hesitate because it's a breaking change that makes any variant plugins authored for 0.x incompatible with 1.x. It's an easy fix on the plugin author's part, but it's still annoying.
I'm leaning towards merging so I don't regret this even more later when the plugin ecosystem is a lot bigger. Anyone have any thoughts?
2019-02-28 10:17:09 -05:00
|
|
|
rule.selector = `.${e(`supports-grid${separator}${rule.selector.slice(1)}`)}`
|
2018-06-26 10:31:06 -04:00
|
|
|
})
|
2018-06-22 12:30:30 -04:00
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
],
|
2020-10-16 15:39:44 -04:00
|
|
|
}).then((result) => {
|
2018-06-22 12:30:30 -04:00
|
|
|
expect(result.css).toMatchCss(output)
|
|
|
|
|
expect(result.warnings().length).toBe(0)
|
|
|
|
|
})
|
|
|
|
|
})
|