mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-11-09 14:03:33 +09:00
## Description Needed to support all wf presets for fixing this https://github.com/webstudio-is/webstudio/pull/4155 ## Steps for reproduction 1. click button 2. expect xyz ## Code Review - [ ] hi @kof, I need you to do - conceptual review (architecture, feature-correctness) - detailed review (read every line) - test it on preview ## Before requesting a review - [ ] made a self-review - [ ] added inline comments where things may be not obvious (the "why", not "what") ## Before merging - [ ] tested locally and on preview environment (preview dev login: 5de6) - [ ] updated [test cases](https://github.com/webstudio-is/webstudio/blob/main/apps/builder/docs/test-cases.md) document - [ ] added tests - [ ] if any new env variables are added, added them to `.env` file
738 lines
17 KiB
TypeScript
738 lines
17 KiB
TypeScript
import { describe, expect, test } from "@jest/globals";
|
|
import { parseCss, parseMediaQuery } from "./parse-css";
|
|
|
|
describe("Parse CSS", () => {
|
|
test("longhand property name with keyword value", () => {
|
|
expect(parseCss(`.test { background-color: red }`)).toEqual([
|
|
{
|
|
selector: ".test",
|
|
property: "backgroundColor",
|
|
value: { type: "keyword", value: "red" },
|
|
},
|
|
]);
|
|
});
|
|
|
|
test("one class selector rules", () => {
|
|
expect(parseCss(`.test { color: #ff0000 }`)).toEqual([
|
|
{
|
|
selector: ".test",
|
|
property: "color",
|
|
value: { alpha: 1, b: 0, g: 0, r: 255, type: "rgb" },
|
|
},
|
|
]);
|
|
});
|
|
|
|
test("parse supported shorthand values", () => {
|
|
const css = `
|
|
.test {
|
|
background: #ff0000 linear-gradient(180deg, #11181C 0%, rgba(17, 24, 28, 0) 36.09%), #EBFFFC;
|
|
}
|
|
`;
|
|
expect(parseCss(css)).toEqual([
|
|
{
|
|
selector: ".test",
|
|
property: "backgroundImage",
|
|
value: {
|
|
type: "layers",
|
|
value: [
|
|
{
|
|
type: "unparsed",
|
|
value:
|
|
"linear-gradient(180deg,#11181C 0%,rgba(17,24,28,0) 36.09%)",
|
|
},
|
|
{ type: "keyword", value: "none" },
|
|
],
|
|
},
|
|
},
|
|
{
|
|
selector: ".test",
|
|
property: "backgroundPositionX",
|
|
value: {
|
|
type: "layers",
|
|
value: [
|
|
{ type: "unit", unit: "%", value: 0 },
|
|
{ type: "unit", unit: "%", value: 0 },
|
|
],
|
|
},
|
|
},
|
|
{
|
|
selector: ".test",
|
|
property: "backgroundPositionY",
|
|
value: {
|
|
type: "layers",
|
|
value: [
|
|
{ type: "unit", unit: "%", value: 0 },
|
|
{ type: "unit", unit: "%", value: 0 },
|
|
],
|
|
},
|
|
},
|
|
{
|
|
selector: ".test",
|
|
property: "backgroundSize",
|
|
value: {
|
|
type: "layers",
|
|
value: [
|
|
{
|
|
type: "tuple",
|
|
value: [
|
|
{ type: "keyword", value: "auto" },
|
|
{ type: "keyword", value: "auto" },
|
|
],
|
|
},
|
|
{
|
|
type: "tuple",
|
|
value: [
|
|
{ type: "keyword", value: "auto" },
|
|
{ type: "keyword", value: "auto" },
|
|
],
|
|
},
|
|
],
|
|
},
|
|
},
|
|
{
|
|
selector: ".test",
|
|
property: "backgroundRepeat",
|
|
value: {
|
|
type: "layers",
|
|
value: [
|
|
{ type: "keyword", value: "repeat" },
|
|
{ type: "keyword", value: "repeat" },
|
|
],
|
|
},
|
|
},
|
|
{
|
|
selector: ".test",
|
|
property: "backgroundAttachment",
|
|
value: {
|
|
type: "layers",
|
|
value: [
|
|
{ type: "keyword", value: "scroll" },
|
|
{ type: "keyword", value: "scroll" },
|
|
],
|
|
},
|
|
},
|
|
{
|
|
selector: ".test",
|
|
property: "backgroundOrigin",
|
|
value: {
|
|
type: "layers",
|
|
value: [
|
|
{ type: "keyword", value: "padding-box" },
|
|
{ type: "keyword", value: "padding-box" },
|
|
],
|
|
},
|
|
},
|
|
{
|
|
selector: ".test",
|
|
property: "backgroundClip",
|
|
value: {
|
|
type: "layers",
|
|
value: [
|
|
{ type: "keyword", value: "border-box" },
|
|
{ type: "keyword", value: "border-box" },
|
|
],
|
|
},
|
|
},
|
|
{
|
|
selector: ".test",
|
|
property: "backgroundColor",
|
|
value: { alpha: 1, b: 252, g: 255, r: 235, type: "rgb" },
|
|
},
|
|
]);
|
|
});
|
|
|
|
test("parses single layer", () => {
|
|
const css = `
|
|
.test {
|
|
background-image: none; background-position: 0px 0px; background-size: auto;
|
|
}
|
|
`;
|
|
expect(parseCss(css)).toEqual([
|
|
{
|
|
selector: ".test",
|
|
property: "backgroundImage",
|
|
value: {
|
|
type: "layers",
|
|
value: [{ type: "keyword", value: "none" }],
|
|
},
|
|
},
|
|
{
|
|
selector: ".test",
|
|
property: "backgroundPositionX",
|
|
value: {
|
|
type: "layers",
|
|
value: [{ type: "unit", unit: "px", value: 0 }],
|
|
},
|
|
},
|
|
{
|
|
selector: ".test",
|
|
property: "backgroundPositionY",
|
|
value: {
|
|
type: "layers",
|
|
value: [{ type: "unit", unit: "px", value: 0 }],
|
|
},
|
|
},
|
|
{
|
|
selector: ".test",
|
|
property: "backgroundSize",
|
|
value: {
|
|
type: "layers",
|
|
value: [{ type: "keyword", value: "auto" }],
|
|
},
|
|
},
|
|
]);
|
|
});
|
|
|
|
test("parse state", () => {
|
|
expect(parseCss(`a:hover { color: #ff0000 }`)).toEqual([
|
|
{
|
|
selector: "a",
|
|
state: ":hover",
|
|
property: "color",
|
|
value: { alpha: 1, b: 0, g: 0, r: 255, type: "rgb" },
|
|
},
|
|
]);
|
|
});
|
|
|
|
test("attribute selector", () => {
|
|
expect(parseCss(`[class^="a"] { color: #ff0000 }`)).toEqual([
|
|
{
|
|
selector: '[class^="a"]',
|
|
property: "color",
|
|
value: { alpha: 1, b: 0, g: 0, r: 255, type: "rgb" },
|
|
},
|
|
]);
|
|
});
|
|
|
|
test("parse first pseudo class as selector", () => {
|
|
// E.g. :root
|
|
expect(parseCss(`:first-pseudo:my-state { color: #ff0000 }`)).toEqual([
|
|
{
|
|
selector: ":first-pseudo",
|
|
state: ":my-state",
|
|
property: "color",
|
|
value: { alpha: 1, b: 0, g: 0, r: 255, type: "rgb" },
|
|
},
|
|
]);
|
|
});
|
|
|
|
test("parse pseudo element", () => {
|
|
expect(parseCss(`input::placeholder { color: #ff0000 }`)).toEqual([
|
|
{
|
|
selector: "input",
|
|
state: "::placeholder",
|
|
property: "color",
|
|
value: { alpha: 1, b: 0, g: 0, r: 255, type: "rgb" },
|
|
},
|
|
]);
|
|
});
|
|
|
|
test("parse multiple selectors, one with state", () => {
|
|
expect(parseCss(`a, a:hover { color: #ff0000 }`)).toEqual([
|
|
{
|
|
selector: "a",
|
|
property: "color",
|
|
value: { alpha: 1, b: 0, g: 0, r: 255, type: "rgb" },
|
|
},
|
|
{
|
|
selector: "a",
|
|
state: ":hover",
|
|
property: "color",
|
|
value: { alpha: 1, b: 0, g: 0, r: 255, type: "rgb" },
|
|
},
|
|
]);
|
|
});
|
|
|
|
test("parse multiple selectors, both with state", () => {
|
|
expect(parseCss(`a:active, a:hover { color: #ff0000 }`)).toEqual([
|
|
{
|
|
selector: "a",
|
|
state: ":active",
|
|
property: "color",
|
|
value: { alpha: 1, b: 0, g: 0, r: 255, type: "rgb" },
|
|
},
|
|
{
|
|
selector: "a",
|
|
state: ":hover",
|
|
property: "color",
|
|
value: { alpha: 1, b: 0, g: 0, r: 255, type: "rgb" },
|
|
},
|
|
]);
|
|
});
|
|
|
|
test("parse multiple rules", () => {
|
|
expect(parseCss(`a { color: red} a:hover { color: #ff0000 }`)).toEqual([
|
|
{
|
|
selector: "a",
|
|
property: "color",
|
|
value: {
|
|
type: "keyword",
|
|
value: "red",
|
|
},
|
|
},
|
|
{
|
|
selector: "a",
|
|
state: ":hover",
|
|
property: "color",
|
|
value: {
|
|
alpha: 1,
|
|
b: 0,
|
|
g: 0,
|
|
r: 255,
|
|
type: "rgb",
|
|
},
|
|
},
|
|
]);
|
|
});
|
|
|
|
test("parse multiple rules, remove overwritten properties", () => {
|
|
const css = `
|
|
h1 {
|
|
margin-bottom: 5px;
|
|
font-size: 2em;
|
|
}
|
|
h1 {
|
|
margin-bottom: 10px;
|
|
font-weight: bold;
|
|
}
|
|
|
|
h1 {
|
|
margin-top: 20px;
|
|
font-size: 38px;
|
|
line-height: 44px;
|
|
}
|
|
`;
|
|
expect(parseCss(css)).toEqual([
|
|
{
|
|
selector: "h1",
|
|
property: "marginBottom",
|
|
value: { type: "unit", unit: "px", value: 10 },
|
|
},
|
|
{
|
|
selector: "h1",
|
|
property: "fontSize",
|
|
value: { type: "unit", unit: "px", value: 38 },
|
|
},
|
|
{
|
|
selector: "h1",
|
|
property: "fontWeight",
|
|
value: { type: "keyword", value: "bold" },
|
|
},
|
|
{
|
|
selector: "h1",
|
|
property: "marginTop",
|
|
value: { type: "unit", unit: "px", value: 20 },
|
|
},
|
|
{
|
|
selector: "h1",
|
|
property: "lineHeight",
|
|
value: { type: "unit", unit: "px", value: 44 },
|
|
},
|
|
]);
|
|
});
|
|
|
|
test("parse shorthand", () => {
|
|
expect(parseCss(`a { border: 1px solid red }`)).toEqual([
|
|
{
|
|
selector: "a",
|
|
property: "borderTopWidth",
|
|
value: { type: "unit", unit: "px", value: 1 },
|
|
},
|
|
{
|
|
selector: "a",
|
|
property: "borderRightWidth",
|
|
value: { type: "unit", unit: "px", value: 1 },
|
|
},
|
|
{
|
|
selector: "a",
|
|
property: "borderBottomWidth",
|
|
value: { type: "unit", unit: "px", value: 1 },
|
|
},
|
|
{
|
|
selector: "a",
|
|
property: "borderLeftWidth",
|
|
value: { type: "unit", unit: "px", value: 1 },
|
|
},
|
|
{
|
|
selector: "a",
|
|
property: "borderTopStyle",
|
|
value: { type: "keyword", value: "solid" },
|
|
},
|
|
{
|
|
selector: "a",
|
|
property: "borderRightStyle",
|
|
value: { type: "keyword", value: "solid" },
|
|
},
|
|
{
|
|
selector: "a",
|
|
property: "borderBottomStyle",
|
|
value: { type: "keyword", value: "solid" },
|
|
},
|
|
{
|
|
selector: "a",
|
|
property: "borderLeftStyle",
|
|
value: { type: "keyword", value: "solid" },
|
|
},
|
|
{
|
|
selector: "a",
|
|
property: "borderTopColor",
|
|
value: { type: "keyword", value: "red" },
|
|
},
|
|
{
|
|
selector: "a",
|
|
property: "borderRightColor",
|
|
value: { type: "keyword", value: "red" },
|
|
},
|
|
{
|
|
selector: "a",
|
|
property: "borderBottomColor",
|
|
value: { type: "keyword", value: "red" },
|
|
},
|
|
{
|
|
selector: "a",
|
|
property: "borderLeftColor",
|
|
value: { type: "keyword", value: "red" },
|
|
},
|
|
]);
|
|
});
|
|
|
|
test("parse custom property", () => {
|
|
expect(parseCss(`a { --my-property: red; }`)).toEqual([
|
|
{
|
|
selector: "a",
|
|
property: "--my-property",
|
|
value: { type: "unparsed", value: "red" },
|
|
},
|
|
]);
|
|
});
|
|
|
|
// @todo https://github.com/webstudio-is/webstudio/issues/3399
|
|
test("parse variable as unset default", () => {
|
|
expect(parseCss(`a { color: var(--color) }`)).toEqual([
|
|
{
|
|
selector: "a",
|
|
property: "color",
|
|
value: { type: "keyword", value: "unset" },
|
|
},
|
|
]);
|
|
});
|
|
|
|
test("optionally parse variable as var value", () => {
|
|
expect(
|
|
parseCss(
|
|
`
|
|
a {
|
|
color: var(--color);
|
|
background-color: var(--color, red);
|
|
}
|
|
`,
|
|
{ customProperties: true }
|
|
)
|
|
).toEqual([
|
|
{
|
|
selector: "a",
|
|
property: "color",
|
|
value: { type: "var", value: "color" },
|
|
},
|
|
{
|
|
selector: "a",
|
|
property: "backgroundColor",
|
|
value: {
|
|
type: "var",
|
|
value: "color",
|
|
fallback: { type: "unparsed", value: "red" },
|
|
},
|
|
},
|
|
]);
|
|
});
|
|
|
|
test("parse empty value as unset", () => {
|
|
expect(parseCss(`a { color: ; background-color: red }`)).toEqual([
|
|
{
|
|
selector: "a",
|
|
property: "color",
|
|
value: { type: "keyword", value: "unset" },
|
|
},
|
|
{
|
|
selector: "a",
|
|
property: "backgroundColor",
|
|
value: { type: "keyword", value: "red" },
|
|
},
|
|
]);
|
|
});
|
|
|
|
test("unprefix property that doesn't need a prefix", () => {
|
|
expect(parseCss(`a { -webkit-color: red; }`)).toEqual([
|
|
{
|
|
selector: "a",
|
|
property: "color",
|
|
value: { type: "keyword", value: "red" },
|
|
},
|
|
]);
|
|
});
|
|
|
|
test("keep prefix for property that needs one", () => {
|
|
expect(parseCss(`a { -webkit-box-orient: horizontal; }`)).toEqual([
|
|
{
|
|
selector: "a",
|
|
property: "-webkit-box-orient",
|
|
value: { type: "keyword", value: "horizontal" },
|
|
},
|
|
]);
|
|
});
|
|
|
|
test("parse child combinator", () => {
|
|
expect(parseCss(`a > b { color: #ff0000 }`)).toEqual([
|
|
{
|
|
selector: "a > b",
|
|
property: "color",
|
|
value: { alpha: 1, b: 0, g: 0, r: 255, type: "rgb" },
|
|
},
|
|
]);
|
|
});
|
|
|
|
test("parse space combinator", () => {
|
|
expect(parseCss(`.a b { color: #ff0000 }`)).toEqual([
|
|
{
|
|
selector: ".a b",
|
|
property: "color",
|
|
value: { alpha: 1, b: 0, g: 0, r: 255, type: "rgb" },
|
|
},
|
|
]);
|
|
});
|
|
|
|
test("parse nested selectors as one token", () => {
|
|
expect(parseCss(`a b c.d { color: #ff0000 }`)).toEqual([
|
|
{
|
|
selector: "a b c.d",
|
|
property: "color",
|
|
value: { alpha: 1, b: 0, g: 0, r: 255, type: "rgb" },
|
|
},
|
|
]);
|
|
});
|
|
});
|
|
|
|
test("parse font-smooth properties", () => {
|
|
expect(
|
|
parseCss(`
|
|
a {
|
|
font-smoothing: auto;
|
|
}
|
|
b {
|
|
-webkit-font-smoothing: auto;
|
|
}
|
|
c {
|
|
-moz-osx-font-smoothing: auto;
|
|
}
|
|
`)
|
|
).toEqual([
|
|
{
|
|
selector: "a",
|
|
property: "WebkitFontSmoothing",
|
|
value: { type: "keyword", value: "auto" },
|
|
},
|
|
{
|
|
selector: "b",
|
|
property: "WebkitFontSmoothing",
|
|
value: { type: "keyword", value: "auto" },
|
|
},
|
|
{
|
|
selector: "c",
|
|
property: "MozOsxFontSmoothing",
|
|
value: { type: "keyword", value: "auto" },
|
|
},
|
|
]);
|
|
});
|
|
|
|
test("parse incorrectly unprefixed tap-highlight-color", () => {
|
|
expect(
|
|
parseCss(`
|
|
a {
|
|
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
|
}
|
|
b {
|
|
tap-highlight-color: transparent;
|
|
}
|
|
`)
|
|
).toEqual([
|
|
{
|
|
selector: "a",
|
|
property: "-webkit-tap-highlight-color",
|
|
value: { alpha: 0, b: 0, g: 0, r: 0, type: "rgb" },
|
|
},
|
|
{
|
|
selector: "b",
|
|
property: "-webkit-tap-highlight-color",
|
|
value: { type: "keyword", value: "transparent" },
|
|
},
|
|
]);
|
|
});
|
|
|
|
test("parse top level rules and media all as base query", () => {
|
|
expect(
|
|
parseCss(`
|
|
a {
|
|
color: red;
|
|
}
|
|
@media all {
|
|
b {
|
|
width: auto;
|
|
}
|
|
}
|
|
`)
|
|
).toEqual([
|
|
{
|
|
selector: "a",
|
|
property: "color",
|
|
value: { type: "keyword", value: "red" },
|
|
},
|
|
{
|
|
selector: "b",
|
|
property: "width",
|
|
value: { type: "keyword", value: "auto" },
|
|
},
|
|
]);
|
|
});
|
|
|
|
test("parse media queries", () => {
|
|
expect(
|
|
parseCss(`
|
|
@media ( max-width: 768px ) {
|
|
a {
|
|
color: red;
|
|
}
|
|
}
|
|
@media (min-width: 768px) {
|
|
a {
|
|
color: green;
|
|
}
|
|
}
|
|
`)
|
|
).toEqual([
|
|
{
|
|
breakpoint: `(max-width:768px)`,
|
|
selector: "a",
|
|
property: "color",
|
|
value: { type: "keyword", value: "red" },
|
|
},
|
|
{
|
|
breakpoint: `(min-width:768px)`,
|
|
selector: "a",
|
|
property: "color",
|
|
value: { type: "keyword", value: "green" },
|
|
},
|
|
]);
|
|
});
|
|
|
|
test("support only screen media type", () => {
|
|
expect(
|
|
parseCss(`
|
|
@media all {
|
|
a {
|
|
color: yellow;
|
|
}
|
|
}
|
|
@media all and ( min-width: 768px ) {
|
|
a {
|
|
color: red;
|
|
}
|
|
}
|
|
@media screen and (min-width: 1024px) {
|
|
a {
|
|
color: green;
|
|
}
|
|
}
|
|
@media print and (min-width: 1280px) {
|
|
a {
|
|
color: blue;
|
|
}
|
|
}
|
|
`)
|
|
).toEqual([
|
|
{
|
|
selector: "a",
|
|
property: "color",
|
|
value: { type: "keyword", value: "yellow" },
|
|
},
|
|
{
|
|
breakpoint: `(min-width:768px)`,
|
|
selector: "a",
|
|
property: "color",
|
|
value: { type: "keyword", value: "red" },
|
|
},
|
|
{
|
|
breakpoint: `(min-width:1024px)`,
|
|
selector: "a",
|
|
property: "color",
|
|
value: { type: "keyword", value: "green" },
|
|
},
|
|
]);
|
|
});
|
|
|
|
test("ignore unsupported media queries", () => {
|
|
expect(
|
|
parseCss(`
|
|
a {
|
|
color: red;
|
|
}
|
|
@media (min-width: 768px) and (max-width: 1024px) {
|
|
b {
|
|
color: green;
|
|
}
|
|
}
|
|
@media (min-width: 768px) and (max-width: 1024px) {
|
|
c {
|
|
color: blue;
|
|
}
|
|
}
|
|
@media (hover: hover) {
|
|
d {
|
|
color: yellow;
|
|
}
|
|
}
|
|
@media (min-width: 40rem) {
|
|
e {
|
|
color: orange;
|
|
}
|
|
}
|
|
`)
|
|
).toEqual([
|
|
{
|
|
selector: "a",
|
|
property: "color",
|
|
value: { type: "keyword", value: "red" },
|
|
},
|
|
]);
|
|
});
|
|
|
|
test("ignore unsupported at rules", () => {
|
|
expect(
|
|
parseCss(`
|
|
a {
|
|
color: red;
|
|
}
|
|
@supports (display: grid) {
|
|
b {
|
|
color: green;
|
|
}
|
|
}
|
|
`)
|
|
).toEqual([
|
|
{
|
|
selector: "a",
|
|
property: "color",
|
|
value: { type: "keyword", value: "red" },
|
|
},
|
|
]);
|
|
});
|
|
|
|
test("parse media query", () => {
|
|
expect(parseMediaQuery(`(min-width: 768px)`)).toEqual({
|
|
minWidth: 768,
|
|
});
|
|
expect(parseMediaQuery(`(max-width: 768px)`)).toEqual({
|
|
maxWidth: 768,
|
|
});
|
|
expect(parseMediaQuery(`(hover: hover)`)).toEqual(undefined);
|
|
});
|