mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
ColorField: shared color-input primitive, Theme Builder + Chart Builder migration
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* ColorField — the shared color-input primitive (swatch + optional hex field).
|
||||
* Covers the render shapes and the hex-entry commit/normalize behavior; the
|
||||
* Theme Builder and Chart Builder integrations are exercised in their own tests.
|
||||
*/
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
import { act } from 'react';
|
||||
import { createRoot, type Root } from 'react-dom/client';
|
||||
import { ColorField } from './ColorField';
|
||||
|
||||
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
||||
|
||||
let container: HTMLDivElement;
|
||||
let root: Root;
|
||||
|
||||
beforeEach(() => {
|
||||
container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
act(() => {
|
||||
root = createRoot(container);
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
act(() => root.unmount());
|
||||
container.remove();
|
||||
});
|
||||
|
||||
/** Drive an input's value through the native setter so React's onChange fires. */
|
||||
function setNativeValue(el: HTMLInputElement, value: string) {
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method -- invoked immediately via .call
|
||||
const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value')!.set!;
|
||||
setter.call(el, value);
|
||||
el.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
}
|
||||
|
||||
const swatch = () => container.querySelector<HTMLInputElement>('input[type="color"]')!;
|
||||
const hexField = () => container.querySelector<HTMLInputElement>('input[type="text"]');
|
||||
|
||||
describe('ColorField', () => {
|
||||
test('renders just the swatch by default (no hex field)', () => {
|
||||
act(() => root.render(<ColorField value="#112233" label="Fill" onChange={() => {}} />));
|
||||
expect(swatch().getAttribute('aria-label')).toBe('Fill');
|
||||
expect(swatch().value).toBe('#112233');
|
||||
expect(hexField()).toBeNull();
|
||||
});
|
||||
|
||||
test('falls back to #000000 in the picker for a non-hex value', () => {
|
||||
act(() => root.render(<ColorField value="steelblue" label="Fill" onChange={() => {}} />));
|
||||
expect(swatch().value).toBe('#000000');
|
||||
});
|
||||
|
||||
test('the picker reports its raw value on change', () => {
|
||||
const onChange = vi.fn();
|
||||
act(() => root.render(<ColorField value="#112233" label="Fill" onChange={onChange} />));
|
||||
act(() => setNativeValue(swatch(), '#ff0000'));
|
||||
expect(onChange).toHaveBeenCalledWith('#ff0000');
|
||||
});
|
||||
|
||||
test('hex mode commits a valid hex and normalizes an unprefixed one', () => {
|
||||
const onChange = vi.fn();
|
||||
act(() => root.render(<ColorField value="#112233" label="Fill" onChange={onChange} hex />));
|
||||
const hex = hexField()!;
|
||||
expect(hex.getAttribute('aria-label')).toBe('Fill hex value');
|
||||
|
||||
act(() => setNativeValue(hex, '#abcdef'));
|
||||
expect(onChange).toHaveBeenLastCalledWith('#abcdef');
|
||||
|
||||
act(() => setNativeValue(hex, 'ABCDEF'));
|
||||
expect(onChange).toHaveBeenLastCalledWith('#abcdef');
|
||||
});
|
||||
|
||||
test('hex mode does not commit an incomplete value', () => {
|
||||
const onChange = vi.fn();
|
||||
act(() => root.render(<ColorField value="#112233" label="Fill" onChange={onChange} hex />));
|
||||
act(() => setNativeValue(hexField()!, '#abc'));
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user