diff --git a/CHANGELOG.md b/CHANGELOG.md index a81d096..4a184ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,10 @@ Prefix the change with one of these keywords: ## [Unreleased] +### Added + +- New `_tokens.scss` output written alongside `tokens.css` in `srcDir`. Emits the same tokens as SCSS variables (`$prefix-segment-key: value;`) so consumers can reference tokens inside `@media` queries and other compile-time SCSS contexts where CSS custom properties can't be used. Always on — no opt-out needed. Mirrors the CSS output: same category grouping, same fluid `clamp()` handling, same `camelToKebab` conversion for layout keys, includes `cssOnly` entries (SCSS is another compile-time output). + ## [0.2.7] - 2026-04-14 ### Changed diff --git a/README.md b/README.md index ae49b4d..e5c9f61 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ c2b.config.json single source of truth │ npx c2b generate │ ├──► src/styles/tokens.css CSS custom properties + ├──► src/styles/_tokens.scss SCSS variables (for @media queries) ├──► src/styles/fonts.css @font-face declarations ├──► src/styles/base-styles.scss Base typography (zero-specificity) │ @@ -137,6 +138,7 @@ import { validateConfig, // Generators generateTokensCss, + generateTokensScss, generateTokensWpCss, generateThemeJson, generateFontsCss, diff --git a/dist/cli.js b/dist/cli.js index 25b55b5..c1b3047 100755 --- a/dist/cli.js +++ b/dist/cli.js @@ -37,6 +37,7 @@ try { if (dryRun) { const { loadConfig } = await import('./config.js'); const { generateTokensCss } = await import('./generators/tokens-css.js'); + const { generateTokensScss } = await import('./generators/tokens-scss.js'); const { generateTokensWpCss } = await import('./generators/tokens-wp-css.js'); const { generateThemeJson } = await import('./generators/theme-json.js'); const { generateIntegratePhp } = await import('./generators/integrate-php.js'); @@ -45,6 +46,8 @@ try { const config = loadConfig(configPath); console.log('=== tokens.css ==='); console.log(generateTokensCss(config)); + console.log('=== _tokens.scss ==='); + console.log(generateTokensScss(config)); const fontsCss = generateFontsCss(config); if (fontsCss) { console.log('=== fonts.css ==='); diff --git a/dist/cli.js.map b/dist/cli.js.map index 5eeb856..cd578d0 100644 --- a/dist/cli.js.map +++ b/dist/cli.js.map @@ -1 +1 @@ -{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/config.js b/dist/config.js index ad65cb9..31117be 100644 --- a/dist/config.js +++ b/dist/config.js @@ -319,8 +319,8 @@ function validateTokenGroup(category, group) { // ============================================================================ /** * Map baseStyles property names to their expected token category. - * Properties with no entry here have no associated token category and - * accept only CSS keywords or raw values. + * Properties with no entry here (e.g. `fontStyle`) have no associated token + * category and accept only CSS keywords (from CSS_KEYWORDS below) or raw values. */ const PROPERTY_CATEGORY = { fontFamily: 'fontFamily', diff --git a/dist/generators/tokens-scss.d.ts b/dist/generators/tokens-scss.d.ts new file mode 100644 index 0000000..5d6ee17 --- /dev/null +++ b/dist/generators/tokens-scss.d.ts @@ -0,0 +1,3 @@ +import type { C2bConfig } from '../types.js'; +export declare function generateTokensScss(config: C2bConfig): string; +//# sourceMappingURL=tokens-scss.d.ts.map \ No newline at end of file diff --git a/dist/generators/tokens-scss.d.ts.map b/dist/generators/tokens-scss.d.ts.map new file mode 100644 index 0000000..ae0b4c7 --- /dev/null +++ b/dist/generators/tokens-scss.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"tokens-scss.d.ts","sourceRoot":"","sources":["../../src/generators/tokens-scss.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAI7C,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAyB5D"} \ No newline at end of file diff --git a/dist/generators/tokens-scss.js b/dist/generators/tokens-scss.js new file mode 100644 index 0000000..f1ffa3f --- /dev/null +++ b/dist/generators/tokens-scss.js @@ -0,0 +1,24 @@ +import { CATEGORY_REGISTRY, CATEGORY_ORDER, DEFAULT_FLUID, camelToKebab } from '../types.js'; +import { buildFluidClamp } from './fluid.js'; +export function generateTokensScss(config) { + const lines = [ + '// Auto-generated by component2block — do not edit manually', + ]; + for (const category of CATEGORY_ORDER) { + const group = config.tokens[category]; + if (!group) + continue; + const def = CATEGORY_REGISTRY[category]; + lines.push(''); + lines.push(`// ${def.label}`); + for (const [key, entry] of Object.entries(group)) { + const cssKey = def.directMap ? camelToKebab(key) : key; + const varName = `$${config.prefix}-${def.cssSegment}-${cssKey}`; + const clampValue = entry.fluid ? buildFluidClamp(entry, config.fluid ?? DEFAULT_FLUID) : null; + lines.push(`${varName}: ${clampValue ?? entry.value};`); + } + } + lines.push(''); + return lines.join('\n'); +} +//# sourceMappingURL=tokens-scss.js.map \ No newline at end of file diff --git a/dist/generators/tokens-scss.js.map b/dist/generators/tokens-scss.js.map new file mode 100644 index 0000000..555578a --- /dev/null +++ b/dist/generators/tokens-scss.js.map @@ -0,0 +1 @@ +{"version":3,"file":"tokens-scss.js","sourceRoot":"","sources":["../../src/generators/tokens-scss.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC7F,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE7C,MAAM,UAAU,kBAAkB,CAAC,MAAiB;IAClD,MAAM,KAAK,GAAa;QACtB,6DAA6D;KAC9D,CAAC;IAEF,KAAK,MAAM,QAAQ,IAAI,cAAc,EAAE,CAAC;QACtC,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QACtC,IAAI,CAAC,KAAK;YAAE,SAAS;QAErB,MAAM,GAAG,GAAG,iBAAiB,CAAC,QAAQ,CAAC,CAAC;QAExC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC;QAE9B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACjD,MAAM,MAAM,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;YACvD,MAAM,OAAO,GAAG,IAAI,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,UAAU,IAAI,MAAM,EAAE,CAAC;YAChE,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,eAAe,CAAC,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,aAAa,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YAC9F,KAAK,CAAC,IAAI,CAAC,GAAG,OAAO,KAAK,UAAU,IAAI,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC;QAC1D,CAAC;IACH,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAEf,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"} \ No newline at end of file diff --git a/dist/index.d.ts b/dist/index.d.ts index 61559d4..5b9e047 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -1,5 +1,6 @@ export { loadConfig, validateConfig } from './config.js'; export { generateTokensCss } from './generators/tokens-css.js'; +export { generateTokensScss } from './generators/tokens-scss.js'; export { generateTokensWpCss } from './generators/tokens-wp-css.js'; export { generateThemeJson } from './generators/theme-json.js'; export { generateIntegratePhp } from './generators/integrate-php.js'; diff --git a/dist/index.d.ts.map b/dist/index.d.ts.map index 930199f..f66812a 100644 --- a/dist/index.d.ts.map +++ b/dist/index.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,YAAY,EAAE,SAAS,EAAE,cAAc,EAAE,UAAU,EAAE,UAAU,EAAE,aAAa,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEpI,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC9C;AAED,wBAAgB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,cAAc,CAsD1E"} \ No newline at end of file +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,YAAY,EAAE,SAAS,EAAE,cAAc,EAAE,UAAU,EAAE,UAAU,EAAE,aAAa,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEpI,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC9C;AAED,wBAAgB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,cAAc,CAyD1E"} \ No newline at end of file diff --git a/dist/index.js b/dist/index.js index a4da40f..de4a368 100644 --- a/dist/index.js +++ b/dist/index.js @@ -2,6 +2,7 @@ import { writeFileSync, mkdirSync } from 'node:fs'; import { dirname, resolve, join } from 'node:path'; import { loadConfig } from './config.js'; import { generateTokensCss } from './generators/tokens-css.js'; +import { generateTokensScss } from './generators/tokens-scss.js'; import { generateTokensWpCss } from './generators/tokens-wp-css.js'; import { generateThemeJson } from './generators/theme-json.js'; import { generateIntegratePhp } from './generators/integrate-php.js'; @@ -10,6 +11,7 @@ import { generateContentScss } from './generators/content-scss.js'; import { copyFontFiles } from './generators/copy-fonts.js'; export { loadConfig, validateConfig } from './config.js'; export { generateTokensCss } from './generators/tokens-css.js'; +export { generateTokensScss } from './generators/tokens-scss.js'; export { generateTokensWpCss } from './generators/tokens-wp-css.js'; export { generateThemeJson } from './generators/theme-json.js'; export { generateIntegratePhp } from './generators/integrate-php.js'; @@ -28,6 +30,8 @@ export function generate(configPath, cwd) { }; // Generate tokens.css for local dev (Storybook) write(join(config.srcDir, 'tokens.css'), generateTokensCss(config)); + // Generate _tokens.scss so consumers can use tokens in @media queries and other compile-time contexts + write(join(config.srcDir, '_tokens.scss'), generateTokensScss(config)); // Generate fonts.css if fontFace entries exist const fontsCss = generateFontsCss(config); if (fontsCss) { diff --git a/dist/index.js.map b/dist/index.js.map index 7d92b82..baced62 100644 --- a/dist/index.js.map +++ b/dist/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/src/cli.ts b/src/cli.ts index 558eeae..54beb93 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -43,6 +43,7 @@ try { if (dryRun) { const { loadConfig } = await import('./config.js'); const { generateTokensCss } = await import('./generators/tokens-css.js'); + const { generateTokensScss } = await import('./generators/tokens-scss.js'); const { generateTokensWpCss } = await import('./generators/tokens-wp-css.js'); const { generateThemeJson } = await import('./generators/theme-json.js'); const { generateIntegratePhp } = await import('./generators/integrate-php.js'); @@ -53,6 +54,8 @@ try { console.log('=== tokens.css ==='); console.log(generateTokensCss(config)); + console.log('=== _tokens.scss ==='); + console.log(generateTokensScss(config)); const fontsCss = generateFontsCss(config); if (fontsCss) { console.log('=== fonts.css ==='); diff --git a/src/generators/tokens-scss.ts b/src/generators/tokens-scss.ts new file mode 100644 index 0000000..793a5ab --- /dev/null +++ b/src/generators/tokens-scss.ts @@ -0,0 +1,30 @@ +import type { C2bConfig } from '../types.js'; +import { CATEGORY_REGISTRY, CATEGORY_ORDER, DEFAULT_FLUID, camelToKebab } from '../types.js'; +import { buildFluidClamp } from './fluid.js'; + +export function generateTokensScss(config: C2bConfig): string { + const lines: string[] = [ + '// Auto-generated by component2block — do not edit manually', + ]; + + for (const category of CATEGORY_ORDER) { + const group = config.tokens[category]; + if (!group) continue; + + const def = CATEGORY_REGISTRY[category]; + + lines.push(''); + lines.push(`// ${def.label}`); + + for (const [key, entry] of Object.entries(group)) { + const cssKey = def.directMap ? camelToKebab(key) : key; + const varName = `$${config.prefix}-${def.cssSegment}-${cssKey}`; + const clampValue = entry.fluid ? buildFluidClamp(entry, config.fluid ?? DEFAULT_FLUID) : null; + lines.push(`${varName}: ${clampValue ?? entry.value};`); + } + } + + lines.push(''); + + return lines.join('\n'); +} diff --git a/src/index.ts b/src/index.ts index c0f1044..743da5e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,7 @@ import { writeFileSync, mkdirSync } from 'node:fs'; import { dirname, resolve, join } from 'node:path'; import { loadConfig } from './config.js'; import { generateTokensCss } from './generators/tokens-css.js'; +import { generateTokensScss } from './generators/tokens-scss.js'; import { generateTokensWpCss } from './generators/tokens-wp-css.js'; import { generateThemeJson } from './generators/theme-json.js'; import { generateIntegratePhp } from './generators/integrate-php.js'; @@ -11,6 +12,7 @@ import { copyFontFiles } from './generators/copy-fonts.js'; export { loadConfig, validateConfig } from './config.js'; export { generateTokensCss } from './generators/tokens-css.js'; +export { generateTokensScss } from './generators/tokens-scss.js'; export { generateTokensWpCss } from './generators/tokens-wp-css.js'; export { generateThemeJson } from './generators/theme-json.js'; export { generateIntegratePhp } from './generators/integrate-php.js'; @@ -38,6 +40,9 @@ export function generate(configPath?: string, cwd?: string): GenerateResult { // Generate tokens.css for local dev (Storybook) write(join(config.srcDir, 'tokens.css'), generateTokensCss(config)); + // Generate _tokens.scss so consumers can use tokens in @media queries and other compile-time contexts + write(join(config.srcDir, '_tokens.scss'), generateTokensScss(config)); + // Generate fonts.css if fontFace entries exist const fontsCss = generateFontsCss(config); if (fontsCss) { diff --git a/tests/integration.test.ts b/tests/integration.test.ts index 9f0c0fd..b228878 100644 --- a/tests/integration.test.ts +++ b/tests/integration.test.ts @@ -43,10 +43,11 @@ describe('integration: generate() — default (locked)', () => { it('generates expected files without tokens.wp.css', () => { const result = generate(CONFIG_PATH, TEST_DIR); - expect(result.files).toHaveLength(4); + expect(result.files).toHaveLength(5); const paths = result.files.map((f) => f.path); expect(paths).toContain('src/tokens.css'); + expect(paths).toContain('src/_tokens.scss'); expect(paths).toContain('out/wp/tokens.css'); expect(paths).not.toContain('out/wp/tokens.wp.css'); expect(paths).toContain('out/wp/theme-inttest.json'); @@ -286,10 +287,11 @@ describe('integration: generate() — themeable', () => { it('generates tokens.wp.css when themeable is true', () => { const result = generate(WP_CONFIG_PATH, WP_TEST_DIR); - expect(result.files).toHaveLength(5); + expect(result.files).toHaveLength(6); const paths = result.files.map((f) => f.path); expect(paths).toContain('src/tokens.css'); + expect(paths).toContain('src/_tokens.scss'); expect(paths).toContain('out/wp/tokens.css'); expect(paths).toContain('out/wp/tokens.wp.css'); expect(paths).toContain('out/wp/theme-inttest.json'); diff --git a/tests/tokens-scss.test.ts b/tests/tokens-scss.test.ts new file mode 100644 index 0000000..33807a8 --- /dev/null +++ b/tests/tokens-scss.test.ts @@ -0,0 +1,182 @@ +import { describe, it, expect } from 'vitest'; +import { generateTokensScss } from '../src/generators/tokens-scss.js'; +import type { C2bConfig } from '../src/types.js'; + +const config: C2bConfig = { + prefix: 'test', + srcDir: 'src/styles', + themeDir: 'dist/wp', + bundleFonts: false, + tokens: { + colorPalette: { + primary: { value: '#0073aa', name: 'Primary', slug: 'primary' }, + 'primary-hover': { value: '#005a87' }, + muted: { value: '#999999', cssOnly: true }, + }, + spacing: { + md: { value: '1rem', slug: '40', name: 'Medium' }, + }, + fontFamily: { + base: { value: 'sans-serif', name: 'Sans', slug: 'body' }, + }, + fontSize: { + sm: { value: '0.875rem', slug: 'small', name: 'Small' }, + xs: { value: '0.75rem' }, + }, + zIndex: { + modal: { value: '300' }, + }, + }, +}; + +describe('generateTokensScss', () => { + const output = generateTokensScss(config); + + it('includes the auto-generated header', () => { + expect(output).toContain('// Auto-generated by component2block'); + }); + + it('generates correct color variables', () => { + expect(output).toContain('$test-color-primary: #0073aa;'); + expect(output).toContain('$test-color-primary-hover: #005a87;'); + }); + + it('includes cssOnly tokens (SCSS is another compile-time output)', () => { + expect(output).toContain('$test-color-muted: #999999;'); + }); + + it('generates correct spacing variables', () => { + expect(output).toContain('$test-spacing-md: 1rem;'); + }); + + it('generates correct font-family variables', () => { + expect(output).toContain('$test-font-family-base: sans-serif;'); + }); + + it('generates correct font-size variables', () => { + expect(output).toContain('$test-font-size-sm: 0.875rem;'); + expect(output).toContain('$test-font-size-xs: 0.75rem;'); + }); + + it('generates correct z-index variables', () => { + expect(output).toContain('$test-z-modal: 300;'); + }); + + it('groups tokens by category with line comments', () => { + expect(output).toContain('// Colors'); + expect(output).toContain('// Spacing'); + expect(output).toContain('// Z-Index'); + }); + + it('does not wrap in :root or any selector', () => { + expect(output).not.toContain(':root'); + expect(output).not.toContain('{'); + expect(output).not.toContain('}'); + }); + + it('uses single dash separators (not CSS custom property --)', () => { + expect(output).not.toContain('--'); + expect(output).not.toMatch(/^\$[\w-]+--/m); + }); + + it('uses hardcoded values for all tokens (no var() wrappers)', () => { + expect(output).not.toContain('var('); + }); +}); + +describe('generateTokensScss — layout tokens', () => { + const layoutConfig: C2bConfig = { + prefix: 'test', + srcDir: 'src/styles', + themeDir: 'dist/wp', + bundleFonts: false, + tokens: { + layout: { + contentSize: { value: '645px' }, + wideSize: { value: '1340px' }, + }, + }, + }; + + const output = generateTokensScss(layoutConfig); + + it('applies camelToKebab conversion for layout keys', () => { + expect(output).toContain('$test-layout-content-size: 645px;'); + expect(output).toContain('$test-layout-wide-size: 1340px;'); + }); + + it('groups layout tokens with a comment', () => { + expect(output).toContain('// Layout'); + }); +}); + +describe('generateTokensScss — fluid font sizes', () => { + const fluidConfig: C2bConfig = { + prefix: 'test', + srcDir: 'src/styles', + themeDir: 'dist/wp', + bundleFonts: false, + tokens: { + fontSize: { + medium: { + value: '1.125rem', + slug: 'medium', + name: 'Medium', + fluid: { min: '0.875rem', max: '1.125rem' }, + }, + static: { + value: '0.75rem', + slug: 'static', + name: 'Static', + }, + }, + }, + }; + + const output = generateTokensScss(fluidConfig); + + it('generates clamp() for fluid font sizes identical to tokens.css', () => { + expect(output).toContain( + '$test-font-size-medium: clamp(0.875rem, 0.875rem + ((1vw - 0.2rem) * 0.313), 1.125rem);' + ); + }); + + it('uses static value when no fluid is defined', () => { + expect(output).toContain('$test-font-size-static: 0.75rem;'); + }); +}); + +describe('generateTokensScss — snapshot', () => { + it('matches the expected file shape', () => { + const snapshotConfig: C2bConfig = { + prefix: 'rds', + srcDir: 'src/styles', + themeDir: 'dist/wp', + bundleFonts: false, + tokens: { + colorPalette: { + primary: { value: '#e91c24' }, + secondary: { value: '#426587' }, + }, + layout: { + contentSize: { value: '1024px' }, + wideSize: { value: '1280px' }, + }, + }, + }; + expect(generateTokensScss(snapshotConfig)).toBe( + [ + '// Auto-generated by component2block — do not edit manually', + '', + '// Colors', + '$rds-color-primary: #e91c24;', + '$rds-color-secondary: #426587;', + '', + '// Layout', + '$rds-layout-content-size: 1024px;', + '$rds-layout-wide-size: 1280px;', + '', + ].join('\n'), + ); + }); +});