fix(kanban): honor theme and keyboard moves
This commit is contained in:
@@ -11,6 +11,7 @@ jest.mock('./api', () => ({
|
||||
get: jest.fn(),
|
||||
patch: jest.fn(() => Promise.resolve({ data: {} })),
|
||||
},
|
||||
getApiErrorMessage: jest.fn((_error, fallback) => fallback),
|
||||
}));
|
||||
|
||||
// eslint-disable-next-line import/first
|
||||
@@ -86,6 +87,25 @@ test('a custom status gets an Other column that is not a drop target', async ()
|
||||
await screen.findByText('Other');
|
||||
// Custom statuses survive rather than being coerced into a canonical stage.
|
||||
expect(screen.getByText('Take-home assignment')).toBeInTheDocument();
|
||||
fireEvent.dragStart(screen.getByText('Odd Role').closest('.MuiCard-root')!);
|
||||
const other = screen.getByRole('group', { name: 'Other column, not a drop target' });
|
||||
expect(other).toHaveAttribute('aria-disabled', 'true');
|
||||
expect(other).toHaveAttribute('data-drop-state', 'invalid');
|
||||
});
|
||||
|
||||
test('loading and retryable error states replace the board', async () => {
|
||||
let rejectLoad!: (reason: unknown) => void;
|
||||
mockedApi.get.mockReturnValueOnce(new Promise((_resolve, reject) => { rejectLoad = reject; }) as any);
|
||||
renderBoard();
|
||||
|
||||
expect(screen.getByRole('progressbar')).toBeInTheDocument();
|
||||
expect(screen.queryByRole('group', { name: 'Not Applied column' })).not.toBeInTheDocument();
|
||||
rejectLoad(new Error('offline'));
|
||||
|
||||
expect(await screen.findByText('Unable to load the kanban board')).toBeInTheDocument();
|
||||
mockedApi.get.mockResolvedValueOnce({ data: [] } as any);
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Retry' }));
|
||||
expect(await screen.findByRole('group', { name: 'Not Applied column' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('dropping onto a group applies that group entry stage', async () => {
|
||||
@@ -97,6 +117,9 @@ test('dropping onto a group applies that group entry stage', async () => {
|
||||
const column = screen.getByText('Active').closest('div')!.parentElement!.parentElement!;
|
||||
|
||||
fireEvent.dragStart(card.closest('.MuiCard-root')!);
|
||||
expect(card.closest('.MuiCard-root')).toHaveAttribute('aria-pressed', 'true');
|
||||
fireEvent.dragEnter(column);
|
||||
expect(column).toHaveAttribute('data-drop-state', 'active');
|
||||
fireEvent.dragOver(column);
|
||||
fireEvent.drop(column);
|
||||
|
||||
@@ -104,13 +127,44 @@ test('dropping onto a group applies that group entry stage', async () => {
|
||||
await waitFor(() => expect(mockedApi.patch).toHaveBeenCalledWith('/jobapplications/7/status', { status: 'Applied' }));
|
||||
});
|
||||
|
||||
test('keyboard pickup and drop moves a card to a valid column', async () => {
|
||||
mockedApi.get.mockResolvedValue({ data: [job(11, 'Keyboard Role', 'Saved')] } as any);
|
||||
renderBoard();
|
||||
|
||||
const card = (await screen.findByText('Keyboard Role')).closest('.MuiCard-root')!;
|
||||
fireEvent.keyDown(card, { key: ' ' });
|
||||
expect(card).toHaveAttribute('aria-pressed', 'true');
|
||||
|
||||
const column = screen.getByRole('group', { name: 'Active column' });
|
||||
fireEvent.focus(column);
|
||||
fireEvent.keyDown(column, { key: 'Enter' });
|
||||
|
||||
await waitFor(() => expect(mockedApi.patch).toHaveBeenCalledWith('/jobapplications/11/status', { status: 'Applied' }));
|
||||
expect(await screen.findByText('Job moved to Applied.')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('a failed move keeps the card and reports a retryable error', async () => {
|
||||
mockedApi.get.mockResolvedValue({ data: [job(12, 'Failed Move Role', 'Saved')] } as any);
|
||||
mockedApi.patch.mockRejectedValueOnce(new Error('offline'));
|
||||
renderBoard();
|
||||
|
||||
const card = (await screen.findByText('Failed Move Role')).closest('.MuiCard-root')!;
|
||||
const column = screen.getByRole('group', { name: 'Active column' });
|
||||
fireEvent.dragStart(card);
|
||||
fireEvent.dragOver(column);
|
||||
fireEvent.drop(column);
|
||||
|
||||
expect(await screen.findByText('Unable to move this job right now.')).toBeInTheDocument();
|
||||
expect(screen.getByText('Failed Move Role')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('the card menu offers precise stages grouped by section', async () => {
|
||||
mockedApi.get.mockResolvedValue({ data: [job(9, 'Saved Role', 'Saved')] } as any);
|
||||
|
||||
renderBoard();
|
||||
|
||||
await screen.findByText('Saved Role');
|
||||
fireEvent.click(screen.getByRole('button', { name: '' }) ?? screen.getAllByRole('button')[0]);
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Change status for Saved Role' }));
|
||||
|
||||
const menu = await screen.findByRole('menu');
|
||||
// Drag is coarse; the menu is where Ghosted and Withdrawn are reachable at all.
|
||||
|
||||
Reference in New Issue
Block a user