diff --git a/.changeset/tidy-comics-shave.md b/.changeset/tidy-comics-shave.md new file mode 100644 index 0000000..525b325 --- /dev/null +++ b/.changeset/tidy-comics-shave.md @@ -0,0 +1,5 @@ +--- +"@hebilicious/cssforge": patch +--- + +fix nesting for selector and atRules diff --git a/CHANGELOG.md b/CHANGELOG.md index 198b870..685c637 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,8 @@ - ab752be: # Introduce variantNameOnly feature for themes. When working with themes, you can choose to only include the variant name in the CSS - variable name by setting `variantNameOnly: true` in the color definition settings. This is - usually used in combination with `condition` to conditionnally apply themes. + variable name by setting `variantNameOnly: true` in the color definition settings. This + is usually used in combination with `condition` to conditionnally apply themes. - Default: `--theme-${themeName}-${colorName}-${variantName}` - VariantOnly Name: `--${variantName}` diff --git a/README.md b/README.md index ade741b..6d9fea7 100644 --- a/README.md +++ b/README.md @@ -217,7 +217,7 @@ export default defineConfig({ cyan: { hex: "#00FFFF" }, }, settings: { - condition: ".Another", + selector: ".Another", }, }, }, @@ -272,7 +272,7 @@ export default defineConfig({ }, }, settings: { - condition: "@media (prefers-color-scheme: dark)", + atRule: "@media (prefers-color-scheme: dark)", }, }, pink: { @@ -292,7 +292,7 @@ export default defineConfig({ }, }, settings: { - condition: ".ThemePink", + selector: ".ThemePink", }, }, }, @@ -321,7 +321,7 @@ export default defineConfig({ cyan: { hex: "#00FFFF" }, }, settings: { - condition: ".Another", + selector: ".Another", }, }, }, @@ -376,7 +376,7 @@ export default defineConfig({ }, }, settings: { - condition: "@media (prefers-color-scheme: dark)", + atRule: "@media (prefers-color-scheme: dark)", }, }, pink: { @@ -396,7 +396,7 @@ export default defineConfig({ }, }, settings: { - condition: ".ThemePink", + selector: ".ThemePink", }, }, }, @@ -418,11 +418,6 @@ This will generate the following CSS : --palette-simple-blue: oklch(45.201% 0.31321 264.05202); --palette-simple-violet: oklch(70% 0.2 270); --palette-simple-red: oklch(62.796% 0.25768 29.23388); - /* another */ - .Another { - --palette-another-yellow: oklch(96.798% 0.21101 109.76924); - --palette-another-cyan: oklch(90.54% 0.15455 194.76896); - } /* Gradients */ /* white-green */ --gradients-white-green-primary: linear-gradient( @@ -441,12 +436,17 @@ This will generate the following CSS : --primary: var(--palette-another-yellow); --secondary: var(--palette-another-cyan); } - /* Theme: pink */ - .ThemePink { - /* background */ - --primary: var(--palette-simple-red); - --secondary: var(--palette-simple-violet); - } +} +/* another */ +.Another { + --palette-another-yellow: oklch(96.798% 0.21101 109.76924); + --palette-another-cyan: oklch(90.54% 0.15455 194.76896); +} +/* Theme: pink */ +.ThemePink { + /* background */ + --primary: var(--palette-simple-red); + --secondary: var(--palette-simple-violet); } ``` @@ -454,15 +454,15 @@ This will generate the following CSS : #### Condition -You can conditionnally apply colors, gradients or themes by setting the `condition` -property to a selector or media query. Your variables will be wrapped within the -condition. +You can conditionnally apply colors, gradients or themes by setting the `atRule` or the +`selector` properties. Your variables will be wrapped within `:root` and the selectors +will be placed outside of it. #### Theme: Variant Name Only When working with themes, you can choose to only include the variant name in the CSS variable name by setting `variantNameOnly: true` in the color definition settings. This is -usually used in combination with `condition` to conditionnally apply themes. +usually used in combination with `selector` to conditionnally apply themes. - Default: `--theme-${themeName}-${colorName}-${variantName}` - VariantOnly Name: `--${variantName}` diff --git a/example/basic/cssforge.config.ts b/example/basic/cssforge.config.ts index 323e5a8..67cd2b5 100644 --- a/example/basic/cssforge.config.ts +++ b/example/basic/cssforge.config.ts @@ -60,13 +60,13 @@ export default defineConfig( 100: { hex: "#4F46E5" }, }, }, - // dark-mode overrides wrapped in a condition + // dark-mode overrides wrapped in a atRule coral_dark: { value: { 100: { hex: "#FF6347" }, }, settings: { - condition: "@media (prefers-color-scheme: dark)", + atRule: "@media (prefers-color-scheme: dark)", }, }, mint_dark: { @@ -74,7 +74,7 @@ export default defineConfig( 100: { hex: "#22C55E" }, }, settings: { - condition: "@media (prefers-color-scheme: dark)", + atRule: "@media (prefers-color-scheme: dark)", }, }, indigo_dark: { @@ -82,7 +82,7 @@ export default defineConfig( 100: { hex: "#4338CA" }, }, settings: { - condition: "@media (prefers-color-scheme: dark)", + atRule: "@media (prefers-color-scheme: dark)", }, }, }, diff --git a/src/generator.ts b/src/generator.ts index c69b87e..131ed9a 100644 --- a/src/generator.ts +++ b/src/generator.ts @@ -143,16 +143,24 @@ export function generateTS(config: Partial): string { */ export function generateCSS(config: Partial): string { const chunks: string[] = ["/*____ CSSForge ____*/", ":root {"]; + const outsideChunks: string[] = []; const processedConfig: { - [key: string]: { css: string; resolveMap: ResolveMap } | undefined; + [key: string]: + | { css: { root?: string; outside?: string }; resolveMap: ResolveMap } + | undefined; } = {}; // Process colors if present if (config.colors) { processedConfig.colors = processColors(config.colors); if (processedConfig.colors) { - chunks.push("/*____ Colors ____*/"); - chunks.push(processedConfig.colors.css); + if (processedConfig.colors.css.root) { + chunks.push("/*____ Colors ____*/"); + chunks.push(processedConfig.colors.css.root); + } + if (processedConfig.colors.css.outside) { + outsideChunks.push(processedConfig.colors.css.outside); + } } } @@ -160,8 +168,13 @@ export function generateCSS(config: Partial): string { if (config.spacing) { processedConfig.spacing = processSpacing(config.spacing); if (processedConfig.spacing) { - chunks.push("/*____ Spacing ____*/"); - chunks.push(processedConfig.spacing.css); + if (processedConfig.spacing.css.root) { + chunks.push("/*____ Spacing ____*/"); + chunks.push(processedConfig.spacing.css.root); + } + if (processedConfig.spacing.css.outside) { + outsideChunks.push(processedConfig.spacing.css.outside); + } } } @@ -169,8 +182,13 @@ export function generateCSS(config: Partial): string { if (config.typography) { processedConfig.typography = processTypography(config.typography); if (processedConfig.typography) { - chunks.push("/*____ Typography ____*/"); - chunks.push(processedConfig.typography.css); + if (processedConfig.typography.css.root) { + chunks.push("/*____ Typography ____*/"); + chunks.push(processedConfig.typography.css.root); + } + if (processedConfig.typography.css.outside) { + outsideChunks.push(processedConfig.typography.css.outside); + } } } @@ -182,12 +200,19 @@ export function generateCSS(config: Partial): string { spacing: config.spacing, }); if (primitiveVars) { - chunks.push("/*____ Primitives ____*/"); - chunks.push(primitiveVars.css); + if (primitiveVars.css.root) { + chunks.push("/*____ Primitives ____*/"); + chunks.push(primitiveVars.css.root); + } + if (primitiveVars.css.outside) { + outsideChunks.push(primitiveVars.css.outside); + } } } chunks.push("}"); - // Join all chunks with double newline for readability + + if (outsideChunks.length > 0) chunks.push(outsideChunks.join("\n")); + return chunks.join("\n"); } diff --git a/src/lib.ts b/src/lib.ts index f3a3208..6812bc4 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -12,8 +12,8 @@ export type ResolveMap = Map< * CSS and a resolve map. */ export interface Output { - /** The generated CSS string. */ - css: string; + /** The generated CSS strings. */ + css: { root?: string; outside?: string }; /** A map for resolving variable paths. */ resolveMap: ResolveMap; } diff --git a/src/modules/colors.ts b/src/modules/colors.ts index a3b5100..4dfa24e 100644 --- a/src/modules/colors.ts +++ b/src/modules/colors.ts @@ -31,9 +31,13 @@ interface ColorVariants { export interface WithCondition { /** - * CSS condition to wrap variables in (e.g., ".MyClass", "@media (prefers-color-scheme: dark)") + * CSS selector to wrap variables in (e.g., ".MyClass"). This will be extracted out of the root. */ - condition?: string; + selector?: string; + /** + * CSS at-rule to wrap variables in (e.g., "@supports (display: grid)") + */ + atRule?: string; } /** * Settings for palette colors, including optional conditions like media queries. @@ -219,41 +223,65 @@ function colorValueToOklch(value: ColorValueOrString): string { * ``` */ export function processColors(colors: ColorConfig): Output { - const cssOutput: string[] = []; + const rootOutput: string[] = []; + const outsideOutput: string[] = []; const resolveMap: ResolveMap = new Map(); - cssOutput.push(`/* Palette */`); + rootOutput.push(`/* Palette */`); const moduleKey = "palette"; function conditionalBuilder( settings: WithCondition | undefined, initialComment: string, ) { - const leadingComments: string[] = []; const innerComments: string[] = []; const vars: string[] = []; - if (settings?.condition) { - leadingComments.push(initialComment); - } else { - cssOutput.push(initialComment); - } + const hasSelector = Boolean(settings?.selector); + const hasAtRule = Boolean(settings?.atRule); + + // If no settings provided, emit comment immediately into root output + if (!hasSelector && !hasAtRule) rootOutput.push(initialComment); return { addComment(c: string) { - if (settings?.condition) innerComments.push(c); - else cssOutput.push(c); + if (hasSelector || hasAtRule) innerComments.push(c); + else rootOutput.push(c); }, pushVariable(v: string) { - if (settings?.condition) vars.push(v); - else cssOutput.push(v); + if (hasSelector || hasAtRule) vars.push(v); + else rootOutput.push(v); }, finalize() { - if (settings?.condition && vars.length > 0) { - cssOutput.push(...leadingComments); - cssOutput.push(`${settings.condition} {`); - cssOutput.push(...innerComments.map((c) => ` ${c}`)); - cssOutput.push(...vars.map((v) => ` ${v}`)); - cssOutput.push(`}`); + if (!hasSelector && !hasAtRule) return; + if (vars.length === 0 && innerComments.length === 0) return; + if (!settings) return; + if (hasSelector && hasAtRule) { + outsideOutput.push(initialComment); + outsideOutput.push(`${settings.atRule} {`); + outsideOutput.push(` ${settings.selector} {`); + outsideOutput.push(...innerComments.map((c) => ` ${c}`)); + outsideOutput.push(...vars.map((v) => ` ${v}`)); + outsideOutput.push(` }`); + outsideOutput.push(`}`); + return; + } + + if (hasSelector) { + outsideOutput.push(initialComment); + outsideOutput.push(`${settings.selector} {`); + outsideOutput.push(...innerComments.map((c) => ` ${c}`)); + outsideOutput.push(...vars.map((v) => ` ${v}`)); + outsideOutput.push(`}`); + return; + } + + if (hasAtRule) { + rootOutput.push(initialComment); + rootOutput.push(`${settings.atRule} {`); + rootOutput.push(...innerComments.map((c) => ` ${c}`)); + rootOutput.push(...vars.map((v) => ` ${v}`)); + rootOutput.push(`}`); + return; } }, }; @@ -289,9 +317,12 @@ export function processColors(colors: ColorConfig): Output { } if (colors.gradients) { - cssOutput.push(`/* Gradients */`); + rootOutput.push(`/* Gradients */`); const moduleKey = "gradients"; - const palette = { css: cssOutput.join("\n"), resolveMap }; + const palette = { + css: { root: rootOutput.join("\n"), outside: outsideOutput.join("\n") }, + resolveMap, + }; for (const [gradientName, gradient] of Object.entries(colors.gradients.value)) { validateName(gradientName); @@ -335,9 +366,12 @@ export function processColors(colors: ColorConfig): Output { } if (colors.theme) { - cssOutput.push(`/* Themes */`); + rootOutput.push(`/* Themes */`); const moduleKey = "theme"; - const palette = { css: cssOutput.join("\n"), resolveMap }; + const palette = { + css: { root: rootOutput.join("\n"), outside: outsideOutput.join("\n") }, + resolveMap, + }; for (const [themeName, themeConfig] of Object.entries(colors.theme)) { validateName(themeName); @@ -387,7 +421,9 @@ export function processColors(colors: ColorConfig): Output { } } - const output = { css: cssOutput.join("\n"), resolveMap }; - // console.log(output); + const output = { + css: { root: rootOutput.join("\n"), outside: outsideOutput.join("\n") }, + resolveMap, + }; return output; } diff --git a/src/modules/primitive.ts b/src/modules/primitive.ts index 20c766a..b8965e7 100644 --- a/src/modules/primitive.ts +++ b/src/modules/primitive.ts @@ -143,5 +143,5 @@ export function processPrimitives( } } - return { css: cssOutput.join("\n"), resolveMap }; + return { css: { root: cssOutput.join("\n") }, resolveMap }; } diff --git a/src/modules/spacing.ts b/src/modules/spacing.ts index 3bbf018..57bfe33 100644 --- a/src/modules/spacing.ts +++ b/src/modules/spacing.ts @@ -124,5 +124,5 @@ export function processSpacing(spacing: SpacingConfig): Output { } } - return { css: cssOutput.join("\n"), resolveMap }; + return { css: { root: cssOutput.join("\n") }, resolveMap }; } diff --git a/src/modules/typography.ts b/src/modules/typography.ts index a5a4da3..8b52ca8 100644 --- a/src/modules/typography.ts +++ b/src/modules/typography.ts @@ -119,5 +119,5 @@ export function processTypography(config: TypographyConfig): Output { } } - return { css: cssOutput.join("\n"), resolveMap }; + return { css: { root: cssOutput.join("\n") }, resolveMap }; } diff --git a/tests/__snapshots__/colors.test.ts.snap b/tests/__snapshots__/colors.test.ts.snap index 3c2fdaa..25a561f 100644 --- a/tests/__snapshots__/colors.test.ts.snap +++ b/tests/__snapshots__/colors.test.ts.snap @@ -279,7 +279,7 @@ snapshot[`processColors - handles themes referencing gradients 2`] = ` ] `; -snapshot[`processColors - handles gradients with conditions 1`] = ` +snapshot[`processColors - handles gradients with atRule 1`] = ` "/* Palette */ /* coral */ --palette-coral-50: oklch(73.58% 0.16378 34.33822); @@ -290,7 +290,7 @@ snapshot[`processColors - handles gradients with conditions 1`] = ` }" `; -snapshot[`processColors - handles gradients with conditions 2`] = ` +snapshot[`processColors - handles gradients with atRule 2`] = ` [ [ "palette.coral.50", @@ -311,7 +311,7 @@ snapshot[`processColors - handles gradients with conditions 2`] = ` ] `; -snapshot[`processColors - handles palette colors with conditions 1`] = ` +snapshot[`processColors - handles palette colors with atRule 1`] = ` "/* Palette */ /* background */ --palette-background-light: oklch(100% 0 0); @@ -321,7 +321,7 @@ snapshot[`processColors - handles palette colors with conditions 1`] = ` }" `; -snapshot[`processColors - handles palette colors with conditions 2`] = ` +snapshot[`processColors - handles palette colors with atRule 2`] = ` [ [ "palette.background.light", @@ -342,7 +342,7 @@ snapshot[`processColors - handles palette colors with conditions 2`] = ` ] `; -snapshot[`processColors - handles themes with class condition 1`] = ` +snapshot[`processColors - handles themes with selector 1`] = ` "/* Palette */ /* base */ --palette-base-primary: oklch(73.511% 0.16799 40.24666); @@ -354,7 +354,42 @@ snapshot[`processColors - handles themes with class condition 1`] = ` }" `; -snapshot[`processColors - handles themes with class condition 2`] = ` +snapshot[`processColors - handles themes with selector 2`] = ` +[ + [ + "palette.base.primary", + { + key: "--palette-base-primary", + value: "oklch(73.511% 0.16799 40.24666)", + variable: "--palette-base-primary: oklch(73.511% 0.16799 40.24666);", + }, + ], + [ + "theme.dark.background.primary", + { + key: "--theme-dark-background-primary", + value: "var(--palette-base-primary)", + variable: "--theme-dark-background-primary: var(--palette-base-primary);", + }, + ], +] +`; + +snapshot[`processColors - handles selector + atRule 1`] = ` +"/* Palette */ +/* base */ +--palette-base-primary: oklch(73.511% 0.16799 40.24666); +/* Themes */ +/* Theme: dark */ +@media (prefers-color-scheme: dark) { + .dark-theme { + /* background */ + --theme-dark-background-primary: var(--palette-base-primary); + } +}" +`; + +snapshot[`processColors - handles selector + atRule 2`] = ` [ [ "palette.base.primary", diff --git a/tests/__snapshots__/primitive.test.ts.snap b/tests/__snapshots__/primitive.test.ts.snap index 9b7d113..c6d0fcf 100644 --- a/tests/__snapshots__/primitive.test.ts.snap +++ b/tests/__snapshots__/primitive.test.ts.snap @@ -1,12 +1,14 @@ export const snapshot = {}; snapshot[`processPrimitives - processes button with variables 1`] = ` -"/* button */ +{ + root: "/* button */ --button-small-width: 120px; --button-small-height: 40px; --button-small-fontSize: var(--typography_fluid-arial-foo-m); --button-small-radius: 8px; ---button-small-padding: var(--spacing-size-2) var(--spacing-size-3);" +--button-small-padding: var(--spacing-size-2) var(--spacing-size-3);", +} `; snapshot[`processPrimitives - processes button with variables 2`] = ` @@ -55,13 +57,15 @@ snapshot[`processPrimitives - processes button with variables 2`] = ` `; snapshot[`processPrimitives - processes buttons with settings 1`] = ` -"/* button */ +{ + root: "/* button */ --button-small-width: 120px; --button-small-height: 40px; --button-small-radius: 8px; --button-big-width: 15rem; --button-big-height: 5rem; ---button-big-radius: 1rem;" +--button-big-radius: 1rem;", +} `; snapshot[`processPrimitives - processes buttons with settings 2`] = ` @@ -118,10 +122,12 @@ snapshot[`processPrimitives - processes buttons with settings 2`] = ` `; snapshot[`processPrimitives - references colors, gradients, and themes 1`] = ` -"/* card */ +{ + root: "/* card */ --card-default-background-color: var(--theme-light-background-primary); --card-default-background-image: var(--gradients-orangeGradient-primary); ---card-default-border-color: var(--palette-coral-50);" +--card-default-border-color: var(--palette-coral-50);", +} `; snapshot[`processPrimitives - references colors, gradients, and themes 2`] = ` @@ -154,8 +160,10 @@ snapshot[`processPrimitives - references colors, gradients, and themes 2`] = ` `; snapshot[`processPrimitives - uses fluid spacing references 1`] = ` -"/* box */ ---box-default-padding: var(--spacing_fluid-gap-gs-s) var(--spacing_fluid-gap-gs-m);" +{ + root: "/* box */ +--box-default-padding: var(--spacing_fluid-gap-gs-s) var(--spacing_fluid-gap-gs-m);", +} `; snapshot[`processPrimitives - uses fluid spacing references 2`] = ` diff --git a/tests/__snapshots__/typography.test.ts.snap b/tests/__snapshots__/typography.test.ts.snap index 0ce6ae6..1311895 100644 --- a/tests/__snapshots__/typography.test.ts.snap +++ b/tests/__snapshots__/typography.test.ts.snap @@ -1,7 +1,8 @@ export const snapshot = {}; snapshot[`processTypography - generates correct CSS variables 1`] = ` -"--typography_fluid-arial-4xl: clamp(2.6703rem, 2.5608rem + 0.5474vw, 3.0518rem); +{ + root: "--typography_fluid-arial-4xl: clamp(2.6703rem, 2.5608rem + 0.5474vw, 3.0518rem); --typography_fluid-arial-3xl: clamp(2.1362rem, 2.0486rem + 0.4379vw, 2.4414rem); --typography_fluid-arial-2xl: clamp(1.709rem, 1.6389rem + 0.3503vw, 1.9531rem); --typography_fluid-arial-xl: clamp(1.3672rem, 1.3111rem + 0.2803vw, 1.5625rem); @@ -9,7 +10,8 @@ snapshot[`processTypography - generates correct CSS variables 1`] = ` --typography_fluid-arial-m: clamp(0.875rem, 0.8391rem + 0.1794vw, 1rem); --typography_fluid-arial-s: clamp(0.7rem, 0.6713rem + 0.1435vw, 0.8rem); --typography_fluid-arial-xs: clamp(0.56rem, 0.537rem + 0.1148vw, 0.64rem); ---typography_fluid-arial-2xs: clamp(0.448rem, 0.4296rem + 0.0918vw, 0.512rem);" +--typography_fluid-arial-2xs: clamp(0.448rem, 0.4296rem + 0.0918vw, 0.512rem);", +} `; snapshot[`processTypography - generates correct CSS variables 2`] = ` @@ -90,7 +92,8 @@ snapshot[`processTypography - generates correct CSS variables 2`] = ` `; snapshot[`typography - can handle custom labels and prefixes 1`] = ` -"--typography_fluid-arial-text-h1: clamp(4.1723rem, 4.0013rem + 0.8553vw, 4.7684rem); +{ + root: "--typography_fluid-arial-text-h1: clamp(4.1723rem, 4.0013rem + 0.8553vw, 4.7684rem); --typography_fluid-arial-text-h2: clamp(3.3379rem, 3.201rem + 0.6843vw, 3.8147rem); --typography_fluid-arial-text-h3: clamp(2.6703rem, 2.5608rem + 0.5474vw, 3.0518rem); --typography_fluid-arial-text-h4: clamp(2.1362rem, 2.0486rem + 0.4379vw, 2.4414rem); @@ -99,7 +102,8 @@ snapshot[`typography - can handle custom labels and prefixes 1`] = ` --typography_fluid-arial-text-l: clamp(1.0938rem, 1.0489rem + 0.2242vw, 1.25rem); --typography_fluid-arial-text-m: clamp(0.875rem, 0.8391rem + 0.1794vw, 1rem); --typography_fluid-arial-text-s: clamp(0.7rem, 0.6713rem + 0.1435vw, 0.8rem); ---typography_fluid-arial-text-xs: clamp(0.56rem, 0.537rem + 0.1148vw, 0.64rem);" +--typography_fluid-arial-text-xs: clamp(0.56rem, 0.537rem + 0.1148vw, 0.64rem);", +} `; snapshot[`typography - can handle custom labels and prefixes 2`] = ` @@ -188,14 +192,16 @@ snapshot[`typography - can handle custom labels and prefixes 2`] = ` `; snapshot[`processTypography - can process weights 1`] = ` -"--typography_fluid-arial-2xl: clamp(1.709rem, 1.6389rem + 0.3503vw, 1.9531rem); +{ + root: "--typography_fluid-arial-2xl: clamp(1.709rem, 1.6389rem + 0.3503vw, 1.9531rem); --typography_fluid-arial-xl: clamp(1.3672rem, 1.3111rem + 0.2803vw, 1.5625rem); --typography_fluid-arial-l: clamp(1.0938rem, 1.0489rem + 0.2242vw, 1.25rem); --typography_fluid-arial-m: clamp(0.875rem, 0.8391rem + 0.1794vw, 1rem); --typography_fluid-arial-s: clamp(0.7rem, 0.6713rem + 0.1435vw, 0.8rem); --typography_fluid-arial-xs: clamp(0.56rem, 0.537rem + 0.1148vw, 0.64rem); --typography_fluid-arial-2xs: clamp(0.448rem, 0.4296rem + 0.0918vw, 0.512rem); ---typography-weight-arial-regular: 500;" +--typography-weight-arial-regular: 500;", +} `; snapshot[`processTypography - can process weights 2`] = ` diff --git a/tests/colors.test.ts b/tests/colors.test.ts index 108d88a..310ad7d 100644 --- a/tests/colors.test.ts +++ b/tests/colors.test.ts @@ -1,7 +1,7 @@ import { assert, assertEquals } from "@std/assert"; import { assertSnapshot } from "@std/testing/snapshot"; import { defineConfig, processColors } from "../src/mod.ts"; -import { getLines } from "./helpers.ts"; +import { combine, getLines } from "./helpers.ts"; Deno.test("processColors - converts hex to oklch", async (t) => { const config = defineConfig({ @@ -20,7 +20,8 @@ Deno.test("processColors - converts hex to oklch", async (t) => { }); const { css, resolveMap } = processColors(config.colors); - const lines = getLines(css); + const combined = combine(css); + const lines = getLines(combined); assertEquals( lines[2].trim(), "--palette-coral-100: oklch(73.511% 0.16799 40.24666);", @@ -33,7 +34,7 @@ Deno.test("processColors - converts hex to oklch", async (t) => { "palette.coral.100", "palette.coral.200", ]); - await assertSnapshot(t, css); + await assertSnapshot(t, combined); await assertSnapshot(t, Array.from(resolveMap.entries())); }); @@ -57,7 +58,8 @@ Deno.test("processColors - handles different color formats", async (t) => { } as const, ); const { css, resolveMap } = processColors(config.colors); - const lines = getLines(css); + const combined = combine(css); + const lines = getLines(combined); assertEquals( lines[2].trim(), @@ -79,7 +81,7 @@ Deno.test("processColors - handles different color formats", async (t) => { "palette.brand.400", "palette.brand.default", ]); - await assertSnapshot(t, css); + await assertSnapshot(t, combined); await assertSnapshot(t, Array.from(resolveMap.entries())); }); @@ -99,7 +101,8 @@ Deno.test("processColors - handles string values", async (t) => { }, }); const { css, resolveMap } = processColors(config.colors); - const lines = getLines(css); + const combined = combine(css); + const lines = getLines(combined); assertEquals( lines[2].trim(), "--palette-simple-white: oklch(100% 0 0);", @@ -112,7 +115,7 @@ Deno.test("processColors - handles string values", async (t) => { "palette.simple.white", "palette.simple.black", ]); - await assertSnapshot(t, css); + await assertSnapshot(t, combined); await assertSnapshot(t, Array.from(resolveMap.entries())); }); @@ -148,7 +151,8 @@ Deno.test("processColors - handles themes", async (t) => { }, }); const { css, resolveMap } = processColors(config.colors); - const lines = getLines(css); + const combined = combine(css); + const lines = getLines(combined); assertEquals( lines[7].trim(), "--theme-light-background-primary: var(--palette-simple-white);", @@ -163,7 +167,7 @@ Deno.test("processColors - handles themes", async (t) => { "theme.light.background.primary", "theme.light.background.secondary", ]); - await assertSnapshot(t, css); + await assertSnapshot(t, combined); await assertSnapshot(t, Array.from(resolveMap.entries())); }); @@ -183,7 +187,8 @@ Deno.test("processColors - handles transparency", async (t) => { }, }); const { css, resolveMap } = processColors(config.colors); - const lines = getLines(css); + const combined = combine(css); + const lines = getLines(combined); assertEquals( lines[2].trim(), "--palette-alpha-softGray1: oklch(14.48% 0 0 / 12%);", @@ -196,7 +201,7 @@ Deno.test("processColors - handles transparency", async (t) => { "palette.alpha.softGray1", "palette.alpha.softGray2", ]); - await assertSnapshot(t, css); + await assertSnapshot(t, combined); await assertSnapshot(t, Array.from(resolveMap.entries())); }); @@ -241,7 +246,8 @@ Deno.test("processColors - generates gradient with color variables", async (t) = }); const result = processColors(config.colors); - const lines = getLines(result.css); + const combined = [result.css.root, result.css.outside].filter(Boolean).join("\n"); + const lines = getLines(combined); const expected = [ "--gradients-orangeGradient-primary: linear-gradient(261.78deg, var(--palette-coral-50) 33.1%, var(--palette-coral-90) 56.3%, var(--palette-coral-100) 65.78%, var(--palette-indigo-100) 84.23%);", ].join("\n"); @@ -254,7 +260,7 @@ Deno.test("processColors - generates gradient with color variables", async (t) = "palette.indigo.100", "gradients.orangeGradient.primary", ]); - await assertSnapshot(t, result.css); + await assertSnapshot(t, combined); await assertSnapshot(t, Array.from(result.resolveMap.entries())); }); @@ -303,7 +309,8 @@ Deno.test("processColors - handles themes referencing gradients", async (t) => { }); const { css, resolveMap } = processColors(config.colors); - const lines = getLines(css); + const combined = combine(css); + const lines = getLines(combined); const lastLine = lines.pop(); assertEquals( lastLine, @@ -314,11 +321,11 @@ Deno.test("processColors - handles themes referencing gradients", async (t) => { "gradients.orangeGradient.primary", "theme.light.background.primary", ]); - await assertSnapshot(t, css); + await assertSnapshot(t, combined); await assertSnapshot(t, Array.from(resolveMap.entries())); }); -Deno.test("processColors - handles gradients with conditions", async (t) => { +Deno.test("processColors - handles gradients with atRule", async (t) => { const config = defineConfig({ colors: { palette: { @@ -343,7 +350,7 @@ Deno.test("processColors - handles gradients with conditions", async (t) => { }, }, settings: { - condition: "@media (prefers-color-scheme: dark)", + atRule: "@media (prefers-color-scheme: dark)", }, }, }, @@ -352,7 +359,8 @@ Deno.test("processColors - handles gradients with conditions", async (t) => { }); const { css, resolveMap } = processColors(config.colors); - const lines = getLines(css); + const combined = combine(css); + const lines = getLines(combined); const mediaQueryIndex = lines.findIndex((line) => line.includes("@media (prefers-color-scheme: dark)") @@ -378,11 +386,11 @@ Deno.test("processColors - handles gradients with conditions", async (t) => { "palette.coral.50", "gradients.orangeGradient.primary", ]); - await assertSnapshot(t, css); + await assertSnapshot(t, combined); await assertSnapshot(t, Array.from(resolveMap.entries())); }); -Deno.test("processColors - handles palette colors with conditions", async (t) => { +Deno.test("processColors - handles palette colors with atRule", async (t) => { const config = defineConfig({ colors: { palette: { @@ -397,7 +405,7 @@ Deno.test("processColors - handles palette colors with conditions", async (t) => dark: { hex: "#000" }, }, settings: { - condition: "@media (prefers-color-scheme: dark)", + atRule: "@media (prefers-color-scheme: dark)", }, }, }, @@ -405,7 +413,8 @@ Deno.test("processColors - handles palette colors with conditions", async (t) => }, }); const { css, resolveMap } = processColors(config.colors); - const lines = getLines(css); + const combined = combine(css); + const lines = getLines(combined); assertEquals( lines[2].trim(), @@ -439,11 +448,11 @@ Deno.test("processColors - handles palette colors with conditions", async (t) => "palette.background.light", "palette.backgroundDark.dark", ]); - await assertSnapshot(t, css); + await assertSnapshot(t, combined); await assertSnapshot(t, Array.from(resolveMap.entries())); }); -Deno.test("processColors - handles themes with class condition", async (t) => { +Deno.test("processColors - handles themes with selector", async (t) => { const config = defineConfig({ colors: { palette: { @@ -468,7 +477,7 @@ Deno.test("processColors - handles themes with class condition", async (t) => { }, }, settings: { - condition: ".dark-theme", + selector: ".dark-theme", }, }, }, @@ -476,7 +485,8 @@ Deno.test("processColors - handles themes with class condition", async (t) => { }); const { css, resolveMap } = processColors(config.colors); - const lines = getLines(css); + const combined = combine(css); + const lines = getLines(combined); const classIndex = lines.findIndex((l) => l.includes(".dark-theme")); assertEquals(lines[classIndex], ".dark-theme {"); @@ -488,7 +498,72 @@ Deno.test("processColors - handles themes with class condition", async (t) => { "palette.base.primary", "theme.dark.background.primary", ]); - await assertSnapshot(t, css); + await assertSnapshot(t, combined); + await assertSnapshot(t, Array.from(resolveMap.entries())); +}); + +Deno.test("processColors - handles selector + atRule", async (t) => { + const config = defineConfig({ + colors: { + palette: { + value: { + base: { + value: { + primary: { hex: "#FF7F50" }, + }, + }, + }, + }, + theme: { + dark: { + value: { + background: { + value: { + primary: "var(--1)", + }, + variables: { + "1": "palette.base.primary", + }, + }, + }, + settings: { + selector: ".dark-theme", + atRule: "@media (prefers-color-scheme: dark)", + }, + }, + }, + }, + }); + + const { css, resolveMap } = processColors(config.colors); + const combined = combine(css); + const lines = getLines(combined); + + const mediaIndex = lines.findIndex((l) => + l.includes("@media (prefers-color-scheme: dark)") + ); + assertEquals(lines[mediaIndex], "@media (prefers-color-scheme: dark) {"); + + const classIndex = lines.findIndex((l, idx) => + idx > mediaIndex && l.includes(".dark-theme") + ); + assert(classIndex > mediaIndex, "selector should be inside the media query block"); + assertEquals(lines[classIndex].trim(), ".dark-theme {"); + + const varIndex = lines.findIndex((l, idx) => + idx > classIndex && l.includes("--theme-dark-background-primary:") + ); + assert( + varIndex > classIndex, + "theme variable should be inside the selector block within the media query", + ); + + assertEquals(Array.from(resolveMap.keys()), [ + "palette.base.primary", + "theme.dark.background.primary", + ]); + + await assertSnapshot(t, combined); await assertSnapshot(t, Array.from(resolveMap.entries())); }); @@ -528,7 +603,8 @@ Deno.test("processColors - handles theme variantNameOnly", async (t) => { }); const { css, resolveMap } = processColors(config.colors); - const lines = getLines(css); + const combined = combine(css); + const lines = getLines(combined); const primaryLine = lines.find((l) => l.includes("--primary:")); const secondaryLine = lines.find((l) => l.includes("--secondary:")); @@ -543,6 +619,6 @@ Deno.test("processColors - handles theme variantNameOnly", async (t) => { "theme.light.background.secondary", ]); - await assertSnapshot(t, css); + await assertSnapshot(t, combined); await assertSnapshot(t, Array.from(resolveMap.entries())); }); diff --git a/tests/helpers.ts b/tests/helpers.ts index 40ca300..ae82203 100644 --- a/tests/helpers.ts +++ b/tests/helpers.ts @@ -1,5 +1,10 @@ -export const getLines = (result: string) => - result.split("\n").filter((line) => line.trim()); +import type { Output } from "../src/lib.ts"; + +export const getLines = (result?: string) => + result ? result.split("\n").filter((line) => line.trim()) : []; + +export const combine = (css: Output["css"]) => + [css.root, css.outside].filter(Boolean).join("\n"); export const lineHasProp = (lines: string[]) => (prop: string) => lines.some((line) => line.includes(prop)); diff --git a/tests/primitive.test.ts b/tests/primitive.test.ts index 519c77f..06a68ad 100644 --- a/tests/primitive.test.ts +++ b/tests/primitive.test.ts @@ -65,7 +65,7 @@ Deno.test("processPrimitives - processes button with variables", async (t) => { "--button-small-padding: var(--spacing-size-2) var(--spacing-size-3);", ].join("\n"); - assertEquals(result.css, expected); + assertEquals(result.css.root, expected); await assertSnapshot(t, result.css); await assertSnapshot(t, Array.from(result.resolveMap.entries())); }); @@ -107,7 +107,7 @@ Deno.test("processPrimitives - processes buttons with settings", async (t) => { "--button-big-radius: 1rem;", ].join("\n"); - assertEquals(result.css, expected); + assertEquals(result.css.root, expected); await assertSnapshot(t, result.css); await assertSnapshot(t, Array.from(result.resolveMap.entries())); }); @@ -182,7 +182,7 @@ Deno.test("processPrimitives - references colors, gradients, and themes", async "--card-default-border-color: var(--palette-coral-50);", ].join("\n"); - assertEquals(result.css, expected); + assertEquals(result.css.root, expected); await assertSnapshot(t, result.css); await assertSnapshot(t, Array.from(result.resolveMap.entries())); }); @@ -226,7 +226,7 @@ Deno.test("processPrimitives - uses fluid spacing references", async (t) => { "/* box */", "--box-default-padding: var(--spacing_fluid-gap-gs-s) var(--spacing_fluid-gap-gs-m);", ].join("\n"); - assertEquals(primitives.css, expected); + assertEquals(primitives.css.root, expected); await assertSnapshot(t, primitives.css); await assertSnapshot(t, Array.from(primitives.resolveMap.entries())); }); diff --git a/tests/spacing.test.ts b/tests/spacing.test.ts index 2156e92..ca09609 100644 --- a/tests/spacing.test.ts +++ b/tests/spacing.test.ts @@ -21,7 +21,7 @@ Deno.test("processSpacing - generates correct spacing scale", async (t) => { "--spacing-size-3: 0.75rem;", "--spacing-size-s: 1rem;", ].join("\n"); - assertEquals(result.css, expected); + assertEquals(result.css.root, expected); assertEquals(Array.from(result.resolveMap.keys()), [ "spacing.custom.size.1", @@ -29,7 +29,7 @@ Deno.test("processSpacing - generates correct spacing scale", async (t) => { "spacing.custom.size.3", "spacing.custom.size.s", ]); - await assertSnapshot(t, result.css); + await assertSnapshot(t, result.css.root); await assertSnapshot(t, Array.from(result.resolveMap.entries())); }); @@ -58,7 +58,7 @@ Deno.test("processSpacing - handles settings", async (t) => { "--spacing-scale-lg: 0.75rem;", ].join("\n"); - assertEquals(result.css, expected); + assertEquals(result.css.root, expected); assertEquals(Array.from(result.resolveMap.keys()), [ "spacing.custom.size.1", @@ -66,7 +66,7 @@ Deno.test("processSpacing - handles settings", async (t) => { "spacing.custom.scale.md", "spacing.custom.scale.lg", ]); - await assertSnapshot(t, result.css); + await assertSnapshot(t, result.css.root); await assertSnapshot(t, Array.from(result.resolveMap.entries())); }); @@ -101,7 +101,7 @@ Deno.test("processSpacing - generates fluid spacing (prefix)", async (t) => { "--spacing_fluid-base-foo-s-m: clamp(0.25rem, -0.1667rem + 2.0833vw, 1.5rem);", "--spacing_fluid-base-foo-m-l: clamp(0.25rem, -0.6667rem + 4.5833vw, 3rem);", ].join("\n"); - assertEquals(css, expected); + assertEquals(css.root, expected); assertEquals(Array.from(resolveMap.keys()), [ "spacing_fluid.base@xs", @@ -113,7 +113,7 @@ Deno.test("processSpacing - generates fluid spacing (prefix)", async (t) => { "spacing_fluid.base@s-m", "spacing_fluid.base@m-l", ]); - await assertSnapshot(t, css); + await assertSnapshot(t, css.root); await assertSnapshot(t, Array.from(resolveMap.entries())); }); @@ -142,7 +142,7 @@ Deno.test("processSpacing - fluid without prefix falls back to scale name", asyn "--spacing_fluid-rhythm-xs-s: clamp(0rem, -0.4167rem + 2.0833vw, 1.25rem);", "--spacing_fluid-rhythm-s-m: clamp(0.125rem, -0.25rem + 1.875vw, 1.25rem);", ].join("\n"); - assertEquals(css, expected); + assertEquals(css.root, expected); assertEquals(Array.from(resolveMap.keys()), [ "spacing_fluid.rhythm@xs", @@ -151,7 +151,7 @@ Deno.test("processSpacing - fluid without prefix falls back to scale name", asyn "spacing_fluid.rhythm@xs-s", "spacing_fluid.rhythm@s-m", ]); - await assertSnapshot(t, css); + await assertSnapshot(t, css.root); await assertSnapshot(t, Array.from(resolveMap.entries())); }); @@ -189,7 +189,7 @@ Deno.test("processSpacing - combines fluid and custom spacing", async (t) => { "--spacing-gap-1: 4px;", "--spacing-gap-2: 8px;", ].join("\n"); - assertEquals(css, expected); + assertEquals(css.root, expected); assertEquals(Array.from(resolveMap.keys()), [ "spacing_fluid.base@xs", "spacing_fluid.base@s", @@ -199,6 +199,6 @@ Deno.test("processSpacing - combines fluid and custom spacing", async (t) => { "spacing.custom.gap.1", "spacing.custom.gap.2", ]); - await assertSnapshot(t, css); + await assertSnapshot(t, css.root); await assertSnapshot(t, Array.from(resolveMap.entries())); }); diff --git a/tests/typography.test.ts b/tests/typography.test.ts index 01b2d78..cd88e69 100644 --- a/tests/typography.test.ts +++ b/tests/typography.test.ts @@ -35,7 +35,7 @@ Deno.test("processTypography - generates correct CSS variables", async (t) => { "2xs", ]; const result = processTypography(config.typography); - const lines = getLines(result.css); + const lines = getLines(result.css.root); assertEquals(lines.length, expectedSizes.length); // Test a specific value for precision @@ -109,7 +109,7 @@ Deno.test("typography - can handle custom labels and prefixes", async (t) => { "h1", ]; const result = processTypography(config.typography); - const lines = getLines(result.css); + const lines = getLines(result.css.root); assertEquals(lines.length, expectedSizes.length); // Test a specific value for precision @@ -161,7 +161,7 @@ Deno.test("processTypography - can process weights", async (t) => { }); const result = processTypography(config.typography); - const lines = getLines(result.css); + const lines = getLines(result.css.root); assertEquals(lines.length, positiveSteps + negativeSteps + 1 + 1); // 1 for base size and 1 for weight assertEquals(