unnestcss
Version:
A library for un-nesting CSS
1,362 lines (1,253 loc) • 34.5 kB
text/typescript
import { describe, expect, test } from "vitest";
import { compile, serialize, stringify } from "../stylis.ts";
const stylis = (declarations, selectors = ".user") =>
serialize(compile(`${selectors}{${declarations}}`), stringify);
describe("Parser", () => {
test("unnested", () => {
expect(serialize(compile(`--foo:none;@supports{--bar:none;}`), stringify)).to.equal(
`--foo:none;@supports{--bar:none;}`,
);
});
test("escape", () => {
expect(
stylis(`
height:calc(\\))\t!important;
`),
).to.equal(`.user{height:calc(\\))!important;}`);
});
test("calc", () => {
expect(
stylis(`
height:calc( 100vh - 1px );
height:calc(
100vh -
1px
);
`),
).to.equal(
`.user{height:calc( 100vh - 1px );height:calc(\n 100vh -\n 1px\n );}`,
);
});
test("at-rules", () => {
expect(
stylis(`
@-ms-viewport {
width:device-width;
}
@viewport {
width:device-width;
}
@page & {
invalid:true;
}
@page {
size:A4 landscape;
}
@document url(://www.w3.org/),url-prefix(//www.w3.org/),domain(mozilla.org),regexp("https:.*") {
body {
color: red;
}
}
@viewport {
min-width:640px;
max-width:800px;
}
@counter-style list {
system:fixed;
symbols:url();
suffix:" ";
}
@-moz-document url-prefix() {
.selector {
color:lime;
}
}
@page {
color:red;
@bottom-right {
content: counter(pages);
margin-right: 1cm;
}
width: none;
@right-bottom {
content: "Content";
}
@left-bottom {
content: "Content";
}
}
`),
).to.equal(
[
`@-ms-viewport{width:device-width;}`,
`@viewport{width:device-width;}`,
`@page &{invalid:true;}`,
`@page{size:A4 landscape;}`,
`@document url(://www.w3.org/),url-prefix(//www.w3.org/),domain(mozilla.org),regexp("https:.*"){.user body{color:red;}}`,
`@viewport{min-width:640px;max-width:800px;}`,
`@counter-style list{system:fixed;symbols:url();suffix:" ";}`,
`@-moz-document url-prefix(){.user .selector{color:lime;}}`,
`@page{color:red;@bottom-right{content:counter(pages);margin-right:1cm;}width:none;@right-bottom{content:"Content";}@left-bottom{content:"Content";}}`,
].join(""),
);
});
test("universal selector", () => {
expect(
stylis(`
* {
color:red;
}
svg {
&, & * {
fill: currentColor;
}
}
* * {color:hotpink;}
`),
).to.equal(
[
`.user *{color:red;}`,
`.user svg,.user svg *{fill:currentColor;}`,
`.user * *{color:hotpink;}`,
].join(""),
);
});
test("flat", () => {
expect(
stylis(`
color:20px;
font-size:20px
`),
).to.equal(`.user{color:20px;font-size:20px;}`);
});
test("namespace", () => {
expect(
stylis(`
& {
color:red;
}
`),
).to.equal(`.user{color:red;}`);
});
test("& in value in a nested function", () => {
const urlOneX = "https://images.ctfassets.net/test.jpg?fm=webp&q=70&w=1000";
const urlTwoX = "https://images.ctfassets.net/test.jpg?fm=webp&q=70&w=2000";
expect(
stylis(`
background-image: image-set(url(${urlOneX}) 1x, url(${urlTwoX}) 2x);
`),
).to.equal(`.user{background-image:image-set(url(${urlOneX}) 1x, url(${urlTwoX}) 2x);}`);
});
test("& in a string", () => {
expect(
stylis(`
& [href="https://css-tricks.com?a=1&b=2"] {
color:red;
}
`),
).to.equal(`.user [href="https://css-tricks.com?a=1&b=2"]{color:red;}`);
});
test("& root should be removed", () => {
expect(
stylis(
`
color:red;
`,
"&",
),
).to.equal(``);
});
test("& root should be removed, issue: #333", () => {
expect(
stylis(
`
color: red;
`,
":where(.cls)& span",
),
).to.equal(`:where(.cls) span{color:red;}`);
});
test("noop removal of empty variables", () => {
expect(
stylis(`
--tw-brightness:/*!*/;
`),
).to.equal(`.user{--tw-brightness:;}`);
});
test("& in first selector within a comma-separated list", () => {
expect(
stylis(`
div {
display: flex;
&.foo,
p:not(:last-child) {
background: red;
}
}
`),
).to.equal(
`.user div{display:flex;}.user div.foo,.user div p:not(:last-child){background:red;}`,
);
});
test("escaped chars in selector identifiers", () => {
expect(
stylis(`
&.B\\&W{color:red;}
&.\\@example\\.com{color:blue;}
&.owner\\/founder{color:green;}
&.discount\\% {color:purple;}
`),
).to.equal(
[
".user.B\\&W{color:red;}",
".user.\\@example\\.com{color:blue;}",
".user.owner\\/founder{color:green;}",
// while double spaces after escaped hex codes need to be preserved,
// after an escaped character / code point it need not be preserved
".user.discount\\%{color:purple;}",
].join(""),
);
});
test("escaped hex codes in selector identifiers", () => {
expect(
stylis(`
&.B\\26W{color:red;}
&.B\\000026W{color:green;}
&.B\\26 W{color:blue;}
`),
).to.equal(
[
".user.B\\26W{color:red;}",
".user.B\\000026W{color:green;}",
".user.B\\26 W{color:blue;}",
].join(""),
);
});
test("double spaces after escaped hex codes in selector identifiers", () => {
expect(
stylis(`
&.endsWith\\0000A9 a.childNode{color:green;}
&.endsWith\\AE a.childNode{color:yellow;}
&.Q\\000026A a.childNode{color:purple;}
`),
).to.equal(
[
// next rules are important because the hex digits terminating space
// is not the same as a combinator space
".user.endsWith\\0000A9 a.childNode{color:green;}",
".user.endsWith\\AE a.childNode{color:yellow;}",
// max 6 hex chars in escape sequence - whitespace after 7th char
// is just normal whitespace and can be collapsed
".user.Q\\000026A a.childNode{color:purple;}",
].join(""),
);
});
test("comments", () => {
expect(
stylis(`
// line comment
// color: red;
/**
* removes block comments and line comments,
* there's a fire in the house // there is
*/
button /*
// what's
xxx
*/
{color: blue;}
// hello
button /* 1 */
{
color: red; /* 2 */
}
/*! 1 */
color: red;
/*! 2 */
h1 {
/*! 1 */
color: red;
/*! 2 */
color: red;
/*! 3 */
}
`),
).to.equal(
[
`.user{color:red;}`,
`.user button{color:blue;}.user button{color:red;}`,
`.user h1{color:red;color:red;}`,
].join(""),
);
});
test("&", () => {
expect(
stylis(`
& {
color:blue;
}
&&& {
color:red;
}
& + & {
color:red;
}
.wrapper button& {
color:red;
}
&:hover & {
color: green;
}
div:hover & {
color: green;
}
div:hover & {
h1 & {
color:red;
}
}
`),
).to.equal(
[
`.user{color:blue;}`,
`.user.user.user{color:red;}`,
`.user+.user{color:red;}`,
`.wrapper button.user{color:red;}`,
`.user:hover .user{color:green;}`,
`div:hover .user{color:green;}`,
`h1 div:hover .user{color:red;}`,
].join(""),
);
});
test("&:before", () => {
expect(
stylis(`
&:before{
color:blue;
}
`),
).to.equal(`.user:before{color:blue;}`);
});
test("& :hover", () => {
expect(
stylis(`
& :hover{
color:blue;
}
`),
).to.equal(`.user :hover{color:blue;}`);
});
test("div :hover", () => {
expect(
stylis(`
div :hover {
color:blue;
}
`),
).to.equal(`.user div :hover{color:blue;}`);
});
test("@import", () => {
expect(stylis(`@import url('http://example.com');`)).to.equal(
`@import url('http://example.com');`,
);
});
test("@namespace", () => {
expect(
stylis(`
@namespace svg url('http://www.w3.org/2000/svg');
a {
color: orangered;
text-decoration: underline dashed;
font-weight: bold;
}
svg|a {
fill: blueviolet;
text-decoration: underline solid;
text-transform: uppercase;
}
`),
).to.equal(
[
`@namespace svg url('http://www.w3.org/2000/svg');`,
`.user a{color:orangered;text-decoration:underline dashed;font-weight:bold;}`,
`.user svg|a{fill:blueviolet;text-decoration:underline solid;text-transform:uppercase;}`,
].join(""),
);
});
test("@supports", () => {
expect(
stylis(`
@supports (display:block) {
color:red;
h1 {
color:red;
h2 {
color:blue;
}
}
display:none;
}
@supports (appearance: none) {
color:red;
}
@supports (backdrop-filter: blur(10px)) {
backdrop-filter: blur(10px);
}
`),
).to.equal(
[
"@supports (display:block){.user{color:red;display:none;}.user h1{color:red;}.user h1 h2{color:blue;}}",
"@supports (appearance: none){.user{color:red;}}",
"@supports (backdrop-filter: blur(10px)){.user{backdrop-filter:blur(10px);}}",
].join(""),
);
});
test("@media", () => {
expect(
stylis(`
@media (max-width:600px) {
color:red;
h1 {
color:red;
h2 {
color:blue;
}
}
display:none;
}
@media (min-width:576px) {
&.card-deck {
.card {
&:not(:first-child) {
margin-left:15px;
}
&:not(:last-child) {
margin-right:15px;
}
}
}
}
@supports (display:block) {
@media (min-width:10px) {
background-color:seagreen;
}
}
@media (max-width:600px) {
& { color:red }
}
&:hover {
color:orange
}
`),
).to.equal(
[
"@media (max-width:600px){.user{color:red;display:none;}.user h1{color:red;}.user h1 h2{color:blue;}}",
"@media (min-width:576px){.user.card-deck .card:not(:first-child){margin-left:15px;}.user.card-deck .card:not(:last-child){margin-right:15px;}}",
"@supports (display:block){@media (min-width:10px){.user{background-color:seagreen;}}}",
"@media (max-width:600px){.user{color:red;}}.user:hover{color:orange;}",
].join(""),
);
});
test("@media specifity", () => {
expect(
stylis(`
> #box-not-working {
background:red;
padding-left:8px;
width:10px;
@media only screen and (min-width:10px) {
width: calc(10px + 90px * (100vw - 10px) / 90);
}
@media only screen and (min-width:90px) {
width: 90px;
}
height: 10px;
@media only screen and (min-width:10px) {
height: calc(10px + 90px * (100vw - 10px) / 90);
}
@media only screen and (min-width:90px) {
height: 90px;
}
}
`),
).to.equal(
[
".user >#box-not-working{background:red;padding-left:8px;width:10px;height:10px;}",
"@media only screen and (min-width:10px){.user >#box-not-working{width:calc(10px + 90px * (100vw - 10px) / 90);}}",
"@media only screen and (min-width:90px){.user >#box-not-working{width:90px;}}",
"@media only screen and (min-width:10px){.user >#box-not-working{height:calc(10px + 90px * (100vw - 10px) / 90);}}",
"@media only screen and (min-width:90px){.user >#box-not-working{height:90px;}}",
].join(""),
);
});
test("@font-face", () => {
expect(
stylis(`
@font-face {
font-family:Pangolin;
src:url('Pangolin-Regular.ttf') format('truetype');
}
`),
).to.equal(
`@font-face{font-family:Pangolin;src:url('Pangolin-Regular.ttf') format('truetype');}`,
);
});
test("multiple selectors", () => {
expect(
stylis(`
span, h1 {
color:red;
}
h1, &:after, &:before {
color:red;
}
`),
).to.equal(
[`.user span,.user h1{color:red;}`, `.user h1,.user:after,.user:before{color:red;}`].join(""),
);
});
test('[title="a,b"] and :matches(a,b)', () => {
expect(
stylis(`
.test:matches(a,b,c), .test {
color:blue;
}
.test[title=","] {
color:red;
}
[title="a,b,c, something"], h1, [title="a,b,c"] {
color:red
}
[title="a"],
[title="b"] {
color:red;
}
`),
).to.equal(
[
`.user .test:matches(a,b,c),.user .test{color:blue;}`,
`.user .test[title=","]{color:red;}`,
`.user [title="a,b,c, something"],.user h1,.user [title="a,b,c"]{color:red;}`,
`.user [title="a"],.user [title="b"]{color:red;}`,
].join(""),
);
});
test("quotes", () => {
expect(
stylis(`
.foo:before {
content:".hello {world}";
content:".hello {world} ' ";
content:'.hello {world} " ';
}
`),
).to.equal(
`.user .foo:before{content:".hello {world}";content:".hello {world} ' ";content:'.hello {world} " ';}`,
);
});
test("quotes - escaping", () => {
expect(
stylis(`
.foo:before {
content:"\\"";
content:"\\\\\\"";
content:'\\'';
content:'\\\\\\'';
}
`),
).to.equal(
`.user .foo:before{content:"\\"";content:"\\\\\\"";content:'\\'';content:'\\\\\\'';}`,
);
});
test("remove empty css", () => {
expect(stylis(`& { }`)).to.equal(``);
});
test("remove empty declarations", () => {
expect(stylis(`width:`)).to.equal(``);
expect(stylis(`height:;`)).to.equal(``);
expect(stylis(`max-width: `)).to.equal(``);
expect(stylis(`max-height: ;`)).to.equal(``);
});
test("urls", () => {
expect(
stylis(
`
background:url(http://url.com/});
background:url(http://url.com//1234) '('; // sdsd
background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEYAAAABCAIAAADsEU8HAAAACXBIW` +
`XMAAAsTAAALEwEAmpwYAAAAIklEQVQI12P8//8/Aw4wbdq0rKysAZG1trbGJXv06FH8sgDIJBbBfp+hFAAAAABJRU5ErkJggg==");`,
),
).to.equal(
[
`.user{background:url(http://url.com/});`,
`background:url(http://url.com//1234) '(';`,
`background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEYAAAABCAIAAADsEU8HAAAACXBIW`,
`XMAAAsTAAALEwEAmpwYAAAAIklEQVQI12P8//8/Aw4wbdq0rKysAZG1trbGJXv06FH8sgDIJBbBfp+hFAAAAABJRU5ErkJggg==");}`,
].join(""),
);
});
test("last semicolon omission", () => {
expect(
stylis(`
.content {
color:red
}
.content {
color:blue
}
`),
).to.equal([`.user .content{color:red;}`, `.user .content{color:blue;}`].join(""));
});
test(":matches(:not())", () => {
expect(
stylis(`
h1:matches(.a,.b,:not(.c)) {
display: none
}
`),
).to.equal(`.user h1:matches(.a,.b,:not(.c)){display:none;}`);
});
test("pseudo-selectors", () => {
expect(
stylis(`
:not(&) {
color: red;
}
:matches(&) {
color: red;
}
:is(&) {
color: red;
}
:any(&) {
color: red;
}
:where(&) {
color: red;
}
:has(&) {
color: red;
}
`),
).to.equal(
[
`:not(.user){color:red;}`,
`:matches(.user){color:red;}`,
`:is(.user){color:red;}`,
`:any(.user){color:red;}`,
`:where(.user){color:red;}`,
`:has(.user){color:red;}`,
].join(""),
);
});
test("nested pseudo-selectors", () => {
expect(
stylis(`
:not(&) {
:matches(&) {
:is(&) {
:any(&) {
:where(&) {
:has(&) {
color: red;
}
}
}
}
}
}
`),
).to.equal(`:has(:where(:any(:is(:matches(:not(.user)))))){color:red;}`);
});
test("pseudo-selector inside pseudo-selector", () => {
expect(
stylis(`
h1:matches(.a,.b,:not(&)) {
display: none
}
`),
).to.equal(`h1:matches(.a,.b,:not(.user)){display:none;}`);
});
test("pseudo-selector with & in a string", () => {
expect(
stylis(`
h1:matches(&,[href="https://css-tricks.com?a=1&b=2"]) {
display: none
}
`),
).to.equal(`h1:matches(.user,[href="https://css-tricks.com?a=1&b=2"]){display:none;}`);
});
test("@keyframes", () => {
expect(
serialize(
compile(`
@-webkit-keyframes slidein {
to { transform:translate(20px); }
}
@keyframes slidein {
to { transform:translate(20px); }
}
@keyframes hahaha {
0%,
1%{t:0}
}
@keyframes infinite-spinning {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
`),
stringify,
),
).to.equal(
[
`@-webkit-keyframes slidein{to{transform:translate(20px);}}`,
`@keyframes slidein{to{transform:translate(20px);}}`,
`@keyframes hahaha{0%,1%{t:0;}}`,
`@keyframes infinite-spinning{from{transform:rotate(0deg);}to{transform:rotate(360deg);}}`,
].join(""),
);
});
test("edge cases", () => {
expect(
stylis(`
@media (min-width:537px) {
border-bottom:4px solid red;
}
&::placeholder {
color:pink;
}
.a {color:'red'}
.b {color:"red"}
.a {color:red;}[role=button]{color:red;}
.b {padding:30 3}
.c {v-text-anchor: middle;}
`),
).to.equal(
[
`@media (min-width:537px){.user{border-bottom:4px solid red;}}`,
`.user::placeholder{color:pink;}`,
`.user .a{color:'red';}`,
`.user .b{color:"red";}`,
`.user .a{color:red;}`,
`.user [role=button]{color:red;}`,
`.user .b{padding:30 3;}`,
`.user .c{v-text-anchor:middle;}`,
].join(""),
);
});
test("whitespace", () => {
expect(
stylis(`
@import\r\n "./a.css";
div {
${"width:0; "}
}
.foo {
color : hotpink;
}
`),
).to.equal(
[`@import "./a.css";`, `.user div{width:0;}`, `.user .foo{color:hotpink;}`].join(""),
);
});
test("no trailing semi-colons", () => {
expect(
stylis(`
h2 {
display:none
}
div:hover
{
color:red
}
`),
).to.equal([".user h2{display:none;}", ".user div:hover{color:red;}"].join(""));
});
test("multiline declaration", () => {
expect(
stylis(`
html {
background:
linear-gradient(0deg, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.8)),
url(/static/background.svg);
}
`),
).to.equal(
`.user html{background:linear-gradient(0deg, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.8)),url(/static/background.svg);}`,
);
});
test("nesting selector multiple levels", () => {
expect(
stylis(`
a {
a {
a {
a {
a {
a {
a {
a {
a {
a {
a {
a {
color:red;
}
}
}
}
}
}
}
}
}
}
}
}
`),
).to.equal(`.user a a a a a a a a a a a a{color:red;}`);
});
test("nesting @container multiple levels", () => {
expect(
stylis(`
div {
@container {
a {
color:red;
@container {
h1 {
color:hotpink;
}
}
}
}
}
`),
).to.equal(
[`@container{.user div a{color:red;}`, `@container{.user div a h1{color:hotpink;}}}`].join(
"",
),
);
});
test("nesting @media multiple levels", () => {
expect(
stylis(`
div {
@media {
a {
color:red;
@media {
h1 {
color:hotpink;
}
}
}
}
}
`),
).to.equal(
[`@media{.user div a{color:red;}`, `@media{.user div a h1{color:hotpink;}}}`].join(""),
);
});
test("noop tail I", () => {
expect(stylis(`color:red/**/`)).to.equal(`.user{color:red;}`);
});
test("noop tail II", () => {
expect(stylis(`color:red//`)).to.equal(".user{color:red;}");
});
test("noop tail III", () => {
expect(stylis(`color:red[]`)).to.equal(`.user{color:red[];}`);
});
test("noop tail IV", () => {
expect(stylis(`color:red()`)).to.equal(`.user{color:red();}`);
});
test("noop tail V", () => {
expect(stylis(`color:red''`)).to.equal(`.user{color:red'';}`);
});
test("noop tail VI", () => {
expect(stylis(`color:red""`)).to.equal(`.user{color:red"";}`);
});
test("noop tail VII", () => {
expect(stylis(`h1{color:red/**}`)).to.equal(`.user h1{color:red;}`);
});
test("context character I", () => {
expect(stylis(`.a{color:red;/* } */}`)).to.equal(`.user .a{color:red;}`);
});
test("context character II", () => {
expect(stylis(`.a{color:red;/*}*/}`)).to.equal(`.user .a{color:red;}`);
});
test("context character III", () => {
expect(stylis(`.a{color:red;/*{*/}`)).to.equal(`.user .a{color:red;}`);
});
test("context character IV", () => {
expect(stylis(`.a{/**/color:red}`)).to.equal(`.user .a{color:red;}`);
});
test("context character V", () => {
expect(stylis(`.a{color:red;/*//color:blue;*/}`)).to.equal(`.user .a{color:red;}`);
});
test("context character VI", () => {
expect(stylis(`background: url("img}.png");.a {background: url("img}.png");}`)).to.equal(
[`.user{background:url("img}.png");}`, `.user .a{background:url("img}.png");}`].join(""),
);
});
test("context character VII", () => {
expect(stylis(`background: url(img}.png);.a {background: url(img}.png);}`)).to.equal(
[`.user{background:url(img}.png);}`, `.user .a{background:url(img}.png);}`].join(""),
);
});
test("context character VIII", () => {
expect(stylis(`background: url[img}.png];.a {background: url[img}.png];}`)).to.equal(
[`.user{background:url[img}.png];}`, `.user .a{background:url[img}.png];}`].join(""),
);
});
test("context character IX", () => {
expect(stylis(`background: url(i&m&g.png);.a {background: url(i&m&g.png);}`)).to.equal(
[`.user{background:url(i&m&g.png);}`, `.user .a{background:url(i&m&g.png);}`].join(""),
);
});
test("`--` in an identifier (#220)", () => {
expect(
stylis(`
.block--modifier {
color: hotpink;
}
.card {
color: black;
}
`),
).to.equal([`.user .block--modifier{color:hotpink;}`, `.user .card{color:black;}`].join(""));
});
test("comments in rules not increasing depth of consecutive rules (#154)", () => {
expect(
stylis(`
font-size:2rem;
.one{color:black;/* foo */}
.two{color:black;/* bar */}
.three{color:black;/* baz */}
`),
).to.equal(
[
".user{font-size:2rem;}",
".user .one{color:black;}",
".user .two{color:black;}",
".user .three{color:black;}",
].join(""),
);
});
test("comment in a group of selectors inside a media query (#152)", () => {
expect(
stylis(`
@media (min-width: 400px) {
div /* comment */ {
border-left:1px solid hotpink;
}
span {
border-top:none;
}
}
`),
).to.equal(
`@media (min-width: 400px){.user div{border-left:1px solid hotpink;}.user span{border-top:none;}}`,
);
});
test("comment in a group of selectors inside a container query", () => {
expect(
stylis(`
@container (min-width: 400px) {
div /* comment */ {
border-left:1px solid hotpink;
}
span {
border-top:none;
}
}
`),
).to.equal(
`@container (min-width: 400px){.user div{border-left:1px solid hotpink;}.user span{border-top:none;}}`,
);
});
test("comment - bang at the start (#114)", () => {
expect(serialize(compile(`/*! test */body{color:red;}`), stringify)).to.equal(
"body{color:red;}",
);
});
test("parenthesis in string literal I (#151)", () => {
expect(
stylis(`
@media only screen and (max-width: 320px){
background: url("${"image_(1).jpg"}");
}
@media only screen and (min-width:321px) {
background: url("${"image_(1).jpg"}");
}
`),
).to.equal(
[
`@media only screen and (max-width: 320px){.user{background:url("${"image_(1).jpg"}");}}`,
`@media only screen and (min-width:321px){.user{background:url("${"image_(1).jpg"}");}}`,
].join(""),
);
});
test("parenthesis in string literal II (#123)", () => {
expect(
stylis(`
.a {
background: url("${"image_(1).jpg"})");
}
.b {
background: url("abc");
}
`),
).to.equal(
[`.user .a{background:url("image_(1).jpg)");}`, `.user .b{background:url("abc");}`].join(""),
);
});
test("parenthesis in string literal III (#128)", () => {
expect(
stylis(`
.icon {
background:url("data:image/svg+xml;charset=utf-8,%3Csvg width='12' height='12' viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M14.117.323L8.044 6.398 2.595.323a1.105 1.105 0 0 0-1.562 1.562L6.482 7.96.323 14.119a1.105 1.105 0 0 0 1.562 1.562L7.96 9.608l5.449 6.073a1.103 1.103 0 1 0 1.56-1.562L9.517 8.046l6.159-6.161a1.103 1.103 0 1 0-1.56-1.562z' fill='rgba(85, 85, 85, 0.5)'/%3E%3C/svg%3E");
}
div {
background: cyan;
}
`),
).to.equal(
[
`.user .icon{background:url("data:image/svg+xml;charset=utf-8,%3Csvg width='12' height='12' viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M14.117.323L8.044 6.398 2.595.323a1.105 1.105 0 0 0-1.562 1.562L6.482 7.96.323 14.119a1.105 1.105 0 0 0 1.562 1.562L7.96 9.608l5.449 6.073a1.103 1.103 0 1 0 1.56-1.562L9.517 8.046l6.159-6.161a1.103 1.103 0 1 0-1.56-1.562z' fill='rgba(85, 85, 85, 0.5)'/%3E%3C/svg%3E");}`,
`.user div{background:cyan;}`,
].join(""),
);
});
test("parenthesis in string literal IV (#116)", () => {
expect(
stylis(`
.a .b .c {
width: calc(100% / "func()");
}
.d {
background: yellow;
}
`),
).to.equal(
[`.user .a .b .c{width:calc(100% / "func()");}`, `.user .d{background:yellow;}`].join(""),
);
});
test("nested parenthesis", () => {
expect(stylis(`width: calc(calc(1) + 10);`)).to.equal(`.user{width:calc(calc(1) + 10);}`);
});
test("css variables edge cases (#144, #173)", () => {
expect(
stylis(`
--braces: { };
--at-keyword-unknown-block: @foobar {};
--at-keyword-known-block: @media {};
--cdo-at-top-level: <!--;
--cdc-at-top-level: -->;
--semicolon-not-top-level: (;);
--cdo-not-top-level: (<!--);
--cdc-not-top-level: (-->);
--ampersand-preserved: foo & bar;
`),
).to.equal(
`.user{` +
[
`--braces:{};`,
`--at-keyword-unknown-block:@foobar{};`,
`--at-keyword-known-block:@media{};`,
`--cdo-at-top-level:<!--;`,
`--cdc-at-top-level:-->;`,
`--semicolon-not-top-level:(;);`,
`--cdo-not-top-level:(<!--);`,
`--cdc-not-top-level:(-->);`,
`--ampersand-preserved:foo & bar;`,
].join("") +
"}",
);
expect(
serialize(
compile(`.foo { --bar: var(--baz) } .bar{--foo: {} } .foo {--min-height: 66px }`),
stringify,
),
).to.equal([`.foo{--bar:var(--baz);}`, `.bar{--foo:{};}`, `.foo{--min-height:66px;}`].join(""));
});
test("does not hang on unterminated block comment (#129)", () => {
expect(stylis(`/*`)).to.equal(``);
});
test("does not hang on unterminated function", () => {
expect(stylis(`(`)).to.equal(``);
});
test("handles single `/` in a value", () => {
expect(stylis(`font: 12px/14px serif;`)).to.equal(`.user{font:12px/14px serif;}`);
});
test("nested", () => {
expect(
stylis(`
div {
h2 {
color:red;
h3 {
color:blue;
}
}
}
.foo & {
width:1px;
&:hover {
color:black;
}
li {
color:white;
}
}
h1, div {
color:red;
h2,
&:before {
color:red;
}
color:blue;
header {
font-size:12px;
}
@media {
color:red;
}
@media {
color:blue;
}
@container (min-width: 250px) {
color:green;
}
}
&.foo {
&.bar {
color:orange
}
}
&.foo {
&.bar {
&.barbar {
color:orange
}
}
}
`),
).to.equal(
[
`.user div h2{color:red;}` +
`.user div h2 h3{color:blue;}` +
`.foo .user{width:1px;}` +
`.foo .user:hover{color:black;}` +
`.foo .user li{color:white;}` +
`.user h1,.user div{color:red;color:blue;}` +
`.user h1 h2,.user div h2,.user h1:before,.user div:before{color:red;}` +
`.user h1 header,.user div header{font-size:12px;}` +
`@media{.user h1,.user div{color:red;}}` +
`@media{.user h1,.user div{color:blue;}}` +
`@container (min-width: 250px){.user h1,.user div{color:green;}}` +
`.user.foo.bar{color:orange;}` +
`.user.foo.bar.barbar{color:orange;}`,
].join(""),
);
});
test("cascade @layer (#312)", () => {
expect(
stylis(`
@layer base {
border-left:1px solid hotpink;
}
`),
).to.equal(`@layer base{.user{border-left:1px solid hotpink;}}`);
expect(
stylis(`
@layer base {
@layer layout {
border-left:1px solid hotpink;
}
}
`),
).to.equal(`@layer base{@layer layout{.user{border-left:1px solid hotpink;}}}`);
expect(
stylis(`
@layer framework.layout {
border-left:1px solid hotpink;
}
`),
).to.equal(`@layer framework.layout{.user{border-left:1px solid hotpink;}}`);
expect(
stylis(`
@import "theme.css" layer(utilties);
`),
).to.equal(`@import "theme.css" layer(utilties);`);
expect(
stylis(`
@import "foo";
`),
).to.equal(`@import "foo";`);
expect(
stylis(`
@layer utilities;
`),
).to.equal(`@layer utilities;`);
expect(
stylis(`
@layer theme, layout, utilities;
`),
).to.equal(`@layer theme,layout,utilities;`);
expect(
stylis(`
@media (min-width: 30em) {
@layer layout {
.title { font-size: x-large; }
}
}
@layer theme {
@media (prefers-color-scheme: dark) {
.title { color: white; }
}
}
`),
).to.equal(
[
`@media (min-width: 30em){@layer layout{.user .title{font-size:x-large;}}}`,
`@layer theme{@media (prefers-color-scheme: dark){.user .title{color:white;}}}`,
].join(""),
);
expect(
stylis(`
@media (min-width: 30em) {
@layer layout {
.title { font-size: x-large; }
}
}
@layer theme {
@media (prefers-color-scheme: dark) {
.title { color: white; }
}
}
`),
).to.equal(
[
`@media (min-width: 30em){@layer layout{.user .title{font-size:x-large;}}}`,
`@layer theme{@media (prefers-color-scheme: dark){.user .title{color:white;}}}`,
].join(""),
);
expect(
stylis(`
@layer framework {
@keyframes slide-left {
from { margin-left: 0; }
to { margin-left: -100%; }
}
}
`),
).to.equal(
`@layer framework{@keyframes slide-left{from{margin-left:0;}to{margin-left:-100%;}}}`,
);
});
});