UNPKG

@furystack/shades-common-components

Version:

Common UI components for FuryStack Shades

423 lines 22.5 kB
import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { buildTransition, cssVariableTheme, getCssVariable, removeCssVariable, setCssVariable, useThemeCssVariables, } from './css-variable-theme.js'; describe('css-variable-theme', () => { describe('cssVariableTheme', () => { it('should have a name property', () => { expect(cssVariableTheme.name).toBe('css-variable-theme'); }); it('should have text properties with CSS variable references', () => { expect(cssVariableTheme.text.primary).toBe('var(--shades-theme-text-primary)'); expect(cssVariableTheme.text.secondary).toBe('var(--shades-theme-text-secondary)'); expect(cssVariableTheme.text.disabled).toBe('var(--shades-theme-text-disabled)'); }); it('should have background properties with CSS variable references', () => { expect(cssVariableTheme.background.default).toBe('var(--shades-theme-background-default)'); expect(cssVariableTheme.background.paper).toBe('var(--shades-theme-background-paper)'); }); it('should have palette with color variants', () => { expect(cssVariableTheme.palette.primary.main).toBe('var(--shades-theme-palette-primary-main)'); expect(cssVariableTheme.palette.error.main).toBe('var(--shades-theme-palette-error-main)'); }); it('should have action properties with CSS variable references', () => { expect(cssVariableTheme.action.hoverBackground).toBe('var(--shades-theme-action-hover-background)'); expect(cssVariableTheme.action.selectedBackground).toBe('var(--shades-theme-action-selected-background)'); expect(cssVariableTheme.action.activeBackground).toBe('var(--shades-theme-action-active-background)'); expect(cssVariableTheme.action.focusRing).toBe('var(--shades-theme-action-focus-ring)'); expect(cssVariableTheme.action.focusOutline).toBe('var(--shades-theme-action-focus-outline)'); expect(cssVariableTheme.action.disabledOpacity).toBe('var(--shades-theme-action-disabled-opacity)'); expect(cssVariableTheme.action.backdrop).toBe('var(--shades-theme-action-backdrop)'); expect(cssVariableTheme.action.subtleBorder).toBe('var(--shades-theme-action-subtle-border)'); }); it('should have shape properties with CSS variable references', () => { expect(cssVariableTheme.shape.borderRadius.xs).toBe('var(--shades-theme-shape-border-radius-xs)'); expect(cssVariableTheme.shape.borderRadius.sm).toBe('var(--shades-theme-shape-border-radius-sm)'); expect(cssVariableTheme.shape.borderRadius.md).toBe('var(--shades-theme-shape-border-radius-md)'); expect(cssVariableTheme.shape.borderRadius.lg).toBe('var(--shades-theme-shape-border-radius-lg)'); expect(cssVariableTheme.shape.borderRadius.full).toBe('var(--shades-theme-shape-border-radius-full)'); }); it('should have shadow properties with CSS variable references', () => { expect(cssVariableTheme.shadows.none).toBe('var(--shades-theme-shadows-none)'); expect(cssVariableTheme.shadows.sm).toBe('var(--shades-theme-shadows-sm)'); expect(cssVariableTheme.shadows.md).toBe('var(--shades-theme-shadows-md)'); expect(cssVariableTheme.shadows.lg).toBe('var(--shades-theme-shadows-lg)'); expect(cssVariableTheme.shadows.xl).toBe('var(--shades-theme-shadows-xl)'); }); it('should have typography properties with CSS variable references', () => { expect(cssVariableTheme.typography.fontFamily).toBe('var(--shades-theme-typography-font-family)'); expect(cssVariableTheme.typography.fontSize.xs).toBe('var(--shades-theme-typography-font-size-xs)'); expect(cssVariableTheme.typography.fontSize.md).toBe('var(--shades-theme-typography-font-size-md)'); expect(cssVariableTheme.typography.fontSize.xxl).toBe('var(--shades-theme-typography-font-size-xxl)'); expect(cssVariableTheme.typography.fontSize.xxxl).toBe('var(--shades-theme-typography-font-size-xxxl)'); expect(cssVariableTheme.typography.fontSize.xxxxl).toBe('var(--shades-theme-typography-font-size-xxxxl)'); expect(cssVariableTheme.typography.fontWeight.normal).toBe('var(--shades-theme-typography-font-weight-normal)'); expect(cssVariableTheme.typography.fontWeight.bold).toBe('var(--shades-theme-typography-font-weight-bold)'); expect(cssVariableTheme.typography.lineHeight.tight).toBe('var(--shades-theme-typography-line-height-tight)'); expect(cssVariableTheme.typography.lineHeight.normal).toBe('var(--shades-theme-typography-line-height-normal)'); expect(cssVariableTheme.typography.textShadow).toBe('var(--shades-theme-typography-text-shadow)'); }); it('should have transition properties with CSS variable references', () => { expect(cssVariableTheme.transitions.duration.fast).toBe('var(--shades-theme-transitions-duration-fast)'); expect(cssVariableTheme.transitions.duration.normal).toBe('var(--shades-theme-transitions-duration-normal)'); expect(cssVariableTheme.transitions.duration.slow).toBe('var(--shades-theme-transitions-duration-slow)'); expect(cssVariableTheme.transitions.easing.default).toBe('var(--shades-theme-transitions-easing-default)'); expect(cssVariableTheme.transitions.easing.easeOut).toBe('var(--shades-theme-transitions-easing-ease-out)'); expect(cssVariableTheme.transitions.easing.easeInOut).toBe('var(--shades-theme-transitions-easing-ease-in-out)'); }); it('should have spacing properties with CSS variable references', () => { expect(cssVariableTheme.spacing.xs).toBe('var(--shades-theme-spacing-xs)'); expect(cssVariableTheme.spacing.sm).toBe('var(--shades-theme-spacing-sm)'); expect(cssVariableTheme.spacing.md).toBe('var(--shades-theme-spacing-md)'); expect(cssVariableTheme.spacing.lg).toBe('var(--shades-theme-spacing-lg)'); expect(cssVariableTheme.spacing.xl).toBe('var(--shades-theme-spacing-xl)'); }); }); describe('setCssVariable', () => { let testElement; beforeEach(() => { testElement = document.createElement('div'); document.body.appendChild(testElement); }); afterEach(() => { testElement.remove(); }); it('should set CSS variable on element', () => { setCssVariable('--test-color', 'red', testElement); expect(testElement.style.getPropertyValue('--test-color')).toBe('red'); }); it('should handle var() wrapper in key name', () => { setCssVariable('var(--test-padding)', '10px', testElement); expect(testElement.style.getPropertyValue('--test-padding')).toBe('10px'); }); it('should set multiple CSS variables on same element', () => { setCssVariable('--color-a', 'blue', testElement); setCssVariable('--color-b', 'green', testElement); expect(testElement.style.getPropertyValue('--color-a')).toBe('blue'); expect(testElement.style.getPropertyValue('--color-b')).toBe('green'); }); it('should override existing CSS variable', () => { setCssVariable('--test-value', 'first', testElement); setCssVariable('--test-value', 'second', testElement); expect(testElement.style.getPropertyValue('--test-value')).toBe('second'); }); }); describe('removeCssVariable', () => { let testElement; beforeEach(() => { testElement = document.createElement('div'); document.body.appendChild(testElement); }); afterEach(() => { testElement.remove(); }); it('should remove a CSS variable from element', () => { testElement.style.setProperty('--test-color', 'red'); expect(testElement.style.getPropertyValue('--test-color')).toBe('red'); removeCssVariable('--test-color', testElement); expect(testElement.style.getPropertyValue('--test-color')).toBe(''); }); it('should handle var() wrapper in key name', () => { testElement.style.setProperty('--test-padding', '10px'); removeCssVariable('var(--test-padding)', testElement); expect(testElement.style.getPropertyValue('--test-padding')).toBe(''); }); it('should not throw when removing a non-existent variable', () => { expect(() => removeCssVariable('--non-existent', testElement)).not.toThrow(); }); }); describe('getCssVariable', () => { let testElement; beforeEach(() => { testElement = document.createElement('div'); document.body.appendChild(testElement); }); afterEach(() => { testElement.remove(); }); it('should get CSS variable from element', () => { testElement.style.setProperty('--test-color', 'red'); const result = getCssVariable('--test-color', testElement); expect(result).toBe('red'); }); it('should handle var() wrapper in key name', () => { testElement.style.setProperty('--test-padding', '20px'); const result = getCssVariable('var(--test-padding)', testElement); expect(result).toBe('20px'); }); it('should return empty string for non-existent variable', () => { const result = getCssVariable('--non-existent', testElement); expect(result).toBe(''); }); }); describe('useThemeCssVariables', () => { let root; beforeEach(() => { root = document.documentElement; }); afterEach(() => { root.style.cssText = ''; }); it('should set text color CSS variables from theme', () => { useThemeCssVariables({ text: { primary: '#ffffff', secondary: '#cccccc', }, }); expect(root.style.getPropertyValue('--shades-theme-text-primary')).toBe('#ffffff'); expect(root.style.getPropertyValue('--shades-theme-text-secondary')).toBe('#cccccc'); }); it('should set background CSS variables from theme', () => { useThemeCssVariables({ background: { default: '#000000', paper: '#111111', }, }); expect(root.style.getPropertyValue('--shades-theme-background-default')).toBe('#000000'); expect(root.style.getPropertyValue('--shades-theme-background-paper')).toBe('#111111'); }); it('should set button CSS variables from theme', () => { useThemeCssVariables({ button: { active: '#ff0000', hover: '#00ff00', }, }); expect(root.style.getPropertyValue('--shades-theme-button-active')).toBe('#ff0000'); expect(root.style.getPropertyValue('--shades-theme-button-hover')).toBe('#00ff00'); }); it('should set deeply nested palette CSS variables from theme', () => { useThemeCssVariables({ palette: { primary: { main: '#1976d2', light: '#42a5f5', dark: '#1565c0', }, error: { main: '#d32f2f', }, }, }); expect(root.style.getPropertyValue('--shades-theme-palette-primary-main')).toBe('#1976d2'); expect(root.style.getPropertyValue('--shades-theme-palette-primary-light')).toBe('#42a5f5'); expect(root.style.getPropertyValue('--shades-theme-palette-primary-dark')).toBe('#1565c0'); expect(root.style.getPropertyValue('--shades-theme-palette-error-main')).toBe('#d32f2f'); }); it('should set divider CSS variable from theme', () => { useThemeCssVariables({ divider: 'rgba(255, 255, 255, 0.12)', }); expect(root.style.getPropertyValue('--shades-theme-divider')).toBe('rgba(255, 255, 255, 0.12)'); }); it('should handle partial theme with mixed nesting levels', () => { useThemeCssVariables({ text: { primary: '#fff', }, divider: '#333', palette: { success: { main: '#2e7d32', }, }, }); expect(root.style.getPropertyValue('--shades-theme-text-primary')).toBe('#fff'); expect(root.style.getPropertyValue('--shades-theme-divider')).toBe('#333'); expect(root.style.getPropertyValue('--shades-theme-palette-success-main')).toBe('#2e7d32'); }); it('should allow overriding previously set CSS variables', () => { useThemeCssVariables({ text: { primary: '#aaa', }, }); expect(root.style.getPropertyValue('--shades-theme-text-primary')).toBe('#aaa'); useThemeCssVariables({ text: { primary: '#bbb', }, }); expect(root.style.getPropertyValue('--shades-theme-text-primary')).toBe('#bbb'); }); it('should remove stale CSS variables not present in the new theme', () => { useThemeCssVariables({ text: { primary: '#fff', secondary: '#ccc', disabled: '#999', }, background: { default: '#000', paper: '#111', paperImage: 'url(texture.png)', }, }); expect(root.style.getPropertyValue('--shades-theme-text-primary')).toBe('#fff'); expect(root.style.getPropertyValue('--shades-theme-text-secondary')).toBe('#ccc'); expect(root.style.getPropertyValue('--shades-theme-background-paper-image')).toBe('url(texture.png)'); useThemeCssVariables({ text: { primary: '#eee', }, background: { default: '#000', paper: '#222', }, }); expect(root.style.getPropertyValue('--shades-theme-text-primary')).toBe('#eee'); expect(root.style.getPropertyValue('--shades-theme-text-secondary')).toBe(''); expect(root.style.getPropertyValue('--shades-theme-text-disabled')).toBe(''); expect(root.style.getPropertyValue('--shades-theme-background-default')).toBe('#000'); expect(root.style.getPropertyValue('--shades-theme-background-paper')).toBe('#222'); expect(root.style.getPropertyValue('--shades-theme-background-paper-image')).toBe(''); }); it('should remove all nested CSS variables when a whole section is omitted', () => { useThemeCssVariables({ palette: { primary: { light: '#aaa', lightContrast: '#000', main: '#bbb', mainContrast: '#000', dark: '#ccc', darkContrast: '#fff', }, }, spacing: { xs: '4px', sm: '8px', md: '16px', lg: '24px', xl: '32px', }, }); expect(root.style.getPropertyValue('--shades-theme-palette-primary-main')).toBe('#bbb'); expect(root.style.getPropertyValue('--shades-theme-spacing-md')).toBe('16px'); useThemeCssVariables({ spacing: { xs: '2px', sm: '4px', md: '8px', lg: '16px', xl: '24px', }, }); expect(root.style.getPropertyValue('--shades-theme-palette-primary-main')).toBe(''); expect(root.style.getPropertyValue('--shades-theme-palette-primary-light')).toBe(''); expect(root.style.getPropertyValue('--shades-theme-spacing-md')).toBe('8px'); }); it('should set action CSS variables from theme', () => { useThemeCssVariables({ action: { hoverBackground: 'rgba(255, 255, 255, 0.08)', focusRing: '0 0 0 3px rgba(255, 255, 255, 0.15)', }, }); expect(root.style.getPropertyValue('--shades-theme-action-hover-background')).toBe('rgba(255, 255, 255, 0.08)'); expect(root.style.getPropertyValue('--shades-theme-action-focus-ring')).toBe('0 0 0 3px rgba(255, 255, 255, 0.15)'); }); it('should set shape CSS variables from theme', () => { useThemeCssVariables({ shape: { borderRadius: { md: '8px', full: '50%', }, }, }); expect(root.style.getPropertyValue('--shades-theme-shape-border-radius-md')).toBe('8px'); expect(root.style.getPropertyValue('--shades-theme-shape-border-radius-full')).toBe('50%'); }); it('should set typography CSS variables from theme', () => { useThemeCssVariables({ typography: { fontFamily: 'monospace', fontSize: { md: '14px', }, fontWeight: { bold: '700', }, }, }); expect(root.style.getPropertyValue('--shades-theme-typography-font-family')).toBe('monospace'); expect(root.style.getPropertyValue('--shades-theme-typography-font-size-md')).toBe('14px'); expect(root.style.getPropertyValue('--shades-theme-typography-font-weight-bold')).toBe('700'); }); it('should set transition CSS variables from theme', () => { useThemeCssVariables({ transitions: { duration: { fast: '0.15s', }, easing: { default: 'ease', }, }, }); expect(root.style.getPropertyValue('--shades-theme-transitions-duration-fast')).toBe('0.15s'); expect(root.style.getPropertyValue('--shades-theme-transitions-easing-default')).toBe('ease'); }); it('should set spacing CSS variables from theme', () => { useThemeCssVariables({ spacing: { xs: '4px', md: '16px', xl: '32px', }, }); expect(root.style.getPropertyValue('--shades-theme-spacing-xs')).toBe('4px'); expect(root.style.getPropertyValue('--shades-theme-spacing-md')).toBe('16px'); expect(root.style.getPropertyValue('--shades-theme-spacing-xl')).toBe('32px'); }); it('should set CSS variables on a custom root element instead of :root', () => { const customRoot = document.createElement('div'); document.body.appendChild(customRoot); useThemeCssVariables({ text: { primary: '#ff0000' }, divider: '#00ff00', }, customRoot); expect(customRoot.style.getPropertyValue('--shades-theme-text-primary')).toBe('#ff0000'); expect(customRoot.style.getPropertyValue('--shades-theme-divider')).toBe('#00ff00'); expect(root.style.getPropertyValue('--shades-theme-text-primary')).toBe(''); customRoot.remove(); }); it('should scope nested palette variables to custom root element', () => { const customRoot = document.createElement('div'); document.body.appendChild(customRoot); useThemeCssVariables({ palette: { primary: { main: '#1976d2', mainContrast: '#ffffff', }, }, }, customRoot); expect(customRoot.style.getPropertyValue('--shades-theme-palette-primary-main')).toBe('#1976d2'); expect(customRoot.style.getPropertyValue('--shades-theme-palette-primary-main-contrast')).toBe('#ffffff'); expect(root.style.getPropertyValue('--shades-theme-palette-primary-main')).toBe(''); customRoot.remove(); }); }); describe('buildTransition', () => { it('should build a single transition string', () => { expect(buildTransition(['background', '0.2s', 'ease'])).toBe('background 0.2s ease'); }); it('should join multiple transitions with commas', () => { expect(buildTransition(['background', '0.2s', 'ease'], ['opacity', '0.15s', 'ease-out'])).toBe('background 0.2s ease, opacity 0.15s ease-out'); }); it('should handle three or more transitions', () => { const result = buildTransition(['background', '0.2s', 'ease'], ['color', '0.3s', 'linear'], ['transform', '0.1s', 'ease-in-out']); expect(result).toBe('background 0.2s ease, color 0.3s linear, transform 0.1s ease-in-out'); }); it('should work with CSS variable references', () => { expect(buildTransition([ 'background', cssVariableTheme.transitions.duration.normal, cssVariableTheme.transitions.easing.default, ])).toBe('background var(--shades-theme-transitions-duration-normal) var(--shades-theme-transitions-easing-default)'); }); }); }); //# sourceMappingURL=css-variable-theme.spec.js.map