Add shared Icon primitive and controlled glyph vocabulary (Carbon, filled)

This commit is contained in:
2026-06-06 23:51:47 +03:00
parent af9ee1e4c0
commit 3e89d9a531
18 changed files with 587 additions and 77 deletions
+51
View File
@@ -0,0 +1,51 @@
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
import { act } from 'react';
import { createRoot, type Root } from 'react-dom/client';
import { useNotificationStore, type NotificationKind } from '../stores/NotificationStore';
import { Toaster } from './Toaster';
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
let container: HTMLDivElement;
let root: Root;
beforeEach(() => {
useNotificationStore.getState().clear();
container = document.createElement('div');
document.body.appendChild(container);
root = createRoot(container);
});
afterEach(() => {
act(() => root.unmount());
container.remove();
useNotificationStore.getState().clear();
});
const KINDS: NotificationKind[] = ['error', 'warning', 'success', 'info'];
describe('Toaster status glyphs', () => {
// The point of the status icon is a non-colour severity channel (WCAG 1.4.1) — so
// every kind must render a leading glyph, not lean on the border colour alone.
test.each(KINDS)('a %s toast renders a leading status glyph', (kind) => {
act(() => {
useNotificationStore.getState().notify({ kind, title: `${kind} title`, message: 'body' });
root.render(<Toaster />);
});
const toast = container.querySelector('[role="alert"], [role="status"]');
expect(toast).not.toBeNull();
// The status glyph is a direct-child <svg> of the toast (the close button's svg
// is nested inside the button, so :scope > svg isolates the status icon).
expect(toast!.querySelector(':scope > svg')).not.toBeNull();
});
test('error and warning are assertive; success and info are polite', () => {
act(() => {
useNotificationStore.getState().notify({ kind: 'error', title: 'e', message: 'b' });
useNotificationStore.getState().notify({ kind: 'success', title: 's', message: 'b' });
root.render(<Toaster />);
});
expect(container.querySelector('[role="alert"]')).not.toBeNull();
expect(container.querySelector('[role="status"]')).not.toBeNull();
});
});