initial commit

This commit is contained in:
i2p
2026-08-27 21:09:14 +00:00
commit a5b6d59437
12681 changed files with 3253832 additions and 0 deletions
+215
View File
@@ -0,0 +1,215 @@
import { itBundled } from "../expectBundled";
describe("css", () => {
itBundled("css-module/GlobalPseudoFunction", {
files: {
"index.module.css": /* css */ `
:global(.foo) {
color: red;
}
`,
},
outdir: "/out",
entryPoints: ["/index.module.css"],
onAfterBundle(api) {
api.expectFile("/out/index.module.css").toEqualIgnoringWhitespace(`
/* index.module.css */
.foo {
color: red;
}
`);
},
});
itBundled("css-module/BundleTwoFilesWithoutCodeSplitting", {
files: {
"/foo-entry.js": `
import styles from './common.module.css'
console.log(styles)
`,
"/bar-entry.js": `
import styles from './common.module.css'
console.log(styles)
`,
"/common.module.css": `.baz { color: red }`,
},
entryPoints: ["/foo-entry.js", "/bar-entry.js"],
outdir: "/out",
onAfterBundle(api) {
api.expectFile("/out/foo-entry.js").toMatchInlineSnapshot(`
"// common.module.css
var common_module_default = {
baz: "baz_I7o34g"
};
// foo-entry.js
console.log(common_module_default);
"
`);
api.expectFile("/out/bar-entry.js").toMatchInlineSnapshot(`
"// common.module.css
var common_module_default = {
baz: "baz_I7o34g"
};
// bar-entry.js
console.log(common_module_default);
"
`);
},
});
itBundled("css-module/BundleTwoFilesWithCodeSplitting", {
files: {
"/foo-entry.js": `
import styles from './common.module.css'
console.log(styles)
`,
"/bar-entry.js": `
import styles from './common.module.css'
console.log(styles)
`,
"/common.module.css": `.baz { color: red }`,
},
entryPoints: ["/foo-entry.js", "/bar-entry.js"],
splitting: true,
outdir: "/out",
onAfterBundle(api) {
api.expectFile("/out/foo-entry.js").toMatchInlineSnapshot(`
"// common.module.css
var common_module_default = {
baz: "baz_I7o34g"
};
// foo-entry.js
console.log(common_module_default);
"
`);
api.expectFile("/out/bar-entry.js").toMatchInlineSnapshot(`
"// common.module.css
var common_module_default = {
baz: "baz_I7o34g"
};
// bar-entry.js
console.log(common_module_default);
"
`);
},
});
// https://github.com/oven-sh/bun/issues/18921
// The `animation` shorthand and `animation-name` longhand must scope their
// referenced `@keyframes` name to the SAME hashed name the keyframes rule
// receives, otherwise the animation is broken.
itBundled("css-module/AnimationNameScopedToKeyframes", {
files: {
"/entry.js": `
import styles from './styles.module.css';
console.log(styles.playAnim, styles.spin);
`,
"/styles.module.css": `
.playAnim { animation: anim forwards ease-out 0.25s; }
.spin { animation-name: rotate; }
.quoted { animation-name: "anim"; }
@keyframes anim { from { opacity: 0 } to { opacity: 1 } }
@keyframes rotate { to { transform: rotate(360deg) } }
`,
},
entryPoints: ["/entry.js"],
outdir: "/out",
onAfterBundle(api) {
const css = api.readFile("/out/entry.css");
// Each @keyframes name is scoped (e.g. `anim_<hash>`), not left bare.
const animKeyframes = css.match(/@keyframes\s+(anim_[A-Za-z0-9_-]+)\s*\{/);
const rotateKeyframes = css.match(/@keyframes\s+(rotate_[A-Za-z0-9_-]+)\s*\{/);
expect(animKeyframes, "@keyframes anim should be scoped").not.toBeNull();
expect(rotateKeyframes, "@keyframes rotate should be scoped").not.toBeNull();
// The `animation` shorthand references the SAME scoped keyframes name.
const animShorthand = css.match(/animation:\s*([^;]+);/);
expect(animShorthand, "animation shorthand should be present").not.toBeNull();
expect(animShorthand![1]).toContain(animKeyframes![1]);
// The `animation-name` longhand references the SAME scoped keyframes name.
expect(css).toContain(`animation-name: ${rotateKeyframes![1]}`);
// The quoted-string form scopes to the same hash as the ident form.
expect(css).toContain(`animation-name: ${animKeyframes![1]}`);
// The bare (unscoped) names must not survive as animation references.
expect(css).not.toMatch(/animation:[^;]*\banim\b/);
expect(css).not.toMatch(/animation-name:\s*rotate\b/);
},
});
// The parser dedupes repeated class/id names through a borrowed lookup
// (`add_symbol_for_name`); many references to the same names must all map
// to a single hashed symbol each.
itBundled("css-module/RepeatedClassAndIdReferences", {
files: {
"/entry.js": `
import styles from './styles.module.css';
console.log(JSON.stringify(styles));
`,
"/styles.module.css":
Array.from({ length: 64 }, (_, i) => `.btn { z-index: ${i} }`).join("\n") +
"\n#hero { color: red }\n" +
Array.from({ length: 32 }, () => `#hero .btn { color: blue }`).join("\n"),
},
entryPoints: ["/entry.js"],
outdir: "/out",
onAfterBundle(api) {
const js = api.readFile("/out/entry.js");
const css = api.readFile("/out/entry.css");
const btn = js.match(/btn:\s*"(btn_[A-Za-z0-9_-]+)"/);
const hero = js.match(/hero:\s*"(hero_[A-Za-z0-9_-]+)"/);
expect(btn).not.toBeNull();
expect(hero).not.toBeNull();
// Every `.btn` / `#hero` occurrence shares the same hashed name.
const btnHashes = new Set([...css.matchAll(/\.btn_[A-Za-z0-9_-]+/g)].map(m => m[0]));
const heroHashes = new Set([...css.matchAll(/#hero_[A-Za-z0-9_-]+/g)].map(m => m[0]));
expect([...btnHashes]).toEqual([`.${btn![1]}`]);
expect([...heroHashes]).toEqual([`#${hero![1]}`]);
expect(css).not.toMatch(/\.btn\b[^_]/);
expect(css).not.toMatch(/#hero\b[^_]/);
},
});
itBundled("css-module/ExportsMapMultipleClassesAndComposes", {
files: {
"/entry.js": `
import styles from './styles.module.css';
console.log(styles.alpha, styles.betaGamma);
`,
"/styles.module.css": `
.alpha { color: red; }
.betaGamma { composes: alpha; color: blue; }
`,
},
entryPoints: ["/entry.js"],
outdir: "/out",
onAfterBundle(api) {
const js = api.readFile("/out/entry.js");
const alpha = js.match(/alpha:\s*"(alpha_[A-Za-z0-9_-]+)"/);
expect(alpha).not.toBeNull();
// `composes: alpha` => betaGamma's export contains both hashed names.
const beta = js.match(/betaGamma:\s*"([^"]+)"/);
expect(beta).not.toBeNull();
expect(beta![1]).toContain("betaGamma_");
expect(beta![1]).toContain(alpha![1]);
// Printed CSS must use the same hashed names as the exports map.
const css = api.readFile("/out/entry.css");
expect(css).toContain(`.${alpha![1]}`);
const betaOwn = beta![1].split(" ").find(name => name.startsWith("betaGamma_"))!;
expect(css).toContain(`.${betaOwn}`);
},
});
});
@@ -0,0 +1,32 @@
import { itBundled } from "../expectBundled";
describe("css", () => {
itBundled("css/is-selector", {
files: {
"index.css": /* css */ `
.foo:is(input:checked) {
color: red;
}
`,
},
outdir: "/out",
entryPoints: ["/index.css"],
onAfterBundle(api) {
api.expectFile("/out/index.css").toMatchInlineSnapshot(`
"/* index.css */
.foo:-webkit-any(input:checked) {
color: red;
}
.foo:-moz-any(input:checked) {
color: red;
}
.foo:is(input:checked) {
color: red;
}
"
`);
},
});
});
@@ -0,0 +1,44 @@
import { describe, expect } from "bun:test";
import { itBundled } from "../expectBundled";
describe("css", () => {
itBundled("css/mask-geometry-box-preserved", {
files: {
"index.css": /* css */ `
.test-a::after {
mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0);
}
.test-b::after {
mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
}
`,
},
outdir: "/out",
entryPoints: ["/index.css"],
onAfterBundle(api) {
const output = api.readFile("/out/index.css");
expect(output).toContain("padding-box");
expect(output).toContain("content-box");
expect(output).toContain(".test-a");
expect(output).toContain(".test-b");
expect(output).not.toContain(".test-a:after, .test-b:after");
},
});
itBundled("css/webkit-mask-geometry-box-preserved", {
files: {
"index.css": /* css */ `
.test-c::after {
-webkit-mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0);
-webkit-mask-composite: xor;
}
`,
},
outdir: "/out",
entryPoints: ["/index.css"],
onAfterBundle(api) {
const output = api.readFile("/out/index.css");
expect(output).toContain("padding-box");
},
});
});
@@ -0,0 +1,73 @@
import { itBundled } from "../expectBundled";
describe("css", () => {
itBundled("css/view-transition-class-selector-23600", {
files: {
"index.css": /* css */ `
@keyframes slide-out {
from {
opacity: 1;
transform: translateX(0);
}
to {
opacity: 0;
transform: translateX(-100%);
}
}
::view-transition-old(.slide-out) {
animation-name: slide-out;
animation-timing-function: ease-in-out;
}
::view-transition-new(.fade-in) {
animation-name: fade-in;
}
::view-transition-group(.card) {
animation-duration: 1s;
}
::view-transition-image-pair(.hero) {
isolation: isolate;
}
`,
},
outdir: "/out",
entryPoints: ["/index.css"],
onAfterBundle(api) {
api.expectFile("/out/index.css").toMatchInlineSnapshot(`
"/* index.css */
@keyframes slide-out {
from {
opacity: 1;
transform: translateX(0);
}
to {
opacity: 0;
transform: translateX(-100%);
}
}
::view-transition-old(.slide-out) {
animation-name: slide-out;
animation-timing-function: ease-in-out;
}
::view-transition-new(.fade-in) {
animation-name: fade-in;
}
::view-transition-group(.card) {
animation-duration: 1s;
}
::view-transition-image-pair(.hero) {
isolation: isolate;
}
"
`);
},
});
});
@@ -0,0 +1,64 @@
import { describe } from "bun:test";
import { itBundled } from "../../expectBundled";
const runTest = (property: string, input: string, expected: string) => {
const testTitle = `${property}: ${input}`;
itBundled(testTitle, {
files: {
"/a.css": /* css */ `
h1 {
${property}: ${input}
}
`,
},
outfile: "out.css",
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
/* a.css */
h1 {
${property}: ${expected};
}
`);
},
});
};
describe("background-computed", () => {
runTest("background-attachment", "local", "local");
runTest("background-attachment", "scroll, fixed", "scroll, fixed");
runTest("background-attachment", "local, fixed, scroll", "local, fixed, scroll");
runTest("background-attachment", "local, fixed, scroll, fixed", "local, fixed, scroll, fixed");
runTest("background-clip", "border-box", "border-box");
runTest("background-clip", "content-box, border-box", "content-box, border-box");
runTest("background-clip", "border-box, padding-box, content-box", "border-box, padding-box, content-box");
runTest(
"background-clip",
"content-box, border-box, padding-box, content-box",
"content-box, border-box, padding-box, content-box",
);
runTest("background-clip", "content-box, border-box, padding-box", "content-box, border-box, padding-box");
runTest("background-clip", "content-box, border-box, border-area", "content-box, border-box, border-area");
runTest("background-color", "rgb(255, 0, 0)", "red");
runTest("background-origin", "border-box", "border-box");
runTest("background-origin", "content-box, border-box", "content-box, border-box");
runTest("background-origin", "border-box, padding-box, content-box", "border-box, padding-box, content-box");
runTest(
"background-origin",
"content-box, border-box, padding-box, content-box",
"content-box, border-box, padding-box, content-box",
);
runTest("background-position", "50% 6px", "50% 6px");
runTest("background-position", "12px 13px, 50% 6px", "12px 13px, 50% 6px");
runTest("background-position", "12px 13px, 50% 6px, 30px -10px", "12px 13px, 50% 6px, 30px -10px");
runTest(
"background-position",
"12px 13px, 50% 6px, 30px -10px, -7px 8px",
"12px 13px, 50% 6px, 30px -10px, -7px 8px",
);
runTest("background-position-x", "0.5em", ".5em");
runTest("background-position-x", "-20%, 10px", "-20%, 10px");
runTest("background-position-x", "center, left, right", "center, left, right");
runTest("background-position-x", "calc(10px - 0.5em), -20%, right, 15%", "calc(10px - .5em), -20%, right, 15%");
runTest("background-position-y", "0.5em", ".5em");
});
@@ -0,0 +1,203 @@
import { describe } from "bun:test";
import { itBundled } from "../../expectBundled";
const runTest = (testTitle: string, input: string, expected: string) => {
testTitle = testTitle.length === 0 ? input : testTitle;
itBundled(testTitle, {
files: {
"/a.css": /* css */ `
h1 {
color: ${input}
}
`,
},
outfile: "out.css",
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
/* a.css */
h1 {
color: ${expected};
}
`);
},
});
};
describe("color-computed-rgb", () => {
runTest("", "rgb(none none none)", "#000");
runTest("", "rgb(none none none / none)", "#0000");
runTest("", "rgb(128 none none)", "maroon");
runTest("", "rgb(128 none none / none)", "#80000000");
runTest("", "rgb(none none none / .5)", "#00000080");
runTest("", "rgb(20% none none)", "#300");
runTest("", "rgb(20% none none / none)", "#3000");
runTest("", "rgb(none none none / 50%)", "#00000080");
runTest("", "rgba(none none none)", "#000");
runTest("", "rgba(none none none / none)", "#0000");
runTest("", "rgba(128 none none)", "maroon");
runTest("", "rgba(128 none none / none)", "#80000000");
runTest("", "rgba(none none none / .5)", "#00000080");
runTest("", "rgba(20% none none)", "#300");
runTest("", "rgba(20% none none / none)", "#3000");
runTest("", "rgba(none none none / 50%)", "#00000080");
runTest("Tests that RGB channels are rounded appropriately", "rgb(2.5, 3.4, 4.6)", "#030305");
runTest("Valid numbers should be parsed", "rgb(00, 51, 102)", "#036");
runTest("Correct escape sequences should still parse", "r\\gb(00, 51, 102)", "#036");
runTest("Correct escape sequences should still parse", "r\\67 b(00, 51, 102)", "#036");
runTest("Capitalization should not affect parsing", "RGB(153, 204, 255)", "#9cf");
runTest("Capitalization should not affect parsing", "rgB(0, 0, 0)", "#000");
runTest("Capitalization should not affect parsing", "rgB(0, 51, 255)", "#03f");
runTest("Lack of whitespace should not affect parsing", "rgb(0,51,255)", "#03f");
runTest("Whitespace should not affect parsing", "rgb(0 , 51 ,255)", "#03f");
runTest("Comments should be allowed within function", "rgb(/* R */0, /* G */51, /* B */255)", "#03f");
runTest("Invalid values should be clamped to 0 and 255 respectively", "rgb(-51, 306, 0)", "#0f0");
runTest("Valid percentages should be parsed", "rgb(42%, 3%, 50%)", "#6b0880");
runTest("Capitalization should not affect parsing", "RGB(100%, 100%, 100%)", "#fff");
runTest("Capitalization should not affect parsing", "rgB(0%, 0%, 0%)", "#000");
runTest("Capitalization should not affect parsing", "rgB(10%, 20%, 30%)", "#1a334d");
runTest("Whitespace should not affect parsing", "rgb(10%,20%,30%)", "#1a334d");
runTest("Whitespace should not affect parsing", "rgb(10% , 20% ,30%)", "#1a334d");
runTest("Comments should not affect parsing", "rgb(/* R */ 10%, /* G */ 20%, /* B */ 30%)", "#1a334d");
runTest("Invalid values should be clamped to 0 and 255 respectively", "rgb(-12%, 110%, 1400%)", "#0ff");
runTest("RGB and RGBA are synonyms", "rgb(0, 0, 0, 0)", "#0000");
runTest("RGB and RGBA are synonyms", "rgb(0%, 0%, 0%, 0%)", "#0000");
runTest("RGB and RGBA are synonyms", "rgb(0%, 0%, 0%, 0)", "#0000");
runTest("Valid numbers should be parsed", "rgba(0, 0, 0, 0)", "#0000");
runTest("Valid numbers should be parsed", "rgba(204, 0, 102, 0.3)", "#cc00664d");
runTest("Capitalization should not affect parsing", "RGBA(255, 255, 255, 0)", "#fff0");
runTest("Capitalization should not affect parsing", "rgBA(0, 51, 255, 1)", "#03f");
runTest("Invalid alpha values should be clamped to 0 and 1 respectively", "rgba(0, 51, 255, 1.1)", "#03f");
runTest("Invalid alpha values should be clamped to 0 and 1 respectively", "rgba(0, 51, 255, 37)", "#03f");
runTest("Valid numbers should be parsed", "rgba(0, 51, 255, 0.42)", "#0033ff6b");
runTest("Valid numbers should be parsed", "rgba(0, 51, 255, 0)", "#03f0");
runTest("Invalid alpha values should be clamped to 0 and 1 respectively", "rgba(0, 51, 255, -0.1)", "#03f0");
runTest("Invalid alpha values should be clamped to 0 and 1 respectively", "rgba(0, 51, 255, -139)", "#03f0");
runTest("Capitalization should not affect parsing", "RGBA(100%, 100%, 100%, 0)", "#fff0");
runTest("Valid percentages should be parsed", "rgba(42%, 3%, 50%, 0.3)", "#6b08804d");
runTest("Capitalization should not affect parsing", "rgBA(0%, 20%, 100%, 1)", "#03f");
runTest("Invalid alpha values should be clamped to 0 and 1 respectively", "rgba(0%, 20%, 100%, 1.1)", "#03f");
runTest("Invalid alpha values should be clamped to 0 and 1 respectively", "rgba(0%, 20%, 100%, 37)", "#03f");
runTest("Valid percentages should be parsed", "rgba(0%, 20%, 100%, 0.42)", "#0033ff6b");
runTest("Valid percentages should be parsed", "rgba(0%, 20%, 100%, 0)", "#03f0");
runTest("Invalid alpha values should be clamped to 0 and 1 respectively", "rgba(0%, 20%, 100%, -0.1)", "#03f0");
runTest("Invalid alpha values should be clamped to 0 and 1 respectively", "rgba(0%, 20%, 100%, -139)", "#03f0");
runTest("Percent alpha values are accepted in rgb/rgba", "rgba(255, 255, 255, 0%)", "#fff0");
runTest("Percent alpha values are accepted in rgb/rgba", "rgba(0%, 0%, 0%, 0%)", "#0000");
runTest("RGB and RGBA are synonyms", "rgba(0%, 0%, 0%)", "#000");
runTest("RGB and RGBA are synonyms", "rgba(0, 0, 0)", "#000");
runTest("Red channel resolves positive infinity to 255", "rgb(calc(infinity), 0, 0)", "red");
runTest("Green channel resolves positive infinity to 255", "rgb(0, calc(infinity), 0)", "#0f0");
runTest("Blue channel resolves positive infinity to 255", "rgb(0, 0, calc(infinity))", "#00f");
runTest("Alpha channel resolves positive infinity to fully opaque", "rgba(0, 0, 0, calc(infinity))", "#000");
runTest("Red channel resolves negative infinity to zero", "rgb(calc(-infinity), 0, 0)", "#000");
runTest("Green channel resolves negative infinity to zero", "rgb(0, calc(-infinity), 0)", "#000");
runTest("Blue channel resolves negative infinity to zero", "rgb(0, 0, calc(-infinity))", "#000");
runTest("Alpha channel resolves negative infinity to fully transparent", "rgba(0, 0, 0, calc(-infinity))", "#0000");
runTest("Red channel resolves NaN to zero", "rgb(calc(NaN), 0, 0)", "rgb(calc(NaN), 0, 0)");
runTest("Green channel resolves NaN to zero", "rgb(0, calc(NaN), 0)", "rgb(0, calc(NaN), 0)");
runTest("Blue channel resolves NaN to zero", "rgb(0, 0, calc(NaN))", "rgb(0, 0, calc(NaN))");
// TODO: do this later, requires a lot of machinery to change in calc parsing
// not necessary for spec compliance as this is technially browser behavior
// runTest("Alpha channel resolves NaN to zero", "rgba(0, 0, 0, calc(NaN))", "#0000");
// runTest(
// "Red channel resolves NaN equivalent calc statements to zero",
// "rgb(calc(0 / 0), 0, 0)",
// "rgb(calc(0 / 0), 0, 0)",
// );
runTest(
"Green channel resolves NaN equivalent calc statements to zero",
"rgb(0, calc(0 / 0), 0)",
"rgb(0, calc(0 / 0), 0)",
);
runTest(
"Blue channel resolves NaN equivalent calc statements to zero",
"rgb(0, 0, calc(0 / 0))",
"rgb(0, 0, calc(0 / 0))",
);
// runTest("Alpha channel resolves NaN equivalent calc statements to zero", "rgba(0, 0, 0, calc(0 / 0))", "#0000");
runTest("Variables above 255 get clamped to 255.", "rgb(var(--high), 0, 0)", "rgb(var(--high), 0, 0)");
runTest("Variables below 0 get clamped to 0.", "rgb(var(--negative), 64, 128)", "rgb(var(--negative), 64, 128)");
runTest(
"",
"rgb(calc(50% + (sign(1em - 10px) * 10%)), 0%, 0%, 50%)",
"rgb(calc(50% + (sign(1em - 10px) * 10%)), 0%, 0%, 50%)",
);
runTest(
"",
"rgba(calc(50% + (sign(1em - 10px) * 10%)), 0%, 0%, 50%)",
"rgba(calc(50% + (sign(1em - 10px) * 10%)), 0%, 0%, 50%)",
);
runTest(
"",
"rgb(calc(50 + (sign(1em - 10px) * 10)), 0, 0, 0.5)",
"rgb(calc(50 + (sign(1em - 10px) * 10)), 0, 0, .5)",
);
runTest(
"",
"rgba(calc(50 + (sign(1em - 10px) * 10)), 0, 0, 0.5)",
"rgba(calc(50 + (sign(1em - 10px) * 10)), 0, 0, .5)",
);
runTest(
"",
"rgb(0%, 0%, 0%, calc(50% + (sign(1em - 10px) * 10%)))",
"rgb(0%, 0%, 0%, calc(50% + (sign(1em - 10px) * 10%)))",
);
runTest(
"",
"rgba(0%, 0%, 0%, calc(50% + (sign(1em - 10px) * 10%)))",
"rgba(0%, 0%, 0%, calc(50% + (sign(1em - 10px) * 10%)))",
);
runTest(
"",
"rgb(0, 0, 0, calc(0.75 + (sign(1em - 10px) * 0.1)))",
"rgb(0, 0, 0, calc(.75 + (sign(1em - 10px) * .1)))",
);
runTest(
"",
"rgba(0, 0, 0, calc(0.75 + (sign(1em - 10px) * 0.1)))",
"rgba(0, 0, 0, calc(.75 + (sign(1em - 10px) * .1)))",
);
runTest(
"",
"rgb(calc(50% + (sign(1em - 10px) * 10%)) 0% 0% / 50%)",
"rgb(calc(50% + (sign(1em - 10px) * 10%)) 0% 0% / 50%)",
);
runTest(
"",
"rgba(calc(50% + (sign(1em - 10px) * 10%)) 0% 0% / 50%)",
"rgba(calc(50% + (sign(1em - 10px) * 10%)) 0% 0% / 50%)",
);
runTest("", "rgb(calc(50 + (sign(1em - 10px) * 10)) 0 0 / 0.5)", "rgb(calc(50 + (sign(1em - 10px) * 10)) 0 0 / .5)");
runTest(
"",
"rgba(calc(50 + (sign(1em - 10px) * 10)) 0 0 / 0.5)",
"rgba(calc(50 + (sign(1em - 10px) * 10)) 0 0 / .5)",
);
runTest(
"",
"rgb(0% 0% 0% / calc(50% + (sign(1em - 10px) * 10%)))",
"rgb(0 0 0 / calc(50% + (sign(1em - 10px) * 10%)))",
);
runTest(
"",
"rgba(0% 0% 0% / calc(50% + (sign(1em - 10px) * 10%)))",
"rgba(0% 0% 0% / calc(50% + (sign(1em - 10px) * 10%)))",
);
runTest("", "rgb(0 0 0 / calc(0.75 + (sign(1em - 10px) * 0.1)))", "rgb(0 0 0 / calc(.75 + (sign(1em - 10px) * .1)))");
runTest(
"",
"rgba(0 0 0 / calc(0.75 + (sign(1em - 10px) * 0.1)))",
"rgba(0 0 0 / calc(.75 + (sign(1em - 10px) * .1)))",
);
runTest(
"",
"rgba(calc(50% + (sign(1em - 10px) * 10%)) 0 0% / 0.5)",
"rgba(calc(50% + (sign(1em - 10px) * 10%)) 0 0% / .5)",
);
runTest(
"",
"rgba(0% 0 0% / calc(0.75 + (sign(1em - 10px) * 0.1)))",
"rgba(0% 0 0% / calc(.75 + (sign(1em - 10px) * .1)))",
);
});
@@ -0,0 +1,41 @@
import { describe } from "bun:test";
import { itBundled } from "../../expectBundled";
const runTest = (input: string, expected: string) => {
itBundled(input, {
files: {
"/a.css": /* css */ `
h1 {
color: ${input}
}
`,
},
outfile: "out.css",
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
/* a.css */
h1 {
color: ${expected};
}
`);
},
});
};
describe("color-computed", () => {
runTest("currentcolor", "currentColor");
runTest("transparent", "#0000");
runTest("red", "red");
runTest("magenta", "#f0f");
runTest("#234", "#234");
runTest("#FEDCBA", "#fedcba");
runTest("rgb(100%, 0%, 0%)", "red");
runTest("rgba(2, 3, 4, 50%)", "#02030480");
runTest("hsl(120, 100%, 50%)", "#0f0");
runTest("hsla(120, 100%, 50%, 0.25)", "#00ff0040");
runTest("rgb(-2, 3, 4)", "#000304");
runTest("rgb(100, 200, 300)", "#64c8ff");
runTest("rgb(20, 10, 0, -10)", "#140a0000");
runTest("rgb(100%, 200%, 300%)", "#fff");
});
@@ -0,0 +1,546 @@
import { describe } from "bun:test";
import { itBundled } from "../../expectBundled";
let i = 0;
const testname = () => `test-${i++}`;
describe("relative_color_out_of_gamut", () => {
itBundled(testname(), {
files: {
"/a.css": /* css */ `
h1 {
color: rgb(from color(display-p3 0 1 0) r g b / alpha);
}
`,
},
outfile: "out.css",
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
/* a.css */
h1 {
color: #00f942;
}
`);
},
});
itBundled(testname(), {
files: {
"/a.css": /* css */ `
h1 {
color: rgb(from lab(100 104.3 -50.9) r g b);
}
`,
},
outfile: "/out.css",
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
/* a.css */
h1 {
color: rgb(from lab(100 104.3 -50.9) r g b);
}
`);
},
});
itBundled(testname(), {
files: {
"/a.css": /* css */ `
h1 {
color: rgb(from lab(0 104.3 -50.9) r g b);
}
`,
},
outfile: "/out.css",
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
/* a.css */
h1 {
color: rgb(from lab(0 104.3 -50.9) r g b);
}
`);
},
});
itBundled(testname(), {
files: {
"/a.css": /* css */ `
h1 {
color: rgb(from lch(100 116 334) r g b);
}
`,
},
outfile: "/out.css",
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
/* a.css */
h1 {
color: rgb(from lch(100 116 334) r g b);
}
`);
},
});
itBundled(testname(), {
files: {
"/a.css": /* css */ `
h1 {
color: rgb(from lch(0 116 334) r g b);
}
`,
},
outfile: "/out.css",
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
/* a.css */
h1 {
color: rgb(from lch(0 116 334) r g b);
}
`);
},
});
itBundled(testname(), {
files: {
"/a.css": /* css */ `
h1 {
color: rgb(from oklab(1 0.365 -0.16) r g b);
}
`,
},
outfile: "/out.css",
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
/* a.css */
h1 {
color: rgb(from oklab(1 .365 -.16) r g b);
}
`);
},
});
itBundled(testname(), {
files: {
"/a.css": /* css */ `
h1 {
color: rgb(from oklab(0 0.365 -0.16) r g b);
}
`,
},
outfile: "/out.css",
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
/* a.css */
h1 {
color: rgb(from oklab(0 .365 -.16) r g b);
}
`);
},
});
itBundled(testname(), {
files: {
"/a.css": /* css */ `
h1 {
color: rgb(from oklch(1 0.399 336.3) r g b);
}
`,
},
outfile: "/out.css",
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
/* a.css */
h1 {
color: rgb(from oklch(1 .399 336.3) r g b);
}
`);
},
});
itBundled(testname(), {
files: {
"/a.css": /* css */ `
h1 {
color: rgb(from oklch(0 0.399 336.3) r g b);
}
`,
},
outfile: "/out.css",
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
/* a.css */
h1 {
color: rgb(from oklch(0 .399 336.3) r g b);
}
`);
},
});
itBundled(testname(), {
files: {
"/a.css": /* css */ `
h1 {
color: hsl(from color(display-p3 0 1 0) h s l / alpha);
}
`,
},
outfile: "/out.css",
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
/* a.css */
h1 {
color: #00f942;
}
`);
},
});
itBundled(testname(), {
files: {
"/a.css": /* css */ `
h1 {
color: hsl(from lab(100 104.3 -50.9) h s l);
}
`,
},
outfile: "/out.css",
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
/* a.css */
h1 {
color: hsl(from lab(100 104.3 -50.9) h s l);
}
`);
},
});
itBundled(testname(), {
files: {
"/a.css": /* css */ `
h1 {
color: hsl(from lab(0 104.3 -50.9) h s l);
}
`,
},
outfile: "/out.css",
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
/* a.css */
h1 {
color: hsl(from lab(0 104.3 -50.9) h s l);
}
`);
},
});
itBundled(testname(), {
files: {
"/a.css": /* css */ `
h1 {
color: hsl(from lch(100 116 334) h s l);
}
`,
},
outfile: "/out.css",
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
/* a.css */
h1 {
color: hsl(from lch(100 116 334) h s l);
}
`);
},
});
itBundled(testname(), {
files: {
"/a.css": /* css */ `
h1 {
color: hsl(from lch(0 116 334) h s l);
}
`,
},
outfile: "/out.css",
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
/* a.css */
h1 {
color: hsl(from lch(0 116 334) h s l);
}
`);
},
});
itBundled(testname(), {
files: {
"/a.css": /* css */ `
h1 {
color: hsl(from oklab(1 0.365 -0.16) h s l);
}
`,
},
outfile: "/out.css",
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
/* a.css */
h1 {
color: hsl(from oklab(1 .365 -.16) h s l);
}
`);
},
});
itBundled(testname(), {
files: {
"/a.css": /* css */ `
h1 {
color: hsl(from oklab(0 0.365 -0.16) h s l);
}
`,
},
outfile: "/out.css",
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
/* a.css */
h1 {
color: hsl(from oklab(0 .365 -.16) h s l);
}
`);
},
});
itBundled(testname(), {
files: {
"/a.css": /* css */ `
h1 {
color: hsl(from oklch(1 0.399 336.3) h s l);
}
`,
},
outfile: "/out.css",
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
/* a.css */
h1 {
color: hsl(from oklch(1 .399 336.3) h s l);
}
`);
},
});
itBundled(testname(), {
files: {
"/a.css": /* css */ `
h1 {
color: hsl(from oklch(0 0.399 336.3) h s l);
}
`,
},
outfile: "/out.css",
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
/* a.css */
h1 {
color: hsl(from oklch(0 .399 336.3) h s l);
}
`);
},
});
itBundled(testname(), {
files: {
"/a.css": /* css */ `
h1 {
color: hwb(from color(display-p3 0 1 0) h w b / alpha);
}
`,
},
outfile: "/out.css",
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
/* a.css */
h1 {
color: #00f942;
}
`);
},
});
itBundled(testname(), {
files: {
"/a.css": /* css */ `
h1 {
color: hwb(from lab(100 104.3 -50.9) h w b);
}
`,
},
outfile: "/out.css",
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
/* a.css */
h1 {
color: hwb(from lab(100 104.3 -50.9) h w b);
}
`);
},
});
itBundled(testname(), {
files: {
"/a.css": /* css */ `
h1 {
color: hwb(from lab(0 104.3 -50.9) h w b);
}
`,
},
outfile: "/out.css",
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
/* a.css */
h1 {
color: hwb(from lab(0 104.3 -50.9) h w b);
}
`);
},
});
itBundled(testname(), {
files: {
"/a.css": /* css */ `
h1 {
color: hwb(from lch(100 116 334) h w b);
}
`,
},
outfile: "/out.css",
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
/* a.css */
h1 {
color: hwb(from lch(100 116 334) h w b);
}
`);
},
});
itBundled(testname(), {
files: {
"/a.css": /* css */ `
h1 {
color: hwb(from lch(0 116 334) h w b);
}
`,
},
outfile: "/out.css",
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
/* a.css */
h1 {
color: hwb(from lch(0 116 334) h w b);
}
`);
},
});
itBundled(testname(), {
files: {
"/a.css": /* css */ `
h1 {
color: hwb(from oklab(1 0.365 -0.16) h w b);
}
`,
},
outfile: "/out.css",
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
/* a.css */
h1 {
color: hwb(from oklab(1 .365 -.16) h w b);
}
`);
},
});
itBundled(testname(), {
files: {
"/a.css": /* css */ `
h1 {
color: hwb(from oklab(0 0.365 -0.16) h w b);
}
`,
},
outfile: "/out.css",
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
/* a.css */
h1 {
color: hwb(from oklab(0 .365 -.16) h w b);
}
`);
},
});
itBundled(testname(), {
files: {
"/a.css": /* css */ `
h1 {
color: hwb(from oklch(1 0.399 336.3) h w b);
}
`,
},
outfile: "/out.css",
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
/* a.css */
h1 {
color: hwb(from oklch(1 .399 336.3) h w b);
}
`);
},
});
itBundled(testname(), {
files: {
"/a.css": /* css */ `
h1 {
color: hwb(from oklch(0 0.399 336.3) h w b);
}
`,
},
outfile: "/out.css",
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
/* a.css */
h1 {
color: hwb(from oklch(0 .399 336.3) h w b);
}
`);
},
});
});