mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
8c0a6b9239
A persistent left-rail toolbar (APG toolbar + roving tabindex) toggles the library / editor / preview panes; a hidden pane frees its space and the rest redistribute proportionally to their remembered widths. Visibility persists to astrolabe:ux-prefs (separate key from widths, so a hidden pane keeps its width). Adds the positional pane-* glyph sub-family to Icon and a Datasets shortcut button in the strip.
100 lines
3.6 KiB
TypeScript
100 lines
3.6 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
|
import { act } from 'react';
|
|
import { createRoot, type Root } from 'react-dom/client';
|
|
import { usePanesStore } from '../stores/PanesStore';
|
|
import { PaneToggleStrip } from './PaneToggleStrip';
|
|
|
|
vi.mock('../modals/ModalCoordinator', () => ({ openModal: vi.fn() }));
|
|
import { openModal } from '../modals/ModalCoordinator';
|
|
|
|
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
|
|
|
let container: HTMLDivElement;
|
|
let root: Root;
|
|
|
|
const toggleButtons = () =>
|
|
Array.from(container.querySelectorAll<HTMLButtonElement>('button[aria-pressed]'));
|
|
const datasetsButton = () =>
|
|
container.querySelector<HTMLButtonElement>('button[aria-label="Datasets"]')!;
|
|
|
|
beforeEach(() => {
|
|
usePanesStore.getState().hydrate({}, {});
|
|
container = document.createElement('div');
|
|
document.body.appendChild(container);
|
|
root = createRoot(container);
|
|
act(() => root.render(<PaneToggleStrip />));
|
|
});
|
|
|
|
afterEach(() => {
|
|
act(() => root.unmount());
|
|
container.remove();
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
describe('PaneToggleStrip', () => {
|
|
test('renders an APG toolbar: three pane toggles + a Datasets command button', () => {
|
|
expect(container.querySelector('[role="toolbar"][aria-orientation="vertical"]')).not.toBeNull();
|
|
expect(toggleButtons()).toHaveLength(3);
|
|
// The Datasets control is a command button, not a toggle (no aria-pressed).
|
|
expect(datasetsButton().hasAttribute('aria-pressed')).toBe(false);
|
|
});
|
|
|
|
test('aria-pressed mirrors pane visibility and flips on click', () => {
|
|
const [library] = toggleButtons();
|
|
expect(library.getAttribute('aria-pressed')).toBe('true');
|
|
|
|
act(() => library.click());
|
|
|
|
expect(usePanesStore.getState().libraryVisible).toBe(false);
|
|
expect(library.getAttribute('aria-pressed')).toBe('false');
|
|
});
|
|
|
|
test('the accessible name is stable across the pressed/not-pressed flip (APG)', () => {
|
|
const [library] = toggleButtons();
|
|
expect(library.getAttribute('aria-label')).toBe('Library pane');
|
|
act(() => library.click());
|
|
expect(library.getAttribute('aria-label')).toBe('Library pane');
|
|
});
|
|
|
|
test('roving tabindex: exactly one control is in the tab order at a time', () => {
|
|
const all = [...toggleButtons(), datasetsButton()];
|
|
expect(all.filter((b) => b.tabIndex === 0)).toHaveLength(1);
|
|
expect(all[0].tabIndex).toBe(0); // first control by default
|
|
});
|
|
|
|
test('ArrowDown moves the roving tab stop to the next control', () => {
|
|
const all = [...toggleButtons(), datasetsButton()];
|
|
act(() => {
|
|
all[0].dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }));
|
|
});
|
|
expect(all[0].tabIndex).toBe(-1);
|
|
expect(all[1].tabIndex).toBe(0);
|
|
});
|
|
|
|
test('End jumps to the last control (Datasets); Home returns to the first', () => {
|
|
const all = [...toggleButtons(), datasetsButton()];
|
|
act(() => {
|
|
all[0].dispatchEvent(new KeyboardEvent('keydown', { key: 'End', bubbles: true }));
|
|
});
|
|
expect(datasetsButton().tabIndex).toBe(0);
|
|
|
|
act(() => {
|
|
datasetsButton().dispatchEvent(new KeyboardEvent('keydown', { key: 'Home', bubbles: true }));
|
|
});
|
|
expect(all[0].tabIndex).toBe(0);
|
|
});
|
|
|
|
test('ArrowUp from the first control wraps to the last', () => {
|
|
const all = [...toggleButtons(), datasetsButton()];
|
|
act(() => {
|
|
all[0].dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowUp', bubbles: true }));
|
|
});
|
|
expect(datasetsButton().tabIndex).toBe(0);
|
|
});
|
|
|
|
test('the Datasets button opens the Datasets manager', () => {
|
|
act(() => datasetsButton().click());
|
|
expect(openModal).toHaveBeenCalledWith('datasets');
|
|
});
|
|
});
|