@ordojs/core
Version:
Core compiler and runtime for OrdoJS framework
368 lines (299 loc) • 12.4 kB
text/typescript
/**
* @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);
});
});
});