UNPKG

@ordojs/core

Version:

Core compiler and runtime for OrdoJS framework

368 lines (299 loc) 12.4 kB
/** * @fileoverview Tests for CSS-in-JS Compiler */ import { describe, expect, it } from 'vitest'; import type { CSSFunctionExpression, CSSObjectExpression, CSSTemplateExpression } from './css-in-js-compiler.js'; import { OrdoJSCSSInJSCompiler } from './css-in-js-compiler.js'; describe('OrdoJSCSSInJSCompiler', () => { let compiler: OrdoJSCSSInJSCompiler; beforeEach(() => { compiler = new OrdoJSCSSInJSCompiler(); }); describe('CSS Object Compilation', () => { it('should compile simple CSS object', () => { const cssObject: CSSObjectExpression = { type: 'CSSObject', properties: [ { key: 'color', value: 'red', computed: false }, { key: 'fontSize', value: '16px', computed: false } ] }; const result = compiler.compile(cssObject, 'TestComponent'); expect(result.css).toContain('color: red'); expect(result.css).toContain('font-size: 16px'); expect(result.className).toMatch(/^ordojs-css-testcomponent-\d+$/); expect(result.styleBlock.rules).toHaveLength(1); expect(result.styleBlock.rules[0].declarations).toHaveLength(2); }); it('should convert camelCase properties to kebab-case', () => { const cssObject: CSSObjectExpression = { type: 'CSSObject', properties: [ { key: 'backgroundColor', value: 'blue', computed: false }, { key: 'borderRadius', value: '5px', computed: false }, { key: 'marginTop', value: '10px', computed: false } ] }; const result = compiler.compile(cssObject); expect(result.css).toContain('background-color: blue'); expect(result.css).toContain('border-radius: 5px'); expect(result.css).toContain('margin-top: 10px'); }); it('should handle numeric values by adding px unit', () => { const cssObject: CSSObjectExpression = { type: 'CSSObject', properties: [ { key: 'width', value: 100, computed: false }, { key: 'height', value: 200, computed: false }, { key: 'zIndex', value: 1, computed: false } ] }; const result = compiler.compile(cssObject); expect(result.css).toContain('width: 100px'); expect(result.css).toContain('height: 200px'); expect(result.css).toContain('z-index: 1px'); }); it('should handle nested CSS objects', () => { const cssObject: CSSObjectExpression = { type: 'CSSObject', properties: [ { key: 'color', value: 'red', computed: false }, { key: 'nested', value: { type: 'CSSObject', properties: [ { key: 'fontSize', value: '14px', computed: false } ] }, computed: false } ] }; const result = compiler.compile(cssObject); expect(result.css).toContain('color: red'); expect(result.css).toContain('font-size: 14px'); }); }); describe('CSS Template Compilation', () => { it('should compile CSS template strings', () => { const cssTemplate: CSSTemplateExpression = { type: 'CSSTemplate', template: 'color: red; font-size: 16px; background: blue;', expressions: [] }; const result = compiler.compile(cssTemplate); expect(result.css).toContain('color: red'); expect(result.css).toContain('font-size: 16px'); expect(result.css).toContain('background: blue'); expect(result.styleBlock.rules[0].declarations).toHaveLength(3); }); it('should handle template expressions', () => { const cssTemplate: CSSTemplateExpression = { type: 'CSSTemplate', template: 'color: ${0}; font-size: ${1};', expressions: [ { type: 'Expression', expressionType: 'LITERAL' as any, value: 'red', range: { start: { line: 1, column: 0, offset: 0 }, end: { line: 1, column: 0, offset: 0 } } }, { type: 'Expression', expressionType: 'LITERAL' as any, value: '16px', range: { start: { line: 1, column: 0, offset: 0 }, end: { line: 1, column: 0, offset: 0 } } } ] }; const result = compiler.compile(cssTemplate); // Template expressions are currently replaced with comments expect(result.css).toContain('/* expression 0 */'); expect(result.css).toContain('/* expression 1 */'); }); it('should handle malformed CSS gracefully', () => { const cssTemplate: CSSTemplateExpression = { type: 'CSSTemplate', template: 'color red; font-size; background: blue', expressions: [] }; const result = compiler.compile(cssTemplate); // Should only parse valid declarations expect(result.css).toContain('background: blue'); expect(result.styleBlock.rules[0].declarations).toHaveLength(1); }); }); describe('CSS Function Compilation', () => { it('should compile CSS function with string arguments', () => { const cssFunction: CSSFunctionExpression = { type: 'CSSFunction', functionName: 'css', arguments: [ 'color: red; font-size: 16px;', 'background: blue; margin: 10px;' ] }; const result = compiler.compile(cssFunction); expect(result.css).toContain('color: red'); expect(result.css).toContain('font-size: 16px'); expect(result.css).toContain('background: blue'); expect(result.css).toContain('margin: 10px'); expect(result.styleBlock.rules[0].declarations).toHaveLength(4); }); it('should compile CSS function with mixed arguments', () => { const cssObject: CSSObjectExpression = { type: 'CSSObject', properties: [ { key: 'color', value: 'red', computed: false } ] }; const cssFunction: CSSFunctionExpression = { type: 'CSSFunction', functionName: 'styled', arguments: [ 'font-size: 16px;', cssObject ] }; const result = compiler.compile(cssFunction); expect(result.css).toContain('font-size: 16px'); expect(result.css).toContain('color: red'); }); }); describe('Class Name Generation', () => { it('should generate unique class names', () => { const cssObject: CSSObjectExpression = { type: 'CSSObject', properties: [{ key: 'color', value: 'red', computed: false }] }; const result1 = compiler.compile(cssObject); const result2 = compiler.compile(cssObject); expect(result1.className).not.toBe(result2.className); expect(result1.className).toMatch(/^ordojs-css-\d+$/); expect(result2.className).toMatch(/^ordojs-css-\d+$/); }); it('should include component name in class name when provided', () => { const cssObject: CSSObjectExpression = { type: 'CSSObject', properties: [{ key: 'color', value: 'red', computed: false }] }; const result = compiler.compile(cssObject, 'MyComponent'); expect(result.className).toMatch(/^ordojs-css-mycomponent-\d+$/); }); it('should use custom class prefix when configured', () => { const customCompiler = new OrdoJSCSSInJSCompiler({ classPrefix: 'custom-prefix' }); const cssObject: CSSObjectExpression = { type: 'CSSObject', properties: [{ key: 'color', value: 'red', computed: false }] }; const result = customCompiler.compile(cssObject); expect(result.className).toMatch(/^custom-prefix-\d+$/); }); }); describe('Multiple Expression Compilation', () => { it('should compile multiple expressions into a single rule', () => { const cssObject: CSSObjectExpression = { type: 'CSSObject', properties: [{ key: 'color', value: 'red', computed: false }] }; const cssTemplate: CSSTemplateExpression = { type: 'CSSTemplate', template: 'font-size: 16px; background: blue;', expressions: [] }; const result = compiler.compileMultiple([cssObject, cssTemplate], 'TestComponent'); expect(result.css).toContain('color: red'); expect(result.css).toContain('font-size: 16px'); expect(result.css).toContain('background: blue'); expect(result.styleBlock.rules).toHaveLength(1); expect(result.styleBlock.rules[0].declarations).toHaveLength(3); }); }); describe('Static Factory Methods', () => { it('should create CSS object from JavaScript object', () => { const jsObject = { color: 'red', fontSize: '16px', nested: { backgroundColor: 'blue' } }; const cssObject = OrdoJSCSSInJSCompiler.createCSSObject(jsObject); expect(cssObject.type).toBe('CSSObject'); expect(cssObject.properties).toHaveLength(3); expect(cssObject.properties[0].key).toBe('color'); expect(cssObject.properties[0].value).toBe('red'); expect(cssObject.properties[2].key).toBe('nested'); expect(cssObject.properties[2].value).toEqual({ type: 'CSSObject', properties: [{ key: 'backgroundColor', value: 'blue', computed: false }] }); }); it('should create CSS template expression', () => { const template = OrdoJSCSSInJSCompiler.createCSSTemplate('color: red; font-size: 16px;'); expect(template.type).toBe('CSSTemplate'); expect(template.template).toBe('color: red; font-size: 16px;'); expect(template.expressions).toEqual([]); }); it('should create CSS function expression', () => { const cssFunction = OrdoJSCSSInJSCompiler.createCSSFunction('css', ['color: red;']); expect(cssFunction.type).toBe('CSSFunction'); expect(cssFunction.functionName).toBe('css'); expect(cssFunction.arguments).toEqual(['color: red;']); }); }); describe('Configuration Options', () => { it('should respect scoped option', () => { const compiler = new OrdoJSCSSInJSCompiler({ scoped: false }); const cssObject: CSSObjectExpression = { type: 'CSSObject', properties: [{ key: 'color', value: 'red', computed: false }] }; const result = compiler.compile(cssObject); expect(result.styleBlock.scoped).toBe(false); }); it('should use custom class prefix', () => { const compiler = new OrdoJSCSSInJSCompiler({ classPrefix: 'my-app' }); const cssObject: CSSObjectExpression = { type: 'CSSObject', properties: [{ key: 'color', value: 'red', computed: false }] }; const result = compiler.compile(cssObject); expect(result.className).toMatch(/^my-app-\d+$/); }); }); describe('Error Handling', () => { it('should throw error for unsupported expression types', () => { const invalidExpression = { type: 'UnsupportedType' } as any; expect(() => { compiler.compile(invalidExpression); }).toThrow('Unsupported CSS-in-JS expression type: UnsupportedType'); }); it('should handle empty expressions gracefully', () => { const emptyObject: CSSObjectExpression = { type: 'CSSObject', properties: [] }; const result = compiler.compile(emptyObject); expect(result.css).toContain(`.${result.className} {\n\n}`); expect(result.styleBlock.rules[0].declarations).toHaveLength(0); }); }); describe('CSS Generation', () => { it('should generate properly formatted CSS', () => { const cssObject: CSSObjectExpression = { type: 'CSSObject', properties: [ { key: 'color', value: 'red', computed: false }, { key: 'fontSize', value: '16px', computed: false } ] }; const result = compiler.compile(cssObject); expect(result.css).toMatch(/\.[\w-]+ \{\n color: red;\n font-size: 16px;\n\}/); }); it('should handle important declarations', () => { const cssTemplate: CSSTemplateExpression = { type: 'CSSTemplate', template: 'color: red !important; font-size: 16px;', expressions: [] }; const result = compiler.compile(cssTemplate); expect(result.css).toContain('color: red !important'); expect(result.styleBlock.rules[0].declarations[0].important).toBe(true); expect(result.styleBlock.rules[0].declarations[1].important).toBe(false); }); }); });