@furystack/shades
Version:
A lightweight UI framework for FuryStack with JSX support
184 lines (162 loc) • 6.54 kB
text/typescript
import { describe, expect, it } from 'vitest'
import { camelToKebab, generateCSS, generateCSSRule, isSelectorKey, propertiesToCSSString } from './css-generator.js'
import type { CSSProperties } from './models/css-object.js'
describe('css-generator', () => {
describe('camelToKebab', () => {
it('should convert camelCase to kebab-case', () => {
expect(camelToKebab('backgroundColor')).toBe('background-color')
expect(camelToKebab('fontSize')).toBe('font-size')
expect(camelToKebab('borderTopLeftRadius')).toBe('border-top-left-radius')
})
it('should handle single word properties', () => {
expect(camelToKebab('color')).toBe('color')
expect(camelToKebab('margin')).toBe('margin')
})
it('should handle empty string', () => {
expect(camelToKebab('')).toBe('')
})
})
describe('isSelectorKey', () => {
it('should return true for selector keys starting with &', () => {
expect(isSelectorKey('&:hover')).toBe(true)
expect(isSelectorKey('&:active')).toBe(true)
expect(isSelectorKey('& .className')).toBe(true)
expect(isSelectorKey('& > div')).toBe(true)
})
it('should return false for regular CSS property keys', () => {
expect(isSelectorKey('color')).toBe(false)
expect(isSelectorKey('backgroundColor')).toBe(false)
expect(isSelectorKey('fontSize')).toBe(false)
})
})
describe('propertiesToCSSString', () => {
it('should convert CSS properties object to CSS string', () => {
const result = propertiesToCSSString({
color: 'red',
backgroundColor: 'blue',
})
expect(result).toBe('color: red; background-color: blue')
})
it('should skip undefined and null values', () => {
const result = propertiesToCSSString({
color: 'red',
backgroundColor: undefined,
})
expect(result).toBe('color: red')
})
it('should skip empty string values', () => {
const result = propertiesToCSSString({
color: 'red',
backgroundColor: '',
})
expect(result).toBe('color: red')
})
it('should return empty string for empty object', () => {
const result = propertiesToCSSString({})
expect(result).toBe('')
})
it('should ignore selector keys', () => {
// Type assertion needed to test mixed object with selectors
const mixedObject = {
color: 'red',
'&:hover': { color: 'blue' },
}
const result = propertiesToCSSString(mixedObject)
expect(result).toBe('color: red')
})
it('should filter out non-string values', () => {
// Type assertion to test edge case with non-string values
const mixedObject = {
color: 'red',
opacity: 0.5, // number - should be filtered
display: 'flex',
hidden: true, // boolean - should be filtered
}
const result = propertiesToCSSString(mixedObject as unknown as CSSProperties)
expect(result).toBe('color: red; display: flex')
})
})
describe('generateCSSRule', () => {
it('should generate a complete CSS rule', () => {
const result = generateCSSRule('my-component', {
color: 'red',
padding: '10px',
})
expect(result).toBe('my-component { color: red; padding: 10px; }')
})
it('should return empty string for empty properties', () => {
const result = generateCSSRule('my-component', {})
expect(result).toBe('')
})
})
describe('generateCSS', () => {
it('should generate CSS for base properties only', () => {
const result = generateCSS('my-component', {
color: 'red',
padding: '10px',
})
expect(result).toBe('my-component { color: red; padding: 10px; }')
})
it('should generate CSS with pseudo-selectors', () => {
const result = generateCSS('my-component', {
color: 'red',
'&:hover': { color: 'blue' },
})
expect(result).toContain('my-component { color: red; }')
expect(result).toContain('my-component:hover { color: blue; }')
})
it('should generate CSS with nested class selectors', () => {
const result = generateCSS('my-component', {
padding: '10px',
'& .inner': { fontWeight: 'bold' },
})
expect(result).toContain('my-component { padding: 10px; }')
expect(result).toContain('my-component .inner { font-weight: bold; }')
})
it('should generate CSS with child selectors', () => {
const result = generateCSS('my-component', {
display: 'flex',
'& > div': { margin: '5px' },
})
expect(result).toContain('my-component { display: flex; }')
expect(result).toContain('my-component > div { margin: 5px; }')
})
it('should handle multiple pseudo-selectors', () => {
const result = generateCSS('my-button', {
backgroundColor: 'blue',
'&:hover': { backgroundColor: 'darkblue' },
'&:active': { backgroundColor: 'navy' },
'&:disabled': { opacity: '0.5' },
})
expect(result).toContain('my-button { background-color: blue; }')
expect(result).toContain('my-button:hover { background-color: darkblue; }')
expect(result).toContain('my-button:active { background-color: navy; }')
expect(result).toContain('my-button:disabled { opacity: 0.5; }')
})
it('should handle empty css object', () => {
const result = generateCSS('my-component', {})
expect(result).toBe('')
})
it('should handle css object with only selectors', () => {
const result = generateCSS('my-component', {
'&:hover': { color: 'blue' },
})
expect(result).toBe('my-component:hover { color: blue; }')
})
it('should skip selector keys with non-object values', () => {
// Type assertion to test edge case with invalid selector values
const cssObject = {
color: 'red',
'&:hover': 'invalid', // string instead of object - should be skipped
'&:active': null, // null - should be skipped
'&:focus': { backgroundColor: 'blue' }, // valid - should be included
}
const result = generateCSS('my-component', cssObject as unknown as Parameters<typeof generateCSS>[1])
expect(result).toContain('my-component { color: red; }')
expect(result).toContain('my-component:focus { background-color: blue; }')
expect(result).not.toContain('invalid')
expect(result).not.toContain(':hover')
expect(result).not.toContain(':active')
})
})
})