feat(dashboard): add revision-safe interactive widget grid

This commit is contained in:
cesnimda
2026-10-28 15:32:47 +01:00
parent aea7c29e70
commit 0bd290e788
21 changed files with 696 additions and 108 deletions
@@ -26,7 +26,18 @@ beforeEach(() => {
mocked.get.mockImplementation((url: string) => Promise.resolve({ data: response(url) }) as any);
mocked.put.mockReset(); mocked.put.mockImplementation((_url: string, body: any) => { stored = { ...body, revision: body.revision + 1 }; return Promise.resolve({ data: stored }) as any; });
});
async function edit() { fireEvent.click(await screen.findByRole('button', { name: 'Customize dashboard' })); return screen.findByRole('region', { name: 'Editing dashboard' }); }
async function edit() {
fireEvent.click(await screen.findByRole('button', { name: 'Customize dashboard' }));
const editor = await screen.findByRole('region', { name: 'Editing dashboard' });
fireEvent.click(within(editor).getByText('Manage dashboard widgets'));
return editor;
}
function widgetOptions(name: string) {
const preview = screen.getByRole('region', { name: 'Dashboard preview' });
fireEvent.click(within(preview).getAllByText(name).find(element => element.tagName === 'SUMMARY')!);
return preview;
}
test('visibility is a preview; cancel restores the saved layout without a write', async () => {
open(); await screen.findByText('Tracked jobs');
@@ -42,15 +53,16 @@ test('visibility is a preview; cancel restores the saved layout without a write'
test('failed save keeps the draft and retry persists visibility, order and size', async () => {
open(); const editor = await edit();
fireEvent.click(within(editor).getByRole('checkbox', { name: 'Summary cards' }));
fireEvent.click(within(editor).getByRole('button', { name: 'Move Today earlier' }));
fireEvent.change(within(editor).getByLabelText('Size for Today'), { target: { value: 'full' } });
const preview = widgetOptions('Today');
fireEvent.click(within(preview).getByRole('button', { name: 'Move Today earlier' }));
fireEvent.change(within(preview).getByLabelText('Size for Today'), { target: { value: 'full' } });
mocked.put.mockRejectedValueOnce(new Error('offline'));
fireEvent.click(within(editor).getByRole('button', { name: 'Save layout' }));
await screen.findByText('Your layout could not be saved. Your changes are still here. Try again.');
expect(within(editor).getByRole('checkbox', { name: 'Summary cards' })).not.toBeChecked();
fireEvent.click(within(editor).getByRole('button', { name: 'Save layout' }));
await waitFor(() => expect(screen.queryByRole('region', { name: 'Editing dashboard' })).not.toBeInTheDocument());
expect(stored.widgets[0]).toEqual({ id: 'attention', visible: true, size: 'full' });
expect(stored.widgets[0]).toMatchObject({ id: 'attention', visible: true, size: 'full', x: 0, y: 0, w: 12 });
expect(stored.widgets.find((widget: any) => widget.id === 'cards').visible).toBe(false);
expect(screen.queryByText('Tracked jobs')).not.toBeInTheDocument();
});
@@ -67,8 +79,9 @@ test('all hidden widgets leave an accessible recovery and reset remains cancella
test('keyboard ordering keeps focus and announces the resulting position without refetching reports', async () => {
open(); const editor = await edit(); const before = mocked.get.mock.calls.length;
const move = within(editor).getByRole('button', { name: 'Move Today earlier' }); move.focus(); fireEvent.click(move);
await waitFor(() => expect(within(editor).getByRole('button', { name: 'Move Today later' })).toHaveFocus());
const preview = widgetOptions('Today');
const move = within(preview).getByRole('button', { name: 'Move Today earlier' }); move.focus(); fireEvent.click(move);
await waitFor(() => expect(within(preview).getByRole('button', { name: 'Move Today later' })).toHaveFocus());
expect(screen.getByText('Today moved to position 1 of 9.')).toBeInTheDocument();
expect(mocked.get.mock.calls).toHaveLength(before);
});