Drag the left edge of the verse side panel to resize it (arrow keys too, Shift for bigger steps; double-click resets). The text reflows to make room, and the width is remembered on this device. Reuses the Study page's resize handle; the hook now takes the CSS variable to drive. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
91 lines
4.3 KiB
JavaScript
91 lines
4.3 KiB
JavaScript
import { renderHook, act } from '@testing-library/react';
|
|
import { vi, test, expect, beforeEach } from 'vitest';
|
|
import { useResizableWidth } from './useResizableWidth.js';
|
|
|
|
beforeEach(() => { localStorage.clear(); });
|
|
|
|
// A container 1400px wide whose right edge is at x=1400, and a panel currently 448px wide.
|
|
const setup = (opts = {}) => {
|
|
const style = { setProperty: vi.fn(), removeProperty: vi.fn() };
|
|
const containerRef = { current: { getBoundingClientRect: () => ({ right: 1400, width: 1400 }), style } };
|
|
const panelRef = { current: { getBoundingClientRect: () => ({ width: 448 }) } };
|
|
const hook = renderHook(() => useResizableWidth({ storageKey: 'w', containerRef, ...opts }));
|
|
const handle = () => hook.result.current.handleProps(panelRef);
|
|
return { hook, handle, style };
|
|
};
|
|
const target = { setPointerCapture: vi.fn(), releasePointerCapture: vi.fn() };
|
|
const down = (h) => h.handle().onPointerDown({ button: 0, pointerId: 1, currentTarget: target });
|
|
const move = (h, clientX) => h.handle().onPointerMove({ clientX });
|
|
const up = (h) => h.handle().onPointerUp({ pointerId: 1, currentTarget: target });
|
|
|
|
test('dragging sets the width from the pointer to the right edge, then remembers it', () => {
|
|
const h = setup();
|
|
act(() => { down(h); move(h, 900); });
|
|
expect(h.style.setProperty).toHaveBeenLastCalledWith('--study-right', '500px'); // 1400 - 900
|
|
expect(h.hook.result.current.width).toBeNull(); // no React render per pointer move
|
|
act(() => up(h));
|
|
expect(h.hook.result.current.width).toBe(500);
|
|
expect(localStorage.getItem('w')).toBe('500');
|
|
});
|
|
|
|
test('the width is kept between a minimum and a maximum that leaves room for the passage', () => {
|
|
const h = setup();
|
|
act(() => { down(h); move(h, 1350); }); // would be 50px
|
|
expect(h.style.setProperty).toHaveBeenLastCalledWith('--study-right', '320px');
|
|
act(() => { move(h, 100); }); // would be 1300px; the container leaves 1400 - 600 = 800, capped at 760
|
|
expect(h.style.setProperty).toHaveBeenLastCalledWith('--study-right', '760px');
|
|
act(() => up(h));
|
|
expect(h.hook.result.current.width).toBe(760);
|
|
});
|
|
|
|
test('a move without a press does nothing', () => {
|
|
const h = setup();
|
|
act(() => move(h, 900));
|
|
expect(h.style.setProperty).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test('arrow keys resize from the current width; shift takes bigger steps', () => {
|
|
const h = setup();
|
|
act(() => h.handle().onKeyDown({ key: 'ArrowLeft', preventDefault() {} }));
|
|
expect(h.hook.result.current.width).toBe(472); // 448 + 24: wider
|
|
act(() => h.handle().onKeyDown({ key: 'ArrowRight', shiftKey: true, preventDefault() {} }));
|
|
expect(h.hook.result.current.width).toBe(376); // 472 - 96: narrower
|
|
expect(localStorage.getItem('w')).toBe('376');
|
|
});
|
|
|
|
test('Home, Escape and double-click go back to the default width', () => {
|
|
localStorage.setItem('w', '600');
|
|
const h = setup();
|
|
expect(h.hook.result.current.width).toBe(600);
|
|
act(() => h.handle().onKeyDown({ key: 'Home', preventDefault() {} }));
|
|
expect(h.hook.result.current.width).toBeNull();
|
|
expect(localStorage.getItem('w')).toBeNull();
|
|
expect(h.style.removeProperty).toHaveBeenCalledWith('--study-right');
|
|
act(() => h.handle().onKeyDown({ key: 'ArrowLeft', preventDefault() {} }));
|
|
act(() => h.handle().onDoubleClick());
|
|
expect(h.hook.result.current.width).toBeNull();
|
|
});
|
|
|
|
test('a remembered width is restored, kept within the limits, and junk is ignored', () => {
|
|
localStorage.setItem('w', '100');
|
|
expect(setup().hook.result.current.width).toBe(320);
|
|
localStorage.setItem('w', '5000');
|
|
expect(setup().hook.result.current.width).toBe(760);
|
|
localStorage.setItem('w', 'wide');
|
|
expect(setup().hook.result.current.width).toBeNull();
|
|
});
|
|
|
|
test('the handle describes itself for assistive technology', () => {
|
|
const props = setup().handle();
|
|
expect(props).toMatchObject({ role: 'separator', 'aria-orientation': 'vertical', 'aria-label': 'Resize side panel', tabIndex: 0, 'aria-valuenow': 448 });
|
|
});
|
|
|
|
test('a different panel can name its own CSS variable', () => {
|
|
const h = setup({ cssVar: '--reader-pane' });
|
|
act(() => { down(h); move(h, 1000); });
|
|
expect(h.style.setProperty).toHaveBeenLastCalledWith('--reader-pane', '400px');
|
|
act(() => up(h));
|
|
act(() => { h.handle().onDoubleClick(); });
|
|
expect(h.style.removeProperty).toHaveBeenLastCalledWith('--reader-pane');
|
|
});
|