Resizable right column in the Study stage; start on the chosen page without a flash
Build and Push Docker Image / build-and-push (push) Failing after 20s
Build and Push Docker Image / build-and-push (push) Failing after 20s
The Study stage's right column has a drag handle (mouse, arrow keys, double-click or Home to reset); the width is remembered per device. The app now starts on the user's chosen home page from the first render instead of showing My Studies first. Tests wait on page headings so a lazily loaded page can't be confused with hidden text from the previous one. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8c4f54e033
commit
7a010fcda8
5 files changed
+226
-20
No files matched your search
@@ -0,0 +1,81 @@
|
||||
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 });
|
||||
});
|
||||
Reference in new issue
Block a user