openpencil/tests/engine/color/basic.test.ts
2026-05-16 12:30:27 +03:00

233 lines
5.8 KiB
TypeScript

import { describe, test, expect } from 'bun:test'
import {
parseColor,
normalizeColor,
colorToHex,
colorToHex8,
colorToHexRaw,
colorToRgba255,
colorToCSS,
colorToCSSCompact,
rgba255ToColor,
colorToFill,
colorDistance
} from '@open-pencil/core'
describe('parseColor', () => {
test('hex 6-digit', () => {
const c = parseColor('#FF0000')
expect(c.r).toBeCloseTo(1)
expect(c.g).toBeCloseTo(0)
expect(c.b).toBeCloseTo(0)
expect(c.a).toBe(1)
})
test('hex 3-digit', () => {
const c = parseColor('#f00')
expect(c.r).toBeCloseTo(1)
expect(c.g).toBeCloseTo(0)
expect(c.b).toBeCloseTo(0)
})
test('hex 8-digit with alpha', () => {
const c = parseColor('#FF000080')
expect(c.r).toBeCloseTo(1)
expect(c.a).toBeCloseTo(0.5, 1)
})
test('rgb()', () => {
const c = parseColor('rgb(255, 0, 0)')
expect(c.r).toBeCloseTo(1)
expect(c.g).toBeCloseTo(0)
expect(c.b).toBeCloseTo(0)
expect(c.a).toBe(1)
})
test('rgba()', () => {
const c = parseColor('rgba(255, 0, 0, 0.5)')
expect(c.r).toBeCloseTo(1)
expect(c.a).toBeCloseTo(0.5)
})
test('named color', () => {
const c = parseColor('blue')
expect(c.b).toBeCloseTo(1)
expect(c.r).toBeCloseTo(0)
expect(c.a).toBe(1)
})
test('white', () => {
const c = parseColor('#FFFFFF')
expect(c.r).toBeCloseTo(1)
expect(c.g).toBeCloseTo(1)
expect(c.b).toBeCloseTo(1)
})
test('invalid returns black fallback', () => {
const c = parseColor('invalid')
expect(c.r).toBe(0)
expect(c.g).toBe(0)
expect(c.b).toBe(0)
expect(c.a).toBe(1)
})
test('empty string returns black fallback', () => {
const c = parseColor('')
expect(c.r).toBe(0)
expect(c.g).toBe(0)
expect(c.b).toBe(0)
})
})
describe('normalizeColor', () => {
test('undefined returns black', () => {
const c = normalizeColor()
expect(c).toEqual({ r: 0, g: 0, b: 0, a: 1 })
})
test('partial fills defaults', () => {
const c = normalizeColor({ r: 0.5 })
expect(c).toEqual({ r: 0.5, g: 0, b: 0, a: 1 })
})
test('full color passes through', () => {
const c = normalizeColor({ r: 0.1, g: 0.2, b: 0.3, a: 0.4 })
expect(c).toEqual({ r: 0.1, g: 0.2, b: 0.3, a: 0.4 })
})
})
describe('colorToHex', () => {
test('red', () => {
expect(colorToHex({ r: 1, g: 0, b: 0, a: 1 })).toBe('#FF0000')
})
test('black', () => {
expect(colorToHex({ r: 0, g: 0, b: 0, a: 1 })).toBe('#000000')
})
test('white', () => {
expect(colorToHex({ r: 1, g: 1, b: 1, a: 1 })).toBe('#FFFFFF')
})
})
describe('colorToHex8', () => {
test('opaque returns 6-char hex', () => {
expect(colorToHex8({ r: 1, g: 0, b: 0, a: 1 })).toBe('#FF0000')
})
test('semi-transparent returns 8-char hex', () => {
const hex = colorToHex8({ r: 1, g: 0, b: 0, a: 0.5 })
expect(hex).toMatch(/^#[0-9A-F]{8}$/)
expect(hex.slice(0, 7)).toBe('#FF0000')
})
test('alpha override', () => {
const hex = colorToHex8({ r: 1, g: 0, b: 0, a: 1 }, 0.5)
expect(hex).toMatch(/^#FF0000[0-9A-F]{2}$/)
})
test('alpha=0 returns 8-char hex', () => {
expect(colorToHex8({ r: 1, g: 0, b: 0, a: 0 })).toBe('#FF000000')
})
test('uppercase', () => {
const hex = colorToHex8({ r: 0.8, g: 0.6, b: 0.1, a: 0.5 })
expect(hex).toBe(hex.toUpperCase())
})
})
describe('colorToHexRaw', () => {
test('strips # prefix', () => {
expect(colorToHexRaw({ r: 1, g: 0, b: 0, a: 1 })).toBe('FF0000')
})
})
describe('colorToRgba255', () => {
test('converts 0-1 to 0-255', () => {
const result = colorToRgba255({ r: 1, g: 0.5, b: 0, a: 0.8 })
expect(result.r).toBe(255)
expect(result.g).toBe(128)
expect(result.b).toBe(0)
expect(result.a).toBe(0.8)
})
})
describe('colorToCSS', () => {
test('opaque uses rgb()', () => {
const result = colorToCSS({ r: 1, g: 0, b: 0, a: 1 })
expect(result).toBe('rgb(255, 0, 0)')
})
test('transparent uses rgba()', () => {
const result = colorToCSS({ r: 1, g: 0, b: 0, a: 0.5 })
expect(result).toBe('rgba(255, 0, 0, 0.5)')
})
})
describe('colorToCSSCompact', () => {
test('no spaces between values', () => {
const result = colorToCSSCompact({ r: 1, g: 0, b: 0, a: 0.5 })
expect(result).toBe('rgba(255,0,0,0.5)')
})
test('opaque omits alpha', () => {
const result = colorToCSSCompact({ r: 1, g: 0, b: 0, a: 1 })
expect(result).toBe('rgb(255,0,0)')
})
})
describe('rgba255ToColor', () => {
test('converts 255-scale to 0-1', () => {
const c = rgba255ToColor(255, 128, 0, 0.5)
expect(c.r).toBeCloseTo(1)
expect(c.g).toBeCloseTo(0.502, 2)
expect(c.b).toBe(0)
expect(c.a).toBe(0.5)
})
test('alpha defaults to 1', () => {
const c = rgba255ToColor(0, 0, 0)
expect(c.a).toBe(1)
})
})
describe('colorToFill', () => {
test('from Color object', () => {
const fill = colorToFill({ r: 1, g: 0, b: 0, a: 0.5 })
expect(fill.type).toBe('SOLID')
expect(fill.color.r).toBe(1)
expect(fill.opacity).toBe(0.5)
expect(fill.visible).toBe(true)
})
test('from hex string', () => {
const fill = colorToFill('#ff0000')
expect(fill.type).toBe('SOLID')
expect(fill.color.r).toBeCloseTo(1)
expect(fill.color.g).toBeCloseTo(0)
})
})
describe('colorDistance', () => {
test('identical colors have zero distance', () => {
const c = { r: 0.5, g: 0.3, b: 0.1, a: 1 }
expect(colorDistance(c, c)).toBe(0)
})
test('black to white is ~441', () => {
const d = colorDistance({ r: 0, g: 0, b: 0, a: 1 }, { r: 1, g: 1, b: 1, a: 1 })
expect(d).toBeCloseTo(441.67, 0)
})
test('similar reds are close', () => {
const d = colorDistance({ r: 1, g: 0, b: 0, a: 1 }, { r: 0.95, g: 0.02, b: 0.02, a: 1 })
expect(d).toBeLessThan(15)
})
test('red to blue is far', () => {
const d = colorDistance({ r: 1, g: 0, b: 0, a: 1 }, { r: 0, g: 0, b: 1, a: 1 })
expect(d).toBeGreaterThan(300)
})
})