mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-01 22:09:55 +00:00
* fix color picker value prefix and add component tests
* test(widgets): make color text assertion specific in WidgetColorPicker.test per review (DrJKL)
* test(widgets): use expect.soft for valid hex colors loop (suggestion by DrJKL)
* test(widgets): normalize color display to single leading # to address review question (AustinMroz)
* feat(widgets): normalize color widget values to #hex across inputs (hex/rgb/hsb); always emit with leading # using colorUtil conversions
* test(widgets): use data-testid selector for color text instead of generic span; add data-testid to component span for robustness
* support hsb|rgb|hex and coerce to hex with hashtag internally
refactor(widgets,utils): format-driven color normalization to lowercase #hex without casts; add typed toHexFromFormat and guards; simplify WidgetColorPicker state and types\n\n- utils: add ColorFormat, HSB/HSV types, isColorFormat/isHSBObject/isHSVObject, toHexFromFormat; reuse parseToRgb/hsbToRgb/rgbToHex\n- widgets: emit normalized #hex, display derived via toHexFromFormat, keep picker native v-model; typed widget options {format?}\n- tests: consolidate colorUtil tests into tests-ui/tests/colorUtil.test.ts; keep conversion + adjustColor suites; selectors robust\n- docs: add PR-5472-change-summary.md explaining changes\n\nAll type checks pass; ready for your final review before push.
refactor(widgets,utils): format-driven color normalization to lowercase #hex without casts; add typed toHexFromFormat and guards; simplify WidgetColorPicker state and types\n\n- utils: add ColorFormat, HSB/HSV types, isColorFormat/isHSBObject/isHSVObject, toHexFromFormat; reuse parseToRgb/hsbToRgb/rgbToHex\n- widgets: emit normalized #hex, display derived via toHexFromFormat, keep picker native v-model; typed widget options {format?}\n- tests: consolidate colorUtil tests into tests-ui/tests/colorUtil.test.ts; keep conversion + adjustColor suites; selectors robust\n- docs: add PR-5472-change-summary.md explaining changes\n\nAll type checks pass; ready for your final review before push.
chore: untrack PR-5472-change-summary.md and ignore locally (keep file on disk)
* fix(utils): use floor in hsbToRgb to match expected hex (#7f0000) for 50% brightness rounding behavior
* test(widgets): restore invalid-format fallback test and use data-testid selector in hex loop; chore: revert .gitignore change (remove PR-5472-change-summary.md entry)
* chore: restore .gitignore to match main (remove local note/comment)
* [refactor] improve color parsing in ColorPicker widget - addresses review feedback
- Use fancy color parsing for initial value normalization per @DrJKL's suggestion
- Simplify onPickerUpdate to trust configured format per @AustinMroz's feedback
- Remove redundant type checking and format guessing logic
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* [refactor] simplify color parsing - remove unnecessary helper function
- Remove normalizeColorValue helper and inline null checks
- Remove verbose comments
- Keep the same functionality with cleaner code
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* remove unused exports
---------
Co-authored-by: Claude <noreply@anthropic.com>
249 lines
7.1 KiB
TypeScript
249 lines
7.1 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest'
|
|
|
|
import {
|
|
adjustColor,
|
|
hexToRgb,
|
|
hsbToRgb,
|
|
parseToRgb,
|
|
rgbToHex
|
|
} from '@/utils/colorUtil'
|
|
|
|
interface ColorTestCase {
|
|
hex: string
|
|
rgb: string
|
|
rgba: string
|
|
hsl: string
|
|
hsla: string
|
|
lightExpected: string
|
|
transparentExpected: string
|
|
lightTransparentExpected: string
|
|
}
|
|
|
|
type ColorFormat = 'hex' | 'rgb' | 'rgba' | 'hsl' | 'hsla'
|
|
|
|
vi.mock('es-toolkit/compat', () => ({
|
|
memoize: (fn: any) => fn
|
|
}))
|
|
|
|
const targetOpacity = 0.5
|
|
const targetLightness = 0.5
|
|
|
|
const assertColorVariationsMatch = (variations: string[], adjustment: any) => {
|
|
for (let i = 0; i < variations.length - 1; i++) {
|
|
expect(adjustColor(variations[i], adjustment)).toBe(
|
|
adjustColor(variations[i + 1], adjustment)
|
|
)
|
|
}
|
|
}
|
|
|
|
const colors: Record<string, ColorTestCase> = {
|
|
green: {
|
|
hex: '#073642',
|
|
rgb: 'rgb(7, 54, 66)',
|
|
rgba: 'rgba(7, 54, 66, 1)',
|
|
hsl: 'hsl(192, 80.8%, 14.3%)',
|
|
hsla: 'hsla(192, 80.8%, 14.3%, 1)',
|
|
lightExpected: 'hsla(192, 80.8%, 64.3%, 1)',
|
|
transparentExpected: 'hsla(192, 80.8%, 14.3%, 0.5)',
|
|
lightTransparentExpected: 'hsla(192, 80.8%, 64.3%, 0.5)'
|
|
},
|
|
blue: {
|
|
hex: '#00008B',
|
|
rgb: 'rgb(0,0,139)',
|
|
rgba: 'rgba(0,0,139,1)',
|
|
hsl: 'hsl(240,100%,27.3%)',
|
|
hsla: 'hsl(240,100%,27.3%,1)',
|
|
lightExpected: 'hsla(240, 100%, 77.3%, 1)',
|
|
transparentExpected: 'hsla(240, 100%, 27.3%, 0.5)',
|
|
lightTransparentExpected: 'hsla(240, 100%, 77.3%, 0.5)'
|
|
}
|
|
}
|
|
|
|
const formats: ColorFormat[] = ['hex', 'rgb', 'rgba', 'hsl', 'hsla']
|
|
|
|
describe('colorUtil conversions', () => {
|
|
describe('hexToRgb / rgbToHex', () => {
|
|
it('converts 6-digit hex to RGB', () => {
|
|
expect(hexToRgb('#ff0000')).toEqual({ r: 255, g: 0, b: 0 })
|
|
expect(hexToRgb('#00ff00')).toEqual({ r: 0, g: 255, b: 0 })
|
|
expect(hexToRgb('#0000ff')).toEqual({ r: 0, g: 0, b: 255 })
|
|
})
|
|
|
|
it('converts 3-digit hex to RGB', () => {
|
|
expect(hexToRgb('#f00')).toEqual({ r: 255, g: 0, b: 0 })
|
|
expect(hexToRgb('#0f0')).toEqual({ r: 0, g: 255, b: 0 })
|
|
expect(hexToRgb('#00f')).toEqual({ r: 0, g: 0, b: 255 })
|
|
})
|
|
|
|
it('converts RGB to lowercase #hex and clamps values', () => {
|
|
expect(rgbToHex({ r: 255, g: 0, b: 0 })).toBe('#ff0000')
|
|
expect(rgbToHex({ r: 0, g: 255, b: 0 })).toBe('#00ff00')
|
|
expect(rgbToHex({ r: 0, g: 0, b: 255 })).toBe('#0000ff')
|
|
// out-of-range should clamp
|
|
expect(rgbToHex({ r: -10, g: 300, b: 16 })).toBe('#00ff10')
|
|
})
|
|
|
|
it('round-trips #hex -> rgb -> #hex', () => {
|
|
const hex = '#123abc'
|
|
expect(rgbToHex(hexToRgb(hex))).toBe('#123abc')
|
|
})
|
|
})
|
|
|
|
describe('parseToRgb', () => {
|
|
it('parses #hex', () => {
|
|
expect(parseToRgb('#ff0000')).toEqual({ r: 255, g: 0, b: 0 })
|
|
})
|
|
|
|
it('parses rgb()/rgba()', () => {
|
|
expect(parseToRgb('rgb(255, 0, 0)')).toEqual({ r: 255, g: 0, b: 0 })
|
|
expect(parseToRgb('rgba(255,0,0,0.5)')).toEqual({ r: 255, g: 0, b: 0 })
|
|
})
|
|
|
|
it('parses hsl()/hsla()', () => {
|
|
expect(parseToRgb('hsl(0, 100%, 50%)')).toEqual({ r: 255, g: 0, b: 0 })
|
|
const green = parseToRgb('hsla(120, 100%, 50%, 0.7)')
|
|
expect(green.r).toBe(0)
|
|
expect(green.g).toBe(255)
|
|
expect(green.b).toBe(0)
|
|
})
|
|
})
|
|
|
|
describe('hsbToRgb', () => {
|
|
it('converts HSB to primary RGB colors', () => {
|
|
expect(hsbToRgb({ h: 0, s: 100, b: 100 })).toEqual({ r: 255, g: 0, b: 0 })
|
|
expect(hsbToRgb({ h: 120, s: 100, b: 100 })).toEqual({
|
|
r: 0,
|
|
g: 255,
|
|
b: 0
|
|
})
|
|
expect(hsbToRgb({ h: 240, s: 100, b: 100 })).toEqual({
|
|
r: 0,
|
|
g: 0,
|
|
b: 255
|
|
})
|
|
})
|
|
|
|
it('handles non-100 brightness and clamps/normalizes input', () => {
|
|
const rgb = hsbToRgb({ h: 360, s: 150, b: 50 })
|
|
expect(rgbToHex(rgb)).toBe('#7f0000')
|
|
})
|
|
})
|
|
})
|
|
describe('colorUtil - adjustColor', () => {
|
|
const runAdjustColorTests = (
|
|
color: ColorTestCase,
|
|
format: ColorFormat
|
|
): void => {
|
|
it('converts lightness', () => {
|
|
const result = adjustColor(color[format], { lightness: targetLightness })
|
|
expect(result).toBe(color.lightExpected)
|
|
})
|
|
|
|
it('applies opacity', () => {
|
|
const result = adjustColor(color[format], { opacity: targetOpacity })
|
|
expect(result).toBe(color.transparentExpected)
|
|
})
|
|
|
|
it('applies lightness and opacity jointly', () => {
|
|
const result = adjustColor(color[format], {
|
|
lightness: targetLightness,
|
|
opacity: targetOpacity
|
|
})
|
|
expect(result).toBe(color.lightTransparentExpected)
|
|
})
|
|
}
|
|
|
|
describe.each(Object.entries(colors))('%s color', (_colorName, color) => {
|
|
describe.each(formats)('%s format', (format) => {
|
|
runAdjustColorTests(color, format as ColorFormat)
|
|
})
|
|
})
|
|
|
|
it('returns the original value for invalid color formats', () => {
|
|
const invalidColors = [
|
|
'cmky(100, 50, 50, 0.5)',
|
|
'rgb(300, -10, 256)',
|
|
'xyz(255, 255, 255)',
|
|
'hsl(100, 50, 50%)',
|
|
'hsl(100, 50%, 50)',
|
|
'#GGGGGG',
|
|
'#3333'
|
|
]
|
|
|
|
invalidColors.forEach((color) => {
|
|
const result = adjustColor(color, {
|
|
lightness: targetLightness,
|
|
opacity: targetOpacity
|
|
})
|
|
expect(result).toBe(color)
|
|
})
|
|
})
|
|
|
|
it('returns the original value for null or undefined inputs', () => {
|
|
// @ts-expect-error fixme ts strict error
|
|
expect(adjustColor(null, { opacity: targetOpacity })).toBe(null)
|
|
// @ts-expect-error fixme ts strict error
|
|
expect(adjustColor(undefined, { opacity: targetOpacity })).toBe(undefined)
|
|
})
|
|
|
|
describe('handles input variations', () => {
|
|
it('handles spaces in rgb input', () => {
|
|
const variations = [
|
|
'rgb(0, 0, 0)',
|
|
'rgb(0,0,0)',
|
|
'rgb(0, 0,0)',
|
|
'rgb(0,0, 0)'
|
|
]
|
|
assertColorVariationsMatch(variations, { lightness: 0.5 })
|
|
})
|
|
|
|
it('handles spaces in hsl input', () => {
|
|
const variations = [
|
|
'hsl(0, 0%, 0%)',
|
|
'hsl(0,0%,0%)',
|
|
'hsl(0, 0%,0%)',
|
|
'hsl(0,0%, 0%)'
|
|
]
|
|
assertColorVariationsMatch(variations, { lightness: 0.5 })
|
|
})
|
|
|
|
it('handles different decimal places in rgba input', () => {
|
|
const variations = [
|
|
'rgba(0, 0, 0, 0.5)',
|
|
'rgba(0, 0, 0, 0.50)',
|
|
'rgba(0, 0, 0, 0.500)'
|
|
]
|
|
assertColorVariationsMatch(variations, { opacity: 0.5 })
|
|
})
|
|
|
|
it('handles different decimal places in hsla input', () => {
|
|
const variations = [
|
|
'hsla(0, 0%, 0%, 0.5)',
|
|
'hsla(0, 0%, 0%, 0.50)',
|
|
'hsla(0, 0%, 0%, 0.500)'
|
|
]
|
|
assertColorVariationsMatch(variations, { opacity: 0.5 })
|
|
})
|
|
})
|
|
|
|
describe('clamps values correctly', () => {
|
|
it('clamps lightness to 0 and 100', () => {
|
|
expect(adjustColor('hsl(0, 100%, 50%)', { lightness: -1 })).toBe(
|
|
'hsla(0, 100%, 0%, 1)'
|
|
)
|
|
expect(adjustColor('hsl(0, 100%, 50%)', { lightness: 1.5 })).toBe(
|
|
'hsla(0, 100%, 100%, 1)'
|
|
)
|
|
})
|
|
|
|
it('clamps opacity to 0 and 1', () => {
|
|
expect(adjustColor('rgba(0, 0, 0, 0.5)', { opacity: -0.5 })).toBe(
|
|
'hsla(0, 0%, 0%, 0)'
|
|
)
|
|
expect(adjustColor('rgba(0, 0, 0, 0.5)', { opacity: 1.5 })).toBe(
|
|
'hsla(0, 0%, 0%, 1)'
|
|
)
|
|
})
|
|
})
|
|
})
|