use `lightningcss` and `prettier` for `toMatchFormattedCss`
Lightning CSS applies some optimizations and prettier ensures that the
output is always formatted in the same way.
In a future version, we will be using Lightning CSS to apply
optimizations like handling the nesting, optimizing selectors, sorting
declarations, simplifying colors and so on.
To guarantee that we have the same output in both the normal version and
OXIDE version, we can apply lightningcss in our tests so that it always
produces correct results in both version with the same optimizations
applied.
This also aliases `toMatchCss` to `toMatchFormattedCss` so that we can
guarantee the same output. This has a small caveat where `lightningcss`
is pretty strict. It will complain about `@media screen(md) {}` for
example. This is correct because it is not valid CSS, but we eventually
take care of it.
In this case, we will catch the error and use prettier instead (since
this should only happen in tests that doesn't use end-to-end tests, but
uses a more specialized test instead). If prettier still finds an error,
we will ensure to throw the original error.
Eventually, all the "lower" level tests, should probably become higher
level end-to-end tests. But let's do everything step by step!
321 lines
5.9 KiB
JavaScript
321 lines
5.9 KiB
JavaScript
import parseObjectStyles from '../src/util/parseObjectStyles'
|
|
import postcss from 'postcss'
|
|
import { env } from '../src/lib/sharedState'
|
|
|
|
// The skipped tests for OXIDE are testing nested structures. In the oxide version we make use of
|
|
// nesting, and parseObjectStyles keeps everything nested insted of unnesting everything.
|
|
let t = env.OXIDE ? test.skip : test
|
|
|
|
function css(nodes) {
|
|
return postcss.root({ nodes }).toString()
|
|
}
|
|
|
|
test('it parses simple single class definitions', () => {
|
|
const result = parseObjectStyles({
|
|
'.foobar': {
|
|
backgroundColor: 'red',
|
|
color: 'white',
|
|
padding: '1rem',
|
|
},
|
|
})
|
|
|
|
expect(css(result)).toMatchCss(`
|
|
.foobar {
|
|
background-color: red;
|
|
color: white;
|
|
padding: 1rem
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('it parses multiple class definitions', () => {
|
|
const result = parseObjectStyles({
|
|
'.foo': {
|
|
backgroundColor: 'red',
|
|
color: 'white',
|
|
padding: '1rem',
|
|
},
|
|
'.bar': {
|
|
width: '200px',
|
|
height: '100px',
|
|
},
|
|
})
|
|
|
|
expect(css(result)).toMatchCss(`
|
|
.foo {
|
|
background-color: red;
|
|
color: white;
|
|
padding: 1rem
|
|
}
|
|
.bar {
|
|
width: 200px;
|
|
height: 100px
|
|
}
|
|
`)
|
|
})
|
|
|
|
t('it parses nested pseudo-selectors', () => {
|
|
const result = parseObjectStyles({
|
|
'.foo': {
|
|
backgroundColor: 'red',
|
|
color: 'white',
|
|
padding: '1rem',
|
|
'&:hover': {
|
|
backgroundColor: 'orange',
|
|
},
|
|
'&:focus': {
|
|
backgroundColor: 'blue',
|
|
},
|
|
},
|
|
})
|
|
|
|
expect(css(result)).toMatchCss(`
|
|
.foo {
|
|
background-color: red;
|
|
color: white;
|
|
padding: 1rem;
|
|
}
|
|
.foo:hover {
|
|
background-color: orange;
|
|
}
|
|
.foo:focus {
|
|
background-color: blue;
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('it parses top-level media queries', () => {
|
|
const result = parseObjectStyles({
|
|
'@media (min-width: 200px)': {
|
|
'.foo': {
|
|
backgroundColor: 'orange',
|
|
},
|
|
},
|
|
})
|
|
|
|
expect(css(result)).toMatchCss(`
|
|
@media (min-width: 200px) {
|
|
.foo {
|
|
background-color: orange
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
t('it parses nested media queries', () => {
|
|
const result = parseObjectStyles({
|
|
'.foo': {
|
|
backgroundColor: 'red',
|
|
color: 'white',
|
|
padding: '1rem',
|
|
'@media (min-width: 200px)': {
|
|
backgroundColor: 'orange',
|
|
},
|
|
},
|
|
})
|
|
|
|
expect(css(result)).toMatchCss(`
|
|
.foo {
|
|
background-color: red;
|
|
color: white;
|
|
padding: 1rem;
|
|
}
|
|
@media (min-width: 200px) {
|
|
.foo {
|
|
background-color: orange;
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
t('it bubbles nested screen rules', () => {
|
|
const result = parseObjectStyles({
|
|
'.foo': {
|
|
backgroundColor: 'red',
|
|
color: 'white',
|
|
padding: '1rem',
|
|
'@screen sm': {
|
|
backgroundColor: 'orange',
|
|
},
|
|
},
|
|
})
|
|
|
|
expect(css(result)).toMatchCss(`
|
|
.foo {
|
|
background-color: red;
|
|
color: white;
|
|
padding: 1rem;
|
|
}
|
|
@screen sm {
|
|
.foo {
|
|
background-color: orange;
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
t('it parses pseudo-selectors in nested media queries', () => {
|
|
const result = parseObjectStyles({
|
|
'.foo': {
|
|
backgroundColor: 'red',
|
|
color: 'white',
|
|
padding: '1rem',
|
|
'&:hover': {
|
|
'@media (min-width: 200px)': {
|
|
backgroundColor: 'orange',
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
expect(css(result)).toMatchCss(`
|
|
.foo {
|
|
background-color: red;
|
|
color: white;
|
|
padding: 1rem;
|
|
}
|
|
@media (min-width: 200px) {
|
|
.foo:hover {
|
|
background-color: orange;
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
t('it parses descendant selectors', () => {
|
|
const result = parseObjectStyles({
|
|
'.foo': {
|
|
backgroundColor: 'red',
|
|
color: 'white',
|
|
padding: '1rem',
|
|
'.bar': {
|
|
backgroundColor: 'orange',
|
|
},
|
|
},
|
|
})
|
|
|
|
expect(css(result)).toMatchCss(`
|
|
.foo {
|
|
background-color: red;
|
|
color: white;
|
|
padding: 1rem;
|
|
}
|
|
.foo .bar {
|
|
background-color: orange;
|
|
}
|
|
`)
|
|
})
|
|
|
|
t('it parses nested multi-class selectors', () => {
|
|
const result = parseObjectStyles({
|
|
'.foo': {
|
|
backgroundColor: 'red',
|
|
color: 'white',
|
|
padding: '1rem',
|
|
'&.bar': {
|
|
backgroundColor: 'orange',
|
|
},
|
|
},
|
|
})
|
|
|
|
expect(css(result)).toMatchCss(`
|
|
.foo {
|
|
background-color: red;
|
|
color: white;
|
|
padding: 1rem;
|
|
}
|
|
.foo.bar {
|
|
background-color: orange;
|
|
}
|
|
`)
|
|
})
|
|
|
|
t('it parses nested multi-class selectors in media queries', () => {
|
|
const result = parseObjectStyles({
|
|
'.foo': {
|
|
backgroundColor: 'red',
|
|
color: 'white',
|
|
padding: '1rem',
|
|
'@media (min-width: 200px)': {
|
|
'&.bar': {
|
|
backgroundColor: 'orange',
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
expect(css(result)).toMatchCss(`
|
|
.foo {
|
|
background-color: red;
|
|
color: white;
|
|
padding: 1rem;
|
|
}
|
|
@media (min-width: 200px) {
|
|
.foo.bar {
|
|
background-color: orange;
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
t('it strips empty selectors when nesting', () => {
|
|
const result = parseObjectStyles({
|
|
'.foo': {
|
|
'.bar': {
|
|
backgroundColor: 'orange',
|
|
},
|
|
},
|
|
})
|
|
|
|
expect(css(result)).toMatchCss(`
|
|
.foo .bar {
|
|
background-color: orange
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('it can parse an array of styles', () => {
|
|
const result = parseObjectStyles([
|
|
{
|
|
'.foo': {
|
|
backgroundColor: 'orange',
|
|
},
|
|
},
|
|
{
|
|
'.bar': {
|
|
backgroundColor: 'red',
|
|
},
|
|
},
|
|
{
|
|
'.foo': {
|
|
backgroundColor: 'blue',
|
|
},
|
|
},
|
|
])
|
|
|
|
expect(css(result)).toMatchCss(`
|
|
.foo {
|
|
background-color: orange
|
|
}
|
|
.bar {
|
|
background-color: red
|
|
}
|
|
.foo {
|
|
background-color: blue
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('custom properties preserve their case', () => {
|
|
const result = parseObjectStyles({
|
|
':root': {
|
|
'--colors-aColor-500': '0',
|
|
},
|
|
})
|
|
|
|
expect(css(result)).toMatchCss(`
|
|
:root {
|
|
--colors-aColor-500: 0;
|
|
}
|
|
`)
|
|
})
|