mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 10:12:34 +00:00
Add shared Icon primitive and controlled glyph vocabulary (Carbon, filled)
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
|
||||
import { act } from 'react';
|
||||
import { createRoot, type Root } from 'react-dom/client';
|
||||
import { Icon } from './Icon';
|
||||
|
||||
(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);
|
||||
root = createRoot(container);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
act(() => root.unmount());
|
||||
container.remove();
|
||||
});
|
||||
|
||||
function render(node: React.ReactNode) {
|
||||
act(() => root.render(node));
|
||||
const svg = container.querySelector('svg');
|
||||
if (!svg) throw new Error('no <svg> rendered');
|
||||
return svg;
|
||||
}
|
||||
|
||||
describe('Icon', () => {
|
||||
test('renders an SVG on the Carbon 32-grid, decorative and unfocusable', () => {
|
||||
const svg = render(<Icon name="close" />);
|
||||
expect(svg.getAttribute('viewBox')).toBe('0 0 32 32');
|
||||
// Decorative by default: the enclosing control carries the accessible name.
|
||||
expect(svg.getAttribute('aria-hidden')).toBe('true');
|
||||
expect(svg.getAttribute('focusable')).toBe('false');
|
||||
});
|
||||
|
||||
test('each name draws a distinct glyph from the registry', () => {
|
||||
// close is a single polygon; delete (TrashCan) is rects + a path; dataset
|
||||
// (DataTable) is six cells + a path. Distinct geometry ⇒ distinct meaning.
|
||||
expect(render(<Icon name="close" />).querySelector('polygon')).not.toBeNull();
|
||||
const del = render(<Icon name="delete" />);
|
||||
expect(del.querySelectorAll('rect').length).toBe(3);
|
||||
expect(del.querySelector('path')).not.toBeNull();
|
||||
expect(render(<Icon name="dataset" />).querySelectorAll('rect').length).toBe(6);
|
||||
});
|
||||
|
||||
test('size maps to a single, changeable class token', () => {
|
||||
const sm = render(<Icon name="add" />); // default
|
||||
const smClass = sm.getAttribute('class') ?? '';
|
||||
const md = render(<Icon name="add" size="md" />);
|
||||
const mdClass = md.getAttribute('class') ?? '';
|
||||
// Whatever the CSS-module hashing, the two sizes must resolve to different
|
||||
// class strings so the 16/20/24/32 scale is actually applied, not ignored.
|
||||
expect(smClass).not.toBe(mdClass);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user