diff --git a/frontend/src/components/HomeComponents/Tasks/__tests__/Tasks.test.tsx b/frontend/src/components/HomeComponents/Tasks/__tests__/Tasks.test.tsx
index d650a64b..3c4a79fc 100644
--- a/frontend/src/components/HomeComponents/Tasks/__tests__/Tasks.test.tsx
+++ b/frontend/src/components/HomeComponents/Tasks/__tests__/Tasks.test.tsx
@@ -44,15 +44,20 @@ jest.mock('../tasks-utils', () => {
});
jest.mock('@/components/ui/multi-select', () => ({
- MultiSelectFilter: jest.fn(({ title, completionStats }) => (
-
+ MultiSelectFilter: jest.fn(({ id, title, completionStats }) => (
+
+
)),
}));
@@ -509,27 +514,6 @@ describe('Tasks Component', () => {
});
});
- describe('Overdue UI', () => {
- test('shows red background on task ID and Overdue badge for overdue tasks', async () => {
- render();
-
- await screen.findByText('Task 12');
-
- const dropdown = screen.getByLabelText('Show:');
- fireEvent.change(dropdown, { target: { value: '20' } });
-
- const task1Description = screen.getByText('Task 1');
- const row = task1Description.closest('tr');
- const idElement = row?.querySelector('span');
-
- expect(idElement).toHaveClass('bg-red-600/80');
- fireEvent.click(idElement!);
-
- const overdueBadge = await screen.findByText('Overdue');
- expect(overdueBadge).toBeInTheDocument();
- });
- });
-
describe('Selection Logic', () => {
it('adds a task UUID to selectedTaskUUIDs when an individual checkbox is clicked', async () => {
render();
@@ -843,129 +827,150 @@ describe('Tasks Component', () => {
});
});
- test('shows "overdue" in status filter options', async () => {
- render();
+ describe('Overdue', () => {
+ test('shows "overdue" in status filter options', async () => {
+ render();
- expect(await screen.findByText('Mocked BottomBar')).toBeInTheDocument();
+ expect(await screen.findByText('Mocked BottomBar')).toBeInTheDocument();
- const multiSelectFilter = require('@/components/ui/multi-select');
+ const multiSelectFilter = require('@/components/ui/multi-select');
- expect(multiSelectFilter.MultiSelectFilter).toHaveBeenCalledWith(
- expect.objectContaining({
- title: 'Status',
- options: expect.arrayContaining(['overdue']),
- }),
- {}
- );
- });
+ expect(multiSelectFilter.MultiSelectFilter).toHaveBeenCalledWith(
+ expect.objectContaining({
+ title: 'Status',
+ options: expect.arrayContaining(['overdue']),
+ }),
+ {}
+ );
+ });
- test('filters tasks to show only overdue tasks when status "overdue" is selected', async () => {
- const MultiSelectFilter =
- require('@/components/ui/multi-select').MultiSelectFilter;
+ test('filters tasks to show only overdue tasks when status "overdue" is selected', async () => {
+ const MultiSelectFilter =
+ require('@/components/ui/multi-select').MultiSelectFilter;
- MultiSelectFilter.mockImplementation(({ title }: { title: string }) => {
- return Mocked MultiSelect: {title}
;
- });
+ render();
- render();
+ await waitFor(async () => {
+ expect(await screen.findByText('Task 12')).toBeInTheDocument();
+ });
- await waitFor(async () => {
- expect(await screen.findByText('Task 12')).toBeInTheDocument();
- });
+ const lastCall = MultiSelectFilter.mock.calls.find(
+ (call: any[]) => call[0].title === 'Status'
+ );
- const lastCall = MultiSelectFilter.mock.calls.find(
- (call: any[]) => call[0].title === 'Status'
- );
+ const onSelectionChange = lastCall[0].onSelectionChange;
- const onSelectionChange = lastCall[0].onSelectionChange;
+ act(() => {
+ onSelectionChange(['overdue']);
+ });
- act(() => {
- onSelectionChange(['overdue']);
+ const overdueTask = screen.getByText('Task 1');
+ expect(overdueTask).toBeInTheDocument();
+ expect(screen.queryByText('Task 2')).not.toBeInTheDocument();
});
- const overdueTask = screen.getByText('Task 1');
- expect(overdueTask).toBeInTheDocument();
- expect(screen.queryByText('Task 2')).not.toBeInTheDocument();
- });
+ test('shows red background on task ID and Overdue badge for overdue tasks', async () => {
+ render();
- test('shows "O" badge for overdue tasks in status column', async () => {
- render();
+ await screen.findByText('Task 12');
- await screen.findByText('Task 12');
+ const dropdown = screen.getByLabelText('Show:');
+ fireEvent.change(dropdown, { target: { value: '20' } });
- const dropdown = screen.getByLabelText('Show:');
- fireEvent.change(dropdown, { target: { value: '20' } });
+ const task1Description = screen.getByText('Task 1');
+ const row = task1Description.closest('tr');
+ const idElement = row?.querySelector('span');
- const row = screen.getByText('Task 1').closest('tr')!;
- const statusCell = within(row).getByText('O');
+ expect(idElement).toHaveClass('bg-red-600/80');
+ fireEvent.click(idElement!);
- expect(statusCell).toBeInTheDocument();
- });
+ const overdueBadge = await screen.findByText('Overdue');
+ expect(overdueBadge).toBeInTheDocument();
+ });
- test('does not show "O" badge for non-overdue pending tasks', async () => {
- render();
+ test('shows "O" badge for overdue tasks in status column', async () => {
+ render();
- await screen.findByText('Task 12');
+ await screen.findByText('Task 12');
- const dropdown = screen.getByLabelText('Show:');
- fireEvent.change(dropdown, { target: { value: '20' } });
+ const dropdown = screen.getByLabelText('Show:');
+ fireEvent.change(dropdown, { target: { value: '20' } });
- expect(await screen.findByText('Task 2')).toBeInTheDocument();
+ const row = screen.getByText('Task 1').closest('tr')!;
+ const statusCell = within(row).getByText('O');
- const row = screen.getByText('Task 2').closest('tr')!;
- const statusCell = within(row).getByText('P');
+ expect(statusCell).toBeInTheDocument();
+ });
- expect(statusCell).toBeInTheDocument();
- });
+ test('does not show "O" badge for non-overdue pending tasks', async () => {
+ render();
- test('overdue tasks appear at the top of the list', async () => {
- render();
+ await screen.findByText('Task 12');
- await screen.findByText('Task 12');
+ const dropdown = screen.getByLabelText('Show:');
+ fireEvent.change(dropdown, { target: { value: '20' } });
- const dropdown = screen.getByLabelText('Show:');
- fireEvent.change(dropdown, { target: { value: '20' } });
+ expect(await screen.findByText('Task 2')).toBeInTheDocument();
- const firstRow = screen.getAllByRole('row')[1];
- expect(within(firstRow).getByText('Task 1')).toBeInTheDocument();
- });
+ const row = screen.getByText('Task 2').closest('tr')!;
+ const statusCell = within(row).getByText('P');
- test('project dropdown lists existing projects and create-new option', async () => {
- render();
+ expect(statusCell).toBeInTheDocument();
+ });
- expect(await screen.findByText('Task 1')).toBeInTheDocument();
+ test('overdue tasks appear at the top of the list', async () => {
+ render();
- const addTaskButton = screen.getByRole('button', { name: /add task/i });
- fireEvent.click(addTaskButton);
+ await screen.findByText('Task 12');
- const projectSelect = await screen.findByTestId('project-select');
- expect(
- within(projectSelect).getByText('Select a project')
- ).toBeInTheDocument();
- expect(within(projectSelect).getByText('Engineering')).toBeInTheDocument();
- expect(within(projectSelect).getByText('ProjectA')).toBeInTheDocument();
- expect(
- within(projectSelect).getByText('+ Create new project…')
- ).toBeInTheDocument();
- });
+ const dropdown = screen.getByLabelText('Show:');
+ fireEvent.change(dropdown, { target: { value: '20' } });
- test('selecting "+ Create new project…" reveals inline input', async () => {
- render();
+ const firstRow = screen.getAllByRole('row')[1];
+ expect(within(firstRow).getByText('Task 1')).toBeInTheDocument();
+ });
+ });
- expect(await screen.findByText('Task 1')).toBeInTheDocument();
+ describe('Add Task Dialog', () => {
+ test('project dropdown lists existing projects and create-new option', async () => {
+ render();
- fireEvent.click(screen.getByRole('button', { name: /add task/i }));
+ expect(await screen.findByText('Task 1')).toBeInTheDocument();
- const projectSelect = await screen.findByTestId('project-select');
- fireEvent.change(projectSelect, { target: { value: '__CREATE_NEW__' } }); // Empty string triggers "create new project" mode
+ const addTaskButton = screen.getByRole('button', { name: /add task/i });
+ fireEvent.click(addTaskButton);
- const newProjectInput =
- await screen.findByPlaceholderText('New project name');
- fireEvent.change(newProjectInput, {
- target: { value: 'My Fresh Project' },
+ const projectSelect = await screen.findByTestId('project-select');
+ expect(
+ within(projectSelect).getByText('Select a project')
+ ).toBeInTheDocument();
+ expect(
+ within(projectSelect).getByText('Engineering')
+ ).toBeInTheDocument();
+ expect(within(projectSelect).getByText('ProjectA')).toBeInTheDocument();
+ expect(
+ within(projectSelect).getByText('+ Create new project…')
+ ).toBeInTheDocument();
});
- expect(newProjectInput).toHaveValue('My Fresh Project');
+ test('selecting "+ Create new project…" reveals inline input', async () => {
+ render();
+
+ expect(await screen.findByText('Task 1')).toBeInTheDocument();
+
+ fireEvent.click(screen.getByRole('button', { name: /add task/i }));
+
+ const projectSelect = await screen.findByTestId('project-select');
+ fireEvent.change(projectSelect, { target: { value: '__CREATE_NEW__' } }); // Empty string triggers "create new project" mode
+
+ const newProjectInput =
+ await screen.findByPlaceholderText('New project name');
+ fireEvent.change(newProjectInput, {
+ target: { value: 'My Fresh Project' },
+ });
+
+ expect(newProjectInput).toHaveValue('My Fresh Project');
+ });
});
// Task Dependencies Tests
@@ -1006,574 +1011,585 @@ describe('Tasks Component', () => {
});
});
- test('shows red border when task is marked as completed', async () => {
- render();
+ describe('Unsync', () => {
+ test('shows red border when task is marked as completed', async () => {
+ render();
- await waitFor(async () => {
- expect(await screen.findByText('Task 12')).toBeInTheDocument();
- });
+ await waitFor(async () => {
+ expect(await screen.findByText('Task 12')).toBeInTheDocument();
+ });
- const task12 = screen.getByText('Task 12');
- fireEvent.click(task12);
+ const task12 = screen.getByText('Task 12');
+ fireEvent.click(task12);
- await waitFor(() => {
- const completeButton = screen.getByLabelText('complete task');
- fireEvent.click(completeButton);
- });
+ await waitFor(() => {
+ const completeButton = screen.getByLabelText('complete task');
+ fireEvent.click(completeButton);
+ });
- const yesButton = screen.getAllByText('Yes')[0];
- fireEvent.click(yesButton);
+ const yesButton = screen.getAllByText('Yes')[0];
+ fireEvent.click(yesButton);
- await waitFor(() => {
- const row = screen.getByTestId('task-row-12');
- expect(row).toHaveClass('border-l-red-500');
+ await waitFor(() => {
+ const row = screen.getByTestId('task-row-12');
+ expect(row).toHaveClass('border-l-red-500');
+ });
});
- });
- test('shows red border when task is deleted', async () => {
- render();
+ test('shows red border when task is deleted', async () => {
+ render();
- await waitFor(async () => {
- expect(await screen.findByText('Task 12')).toBeInTheDocument();
- });
+ await waitFor(async () => {
+ expect(await screen.findByText('Task 12')).toBeInTheDocument();
+ });
- const task12 = screen.getByText('Task 12');
- fireEvent.click(task12);
+ const task12 = screen.getByText('Task 12');
+ fireEvent.click(task12);
- await waitFor(() => {
- const deleteButton = screen.getByLabelText('delete task');
- fireEvent.click(deleteButton);
- });
+ await waitFor(() => {
+ const deleteButton = screen.getByLabelText('delete task');
+ fireEvent.click(deleteButton);
+ });
- await waitFor(() => {
- const yesButtons = screen.getAllByText('Yes');
- if (yesButtons.length > 0) fireEvent.click(yesButtons[0]);
- });
+ await waitFor(() => {
+ const yesButtons = screen.getAllByText('Yes');
+ if (yesButtons.length > 0) fireEvent.click(yesButtons[0]);
+ });
- await waitFor(() => {
- const row = screen.getByTestId('task-row-12');
- expect(row).toHaveClass('border-l-red-500');
+ await waitFor(() => {
+ const row = screen.getByTestId('task-row-12');
+ expect(row).toHaveClass('border-l-red-500');
+ });
});
- });
- test('shows unsynced count after bulk delete', async () => {
- render();
+ test('shows unsynced count after bulk delete', async () => {
+ render();
- await screen.findByText('Task 1');
- const checkboxes = screen.getAllByRole('checkbox');
+ await screen.findByText('Task 1');
+ const checkboxes = screen.getAllByRole('checkbox');
- fireEvent.click(checkboxes[1]);
- fireEvent.click(checkboxes[2]);
+ fireEvent.click(checkboxes[1]);
+ fireEvent.click(checkboxes[2]);
- const deleteBtn = screen.getByText(/Delete 2 Tasks/i);
- fireEvent.click(deleteBtn);
+ const deleteBtn = screen.getByText(/Delete 2 Tasks/i);
+ fireEvent.click(deleteBtn);
- const yesButton = await screen.findByText('Yes');
- fireEvent.click(yesButton);
+ const yesButton = await screen.findByText('Yes');
+ fireEvent.click(yesButton);
- await waitFor(() => {
- const syncButton = document.getElementById('sync-task');
- expect(within(syncButton!).getByText('2')).toBeInTheDocument();
+ await waitFor(() => {
+ const syncButton = document.getElementById('sync-task');
+ expect(within(syncButton!).getByText('2')).toBeInTheDocument();
+ });
});
- });
-
- test('shows unsynced count after bulk complete', async () => {
- render();
- await screen.findByText('Task 1');
- const checkboxes = screen.getAllByRole('checkbox');
-
- fireEvent.click(checkboxes[1]);
- fireEvent.click(checkboxes[2]);
+ test('shows unsynced count after bulk complete', async () => {
+ render();
- const bulkButton = screen.getByTestId('bulk-complete-btn');
- fireEvent.click(bulkButton);
+ await screen.findByText('Task 1');
+ const checkboxes = screen.getAllByRole('checkbox');
- const yesButton = await screen.findByText('Yes');
- fireEvent.click(yesButton);
+ fireEvent.click(checkboxes[1]);
+ fireEvent.click(checkboxes[2]);
- await waitFor(() => {
- const syncButton = document.getElementById('sync-task');
- expect(within(syncButton!).getByText('2')).toBeInTheDocument();
- });
- });
+ const bulkButton = screen.getByTestId('bulk-complete-btn');
+ fireEvent.click(bulkButton);
- test('shows red border when task description is edited', async () => {
- render();
+ const yesButton = await screen.findByText('Yes');
+ fireEvent.click(yesButton);
- await waitFor(async () => {
- expect(await screen.findByText('Task 12')).toBeInTheDocument();
+ await waitFor(() => {
+ const syncButton = document.getElementById('sync-task');
+ expect(within(syncButton!).getByText('2')).toBeInTheDocument();
+ });
});
- const task12 = screen.getByText('Task 12');
+ test('shows red border when task description is edited', async () => {
+ render();
- fireEvent.click(task12);
+ await waitFor(async () => {
+ expect(await screen.findByText('Task 12')).toBeInTheDocument();
+ });
- await waitFor(() => {
- expect(screen.getByText('Description:')).toBeInTheDocument();
- });
+ const task12 = screen.getByText('Task 12');
- const descriptionLabel = screen.getByText('Description:');
- const descRow = descriptionLabel.closest('tr') as HTMLElement;
- const editButton = within(descRow).getByLabelText('edit');
+ fireEvent.click(task12);
- fireEvent.click(editButton);
+ await waitFor(() => {
+ expect(screen.getByText('Description:')).toBeInTheDocument();
+ });
- const input = await screen.findByDisplayValue('Task 12');
+ const descriptionLabel = screen.getByText('Description:');
+ const descRow = descriptionLabel.closest('tr') as HTMLElement;
+ const editButton = within(descRow).getByLabelText('edit');
- fireEvent.change(input, { target: { value: 'Updated Task 12' } });
+ fireEvent.click(editButton);
- const saveButton = screen.getByLabelText('save');
+ const input = await screen.findByDisplayValue('Task 12');
- fireEvent.click(saveButton);
+ fireEvent.change(input, { target: { value: 'Updated Task 12' } });
- await waitFor(() => {
- const row = screen.getByTestId('task-row-12');
- expect(row).toHaveClass('border-l-red-500');
- });
- });
+ const saveButton = screen.getByLabelText('save');
- test('shows red border when task project is edited', async () => {
- render();
+ fireEvent.click(saveButton);
- await waitFor(async () => {
- expect(await screen.findByText('Task 12')).toBeInTheDocument();
+ await waitFor(() => {
+ const row = screen.getByTestId('task-row-12');
+ expect(row).toHaveClass('border-l-red-500');
+ });
});
- const task12 = screen.getByText('Task 12');
- fireEvent.click(task12);
+ test('shows red border when task project is edited', async () => {
+ render();
- await waitFor(() => {
- expect(screen.getByText('Project:')).toBeInTheDocument();
- });
+ await waitFor(async () => {
+ expect(await screen.findByText('Task 12')).toBeInTheDocument();
+ });
- const projectLabel = screen.getByText('Project:');
- const projectRow = projectLabel.closest('tr') as HTMLElement;
- const editButton = within(projectRow).getByLabelText('edit');
- fireEvent.click(editButton);
+ const task12 = screen.getByText('Task 12');
+ fireEvent.click(task12);
- const projectSelect = await screen.findByTestId('project-select');
- fireEvent.change(projectSelect, { target: { value: 'ProjectA' } });
+ await waitFor(() => {
+ expect(screen.getByText('Project:')).toBeInTheDocument();
+ });
- fireEvent.keyDown(document.body, { key: 'Escape' });
+ const projectLabel = screen.getByText('Project:');
+ const projectRow = projectLabel.closest('tr') as HTMLElement;
+ const editButton = within(projectRow).getByLabelText('edit');
+ fireEvent.click(editButton);
- await waitFor(() => {
- const row = screen.getByTestId('task-row-12');
- expect(row).toHaveClass('border-l-red-500');
- });
- });
+ const projectSelect = await screen.findByTestId('project-select');
+ fireEvent.change(projectSelect, { target: { value: 'ProjectA' } });
- test.each([
- ['Wait', 'Wait:', 'Select wait date and time'],
- ['End', 'End:', 'Select end date and time'],
- ['Due', 'Due:', 'Select due date and time'],
- ['Start', 'Start:', 'Select start date and time'],
- ['Entry', 'Entry:', 'Select entry date and time'],
- ])('shows red when task %s date is edited', async (_, label, placeholder) => {
- render();
+ fireEvent.keyDown(document.body, { key: 'Escape' });
- await waitFor(async () => {
- expect(await screen.findByText('Task 12')).toBeInTheDocument();
+ await waitFor(() => {
+ const row = screen.getByTestId('task-row-12');
+ expect(row).toHaveClass('border-l-red-500');
+ });
});
- const task12 = screen.getByText('Task 12');
- fireEvent.click(task12);
+ test.each([
+ ['Wait', 'Wait:', 'Select wait date and time'],
+ ['End', 'End:', 'Select end date and time'],
+ ['Due', 'Due:', 'Select due date and time'],
+ ['Start', 'Start:', 'Select start date and time'],
+ ['Entry', 'Entry:', 'Select entry date and time'],
+ ])(
+ 'shows red when task %s date is edited',
+ async (_, label, placeholder) => {
+ render();
- await waitFor(() => {
- expect(screen.getByText(label)).toBeInTheDocument();
- });
+ await waitFor(async () => {
+ expect(await screen.findByText('Task 12')).toBeInTheDocument();
+ });
- const dateLabel = screen.getByText(label);
- const dateRow = dateLabel.closest('tr') as HTMLElement;
+ const task12 = screen.getByText('Task 12');
+ fireEvent.click(task12);
- const editButton = within(dateRow).getByLabelText('edit');
- fireEvent.click(editButton);
+ await waitFor(() => {
+ expect(screen.getByText(label)).toBeInTheDocument();
+ });
- const dateButton = within(dateRow).getByText(placeholder).closest('button');
- fireEvent.click(dateButton!);
+ const dateLabel = screen.getByText(label);
+ const dateRow = dateLabel.closest('tr') as HTMLElement;
- await waitFor(() => {
- expect(screen.getByRole('dialog')).toBeInTheDocument();
- });
+ const editButton = within(dateRow).getByLabelText('edit');
+ fireEvent.click(editButton);
- const dialog = screen.getByRole('dialog');
- const day15 = within(dialog).getAllByText('15')[0];
- fireEvent.click(day15);
+ const dateButton = within(dateRow)
+ .getByText(placeholder)
+ .closest('button');
+ fireEvent.click(dateButton!);
- const saveButton = screen.getByLabelText('save');
- fireEvent.click(saveButton);
+ await waitFor(() => {
+ expect(screen.getByRole('dialog')).toBeInTheDocument();
+ });
- await waitFor(() => {
- const row = screen.getByTestId('task-row-12');
- expect(row).toHaveClass('border-l-red-500');
- });
- });
+ const dialog = screen.getByRole('dialog');
+ const day15 = within(dialog).getAllByText('15')[0];
+ fireEvent.click(day15);
- test('shows red border when task priority is edited', async () => {
- render();
+ const saveButton = screen.getByLabelText('save');
+ fireEvent.click(saveButton);
- await waitFor(async () => {
- expect(await screen.findByText('Task 12')).toBeInTheDocument();
- });
+ await waitFor(() => {
+ const row = screen.getByTestId('task-row-12');
+ expect(row).toHaveClass('border-l-red-500');
+ });
+ }
+ );
- const task12 = screen.getByText('Task 12');
- fireEvent.click(task12);
+ test('shows red border when task priority is edited', async () => {
+ render();
- await waitFor(() => {
- expect(screen.getByText('Priority:')).toBeInTheDocument();
- });
+ await waitFor(async () => {
+ expect(await screen.findByText('Task 12')).toBeInTheDocument();
+ });
- const priorityLabel = screen.getByText('Priority:');
- const priorityRow = priorityLabel.closest('tr') as HTMLElement;
+ const task12 = screen.getByText('Task 12');
+ fireEvent.click(task12);
- const editButton = within(priorityRow).getByLabelText('edit');
- fireEvent.click(editButton);
+ await waitFor(() => {
+ expect(screen.getByText('Priority:')).toBeInTheDocument();
+ });
- const select = within(priorityRow).getByTestId('project-select');
- fireEvent.change(select, { target: { value: 'H' } });
+ const priorityLabel = screen.getByText('Priority:');
+ const priorityRow = priorityLabel.closest('tr') as HTMLElement;
- const saveButton = screen.getByLabelText('save');
- fireEvent.click(saveButton);
+ const editButton = within(priorityRow).getByLabelText('edit');
+ fireEvent.click(editButton);
- await waitFor(() => {
- const row = screen.getByTestId('task-row-12');
- expect(row).toHaveClass('border-l-red-500');
- });
- });
+ const select = within(priorityRow).getByTestId('project-select');
+ fireEvent.change(select, { target: { value: 'H' } });
- test('shows red border when task dependencies are edited', async () => {
- render();
+ const saveButton = screen.getByLabelText('save');
+ fireEvent.click(saveButton);
- await waitFor(async () => {
- expect(await screen.findByText('Task 12')).toBeInTheDocument();
+ await waitFor(() => {
+ const row = screen.getByTestId('task-row-12');
+ expect(row).toHaveClass('border-l-red-500');
+ });
});
- const task12 = screen.getByText('Task 12');
- fireEvent.click(task12);
+ test('shows red border when task dependencies are edited', async () => {
+ render();
- await waitFor(() => {
- expect(screen.getByText('Depends:')).toBeInTheDocument();
- });
+ await waitFor(async () => {
+ expect(await screen.findByText('Task 12')).toBeInTheDocument();
+ });
- const dependsLabel = screen.getByText('Depends:');
- const dependsRow = dependsLabel.closest('tr') as HTMLElement;
+ const task12 = screen.getByText('Task 12');
+ fireEvent.click(task12);
- const editButton = within(dependsRow).getByLabelText('edit');
- fireEvent.click(editButton);
+ await waitFor(() => {
+ expect(screen.getByText('Depends:')).toBeInTheDocument();
+ });
- const addDependecyButton = within(dependsRow)
- .getByText('Add Dependency')
- .closest('button');
- fireEvent.click(addDependecyButton!);
+ const dependsLabel = screen.getByText('Depends:');
+ const dependsRow = dependsLabel.closest('tr') as HTMLElement;
- const dropdown = within(dependsRow).getByTestId('dependency-dropdown');
+ const editButton = within(dependsRow).getByLabelText('edit');
+ fireEvent.click(editButton);
- fireEvent.click(within(dropdown).getByText('Task 11'));
- fireEvent.click(within(dropdown).getByText('Task 10'));
+ const addDependecyButton = within(dependsRow)
+ .getByText('Add Dependency')
+ .closest('button');
+ fireEvent.click(addDependecyButton!);
- const saveButton = screen.getByLabelText('save');
- fireEvent.click(saveButton);
+ const dropdown = within(dependsRow).getByTestId('dependency-dropdown');
- await waitFor(() => {
- const row = screen.getByTestId('task-row-12');
- expect(row).toHaveClass('border-l-red-500');
- });
- });
+ fireEvent.click(within(dropdown).getByText('Task 11'));
+ fireEvent.click(within(dropdown).getByText('Task 10'));
- test('shows red border when task tags are edited', async () => {
- render();
+ const saveButton = screen.getByLabelText('save');
+ fireEvent.click(saveButton);
- await waitFor(async () => {
- expect(await screen.findByText('Task 12')).toBeInTheDocument();
+ await waitFor(() => {
+ const row = screen.getByTestId('task-row-12');
+ expect(row).toHaveClass('border-l-red-500');
+ });
});
- const task12 = screen.getByText('Task 12');
- fireEvent.click(task12);
+ test('shows red border when task tags are edited', async () => {
+ render();
- await waitFor(() => {
- expect(screen.getByText('Tags:')).toBeInTheDocument();
- });
+ await waitFor(async () => {
+ expect(await screen.findByText('Task 12')).toBeInTheDocument();
+ });
- const tagsLabel = screen.getByText('Tags:');
- const tagsRow = tagsLabel.closest('tr') as HTMLElement;
+ const task12 = screen.getByText('Task 12');
+ fireEvent.click(task12);
- const editButton = within(tagsRow).getByLabelText('edit');
- fireEvent.click(editButton);
+ await waitFor(() => {
+ expect(screen.getByText('Tags:')).toBeInTheDocument();
+ });
- const tagSelectButton = await screen.findByRole('button', {
- name: /select items/i,
- });
- fireEvent.click(tagSelectButton);
+ const tagsLabel = screen.getByText('Tags:');
+ const tagsRow = tagsLabel.closest('tr') as HTMLElement;
- const editInput = await screen.findByPlaceholderText('Search or create...');
+ const editButton = within(tagsRow).getByLabelText('edit');
+ fireEvent.click(editButton);
- fireEvent.change(editInput, { target: { value: 'unsyncedtag' } });
- fireEvent.keyDown(editInput, { key: 'Enter', code: 'Enter' });
+ const tagSelectButton = await screen.findByRole('button', {
+ name: /select items/i,
+ });
+ fireEvent.click(tagSelectButton);
- const saveButton = screen.getByLabelText('Save items');
- fireEvent.click(saveButton);
+ const editInput = await screen.findByPlaceholderText(
+ 'Search or create...'
+ );
- await waitFor(() => {
- const row = screen.getByTestId('task-row-12');
- expect(row).toHaveClass('border-l-red-500');
- });
- });
+ fireEvent.change(editInput, { target: { value: 'unsyncedtag' } });
+ fireEvent.keyDown(editInput, { key: 'Enter', code: 'Enter' });
- test('shows red border when task recur is edited', async () => {
- render();
+ const saveButton = screen.getByLabelText('Save items');
+ fireEvent.click(saveButton);
- await waitFor(async () => {
- expect(await screen.findByText('Task 12')).toBeInTheDocument();
+ await waitFor(() => {
+ const row = screen.getByTestId('task-row-12');
+ expect(row).toHaveClass('border-l-red-500');
+ });
});
- const task12 = screen.getByText('Task 12');
- fireEvent.click(task12);
+ test('shows red border when task recur is edited', async () => {
+ render();
- await waitFor(() => {
- expect(screen.getByText('Recur:')).toBeInTheDocument();
- });
+ await waitFor(async () => {
+ expect(await screen.findByText('Task 12')).toBeInTheDocument();
+ });
- const recurLabel = screen.getByText('Recur:');
- const recurRow = recurLabel.closest('tr') as HTMLElement;
+ const task12 = screen.getByText('Task 12');
+ fireEvent.click(task12);
- const editButton = within(recurRow).getByLabelText('edit');
- fireEvent.click(editButton);
+ await waitFor(() => {
+ expect(screen.getByText('Recur:')).toBeInTheDocument();
+ });
- const select = within(recurRow).getByTestId('project-select');
- fireEvent.change(select, { target: { value: 'weekly' } });
+ const recurLabel = screen.getByText('Recur:');
+ const recurRow = recurLabel.closest('tr') as HTMLElement;
- const saveButton = screen.getByLabelText('save');
- fireEvent.click(saveButton);
+ const editButton = within(recurRow).getByLabelText('edit');
+ fireEvent.click(editButton);
- await waitFor(() => {
- const row = screen.getByTestId('task-row-12');
- expect(row).toHaveClass('border-l-red-500');
- });
- });
+ const select = within(recurRow).getByTestId('project-select');
+ fireEvent.change(select, { target: { value: 'weekly' } });
- test('shows and updates notification badge count on Sync button', async () => {
- render();
+ const saveButton = screen.getByLabelText('save');
+ fireEvent.click(saveButton);
- await waitFor(async () => {
- expect(await screen.findByText('Task 12')).toBeInTheDocument();
+ await waitFor(() => {
+ const row = screen.getByTestId('task-row-12');
+ expect(row).toHaveClass('border-l-red-500');
+ });
});
- const task12 = screen.getByText('Task 12');
- fireEvent.click(task12);
+ test('shows and updates notification badge count on Sync button', async () => {
+ render();
- await waitFor(() => {
- const completeButton = screen.getByLabelText('complete task');
- fireEvent.click(completeButton);
- });
+ await waitFor(async () => {
+ expect(await screen.findByText('Task 12')).toBeInTheDocument();
+ });
- const yesButton = screen.getAllByText('Yes')[0];
- fireEvent.click(yesButton);
+ const task12 = screen.getByText('Task 12');
+ fireEvent.click(task12);
- await waitFor(() => {
- const row = screen.getByTestId('task-row-12');
- expect(row).toHaveClass('border-l-red-500');
- });
+ await waitFor(() => {
+ const completeButton = screen.getByLabelText('complete task');
+ fireEvent.click(completeButton);
+ });
- const syncButtons = screen.getAllByText('Sync');
- const syncBtnContainer = syncButtons[0].closest('button');
+ const yesButton = screen.getAllByText('Yes')[0];
+ fireEvent.click(yesButton);
- if (syncBtnContainer) {
- expect(within(syncBtnContainer).getByText('1')).toBeInTheDocument();
- } else {
- throw new Error('Sync button not found');
- }
- });
+ await waitFor(() => {
+ const row = screen.getByTestId('task-row-12');
+ expect(row).toHaveClass('border-l-red-500');
+ });
- test('clears red border after sync', async () => {
- render();
+ const syncButtons = screen.getAllByText('Sync');
+ const syncBtnContainer = syncButtons[0].closest('button');
- await waitFor(async () => {
- expect(await screen.findByText('Task 12')).toBeInTheDocument();
+ if (syncBtnContainer) {
+ expect(within(syncBtnContainer).getByText('1')).toBeInTheDocument();
+ } else {
+ throw new Error('Sync button not found');
+ }
});
- const task12 = screen.getByText('Task 12');
- fireEvent.click(task12);
-
- await waitFor(() => {
- const completeButton = screen.getByLabelText('complete task');
- fireEvent.click(completeButton);
- });
+ test('clears red border after sync', async () => {
+ render();
- const yesButton = screen.getAllByText('Yes')[0];
- fireEvent.click(yesButton);
+ await waitFor(async () => {
+ expect(await screen.findByText('Task 12')).toBeInTheDocument();
+ });
- await waitFor(() => {
- const row = screen.getByTestId('task-row-12');
- expect(row).toHaveClass('border-l-red-500');
- });
+ const task12 = screen.getByText('Task 12');
+ fireEvent.click(task12);
- const hooks = require('../hooks');
- hooks.fetchTaskwarriorTasks.mockResolvedValueOnce([
- {
- id: 12,
- description: 'Task 12',
- status: 'completed',
- project: 'ProjectA',
- tags: ['tag1'],
- uuid: 'uuid-12',
- },
- ]);
+ await waitFor(() => {
+ const completeButton = screen.getByLabelText('complete task');
+ fireEvent.click(completeButton);
+ });
- const syncButtons = screen.getAllByText('Sync');
- fireEvent.click(syncButtons[0]);
+ const yesButton = screen.getAllByText('Yes')[0];
+ fireEvent.click(yesButton);
- await waitFor(() => {
- const row = screen.getByTestId('task-row-12');
- expect(row).not.toHaveClass('border-l-red-500');
- });
- });
+ await waitFor(() => {
+ const row = screen.getByTestId('task-row-12');
+ expect(row).toHaveClass('border-l-red-500');
+ });
- test('calculates and passes project completion stats to MultiSelectFilter', async () => {
- render();
+ const hooks = require('../hooks');
+ hooks.fetchTaskwarriorTasks.mockResolvedValueOnce([
+ {
+ id: 12,
+ description: 'Task 12',
+ status: 'completed',
+ project: 'ProjectA',
+ tags: ['tag1'],
+ uuid: 'uuid-12',
+ },
+ ]);
+
+ const syncButtons = screen.getAllByText('Sync');
+ fireEvent.click(syncButtons[0]);
- await waitFor(async () => {
- expect(await screen.findByText('Task 1')).toBeInTheDocument();
+ await waitFor(() => {
+ const row = screen.getByTestId('task-row-12');
+ expect(row).not.toHaveClass('border-l-red-500');
+ });
});
+ });
- const { MultiSelectFilter } = require('@/components/ui/multi-select');
+ describe('Completion Stats', () => {
+ test('calculates and passes project completion stats to MultiSelectFilter', async () => {
+ render();
- // Find the Projects filter call
- const projectsFilterCall = MultiSelectFilter.mock.calls.find(
- (call: any) => call[0].title === 'Projects'
- );
+ await waitFor(async () => {
+ expect(await screen.findByText('Task 1')).toBeInTheDocument();
+ });
- expect(projectsFilterCall).toBeDefined();
- expect(projectsFilterCall[0].completionStats).toBeDefined();
+ const { MultiSelectFilter } = require('@/components/ui/multi-select');
- const stats = projectsFilterCall[0].completionStats;
+ // Find the Projects filter call
+ const projectsFilterCall = MultiSelectFilter.mock.calls.find(
+ (call: any) => call[0].title === 'Projects'
+ );
- // ProjectA has tasks: 1,3,5,7,9,11 (pending) + task 16 (completed) = 1 completed out of 7 total
- expect(stats['ProjectA']).toBeDefined();
- expect(stats['ProjectA'].completed).toBeGreaterThanOrEqual(1);
- expect(stats['ProjectA'].total).toBeGreaterThanOrEqual(1);
- expect(stats['ProjectA'].percentage).toBeGreaterThanOrEqual(0);
- expect(stats['ProjectA'].percentage).toBeLessThanOrEqual(100);
+ expect(projectsFilterCall).toBeDefined();
+ expect(projectsFilterCall[0].completionStats).toBeDefined();
- // ProjectB has tasks: 2,4,6,8,10,12 (pending) + task 17 (deleted) = 0 completed
- expect(stats['ProjectB']).toBeDefined();
- expect(stats['ProjectB'].total).toBeGreaterThanOrEqual(1);
- });
+ const stats = projectsFilterCall[0].completionStats;
- test('calculates and passes tag completion stats to MultiSelectFilter', async () => {
- render();
+ // ProjectA has tasks: 1,3,5,7,9,11 (pending) + task 16 (completed) = 1 completed out of 7 total
+ expect(stats['ProjectA']).toBeDefined();
+ expect(stats['ProjectA'].completed).toBeGreaterThanOrEqual(1);
+ expect(stats['ProjectA'].total).toBeGreaterThanOrEqual(1);
+ expect(stats['ProjectA'].percentage).toBeGreaterThanOrEqual(0);
+ expect(stats['ProjectA'].percentage).toBeLessThanOrEqual(100);
- await waitFor(async () => {
- expect(await screen.findByText('Task 1')).toBeInTheDocument();
+ // ProjectB has tasks: 2,4,6,8,10,12 (pending) + task 17 (deleted) = 0 completed
+ expect(stats['ProjectB']).toBeDefined();
+ expect(stats['ProjectB'].total).toBeGreaterThanOrEqual(1);
});
- const { MultiSelectFilter } = require('@/components/ui/multi-select');
+ test('calculates and passes tag completion stats to MultiSelectFilter', async () => {
+ render();
- // Find the Tags filter call
- const tagsFilterCall = MultiSelectFilter.mock.calls.find(
- (call: any) => call[0].title === 'Tags'
- );
+ await waitFor(async () => {
+ expect(await screen.findByText('Task 1')).toBeInTheDocument();
+ });
- expect(tagsFilterCall).toBeDefined();
- expect(tagsFilterCall[0].completionStats).toBeDefined();
+ const { MultiSelectFilter } = require('@/components/ui/multi-select');
- const stats = tagsFilterCall[0].completionStats;
+ // Find the Tags filter call
+ const tagsFilterCall = MultiSelectFilter.mock.calls.find(
+ (call: any) => call[0].title === 'Tags'
+ );
- // Verify stats structure
- Object.keys(stats).forEach((tag) => {
- expect(stats[tag]).toHaveProperty('completed');
- expect(stats[tag]).toHaveProperty('total');
- expect(stats[tag]).toHaveProperty('percentage');
- expect(typeof stats[tag].completed).toBe('number');
- expect(typeof stats[tag].total).toBe('number');
- expect(typeof stats[tag].percentage).toBe('number');
- expect(stats[tag].percentage).toBeGreaterThanOrEqual(0);
- expect(stats[tag].percentage).toBeLessThanOrEqual(100);
+ expect(tagsFilterCall).toBeDefined();
+ expect(tagsFilterCall[0].completionStats).toBeDefined();
+
+ const stats = tagsFilterCall[0].completionStats;
+
+ // Verify stats structure
+ Object.keys(stats).forEach((tag) => {
+ expect(stats[tag]).toHaveProperty('completed');
+ expect(stats[tag]).toHaveProperty('total');
+ expect(stats[tag]).toHaveProperty('percentage');
+ expect(typeof stats[tag].completed).toBe('number');
+ expect(typeof stats[tag].total).toBe('number');
+ expect(typeof stats[tag].percentage).toBe('number');
+ expect(stats[tag].percentage).toBeGreaterThanOrEqual(0);
+ expect(stats[tag].percentage).toBeLessThanOrEqual(100);
+ });
});
- });
- test('recalculates completion stats after sync', async () => {
- const hooks = require('../hooks');
-
- render();
+ test('recalculates completion stats after sync', async () => {
+ const hooks = require('../hooks');
- await waitFor(async () => {
- expect(await screen.findByText('Task 1')).toBeInTheDocument();
- });
+ render();
- const { MultiSelectFilter } = require('@/components/ui/multi-select');
-
- hooks.fetchTaskwarriorTasks.mockResolvedValueOnce([
- {
- id: 1,
- description: 'Task 1',
- status: 'completed',
- project: 'ProjectA',
- tags: ['tag1'],
- uuid: 'uuid-1',
- },
- {
- id: 2,
- description: 'Task 2',
- status: 'completed',
- project: 'ProjectB',
- tags: ['tag2'],
- uuid: 'uuid-2',
- },
- ]);
-
- MultiSelectFilter.mockClear();
-
- const syncButtons = screen.getAllByText('Sync');
- fireEvent.click(syncButtons[0]);
-
- await waitFor(() => {
- const projectsCall = MultiSelectFilter.mock.calls.find(
- (call: any) => call[0].title === 'Projects'
- );
- expect(projectsCall).toBeDefined();
- });
+ await waitFor(async () => {
+ expect(await screen.findByText('Task 1')).toBeInTheDocument();
+ });
- const updatedProjectsCall = MultiSelectFilter.mock.calls.find(
- (call: any) => call[0].title === 'Projects'
- );
+ const { MultiSelectFilter } = require('@/components/ui/multi-select');
+
+ hooks.fetchTaskwarriorTasks.mockResolvedValueOnce([
+ {
+ id: 1,
+ description: 'Task 1',
+ status: 'completed',
+ project: 'ProjectA',
+ tags: ['tag1'],
+ uuid: 'uuid-1',
+ },
+ {
+ id: 2,
+ description: 'Task 2',
+ status: 'completed',
+ project: 'ProjectB',
+ tags: ['tag2'],
+ uuid: 'uuid-2',
+ },
+ ]);
+
+ MultiSelectFilter.mockClear();
+
+ const syncButtons = screen.getAllByText('Sync');
+ fireEvent.click(syncButtons[0]);
- expect(updatedProjectsCall).toBeDefined();
- expect(updatedProjectsCall[0].completionStats).toBeDefined();
+ await waitFor(() => {
+ const projectsCall = MultiSelectFilter.mock.calls.find(
+ (call: any) => call[0].title === 'Projects'
+ );
+ expect(projectsCall).toBeDefined();
+ });
- const updatedStats = updatedProjectsCall[0].completionStats;
- expect(updatedStats['ProjectA']).toBeDefined();
- expect(updatedStats['ProjectB']).toBeDefined();
- });
+ const updatedProjectsCall = MultiSelectFilter.mock.calls.find(
+ (call: any) => call[0].title === 'Projects'
+ );
- test('completion stats structure is correct', async () => {
- render();
+ expect(updatedProjectsCall).toBeDefined();
+ expect(updatedProjectsCall[0].completionStats).toBeDefined();
- await waitFor(async () => {
- expect(await screen.findByText('Task 1')).toBeInTheDocument();
+ const updatedStats = updatedProjectsCall[0].completionStats;
+ expect(updatedStats['ProjectA']).toBeDefined();
+ expect(updatedStats['ProjectB']).toBeDefined();
});
- const { MultiSelectFilter } = require('@/components/ui/multi-select');
+ test('completion stats structure is correct', async () => {
+ render();
- const projectsCall = MultiSelectFilter.mock.calls.find(
- (call: any) => call[0].title === 'Projects'
- );
+ await waitFor(async () => {
+ expect(await screen.findByText('Task 1')).toBeInTheDocument();
+ });
+
+ const { MultiSelectFilter } = require('@/components/ui/multi-select');
- expect(projectsCall).toBeDefined();
- const stats = projectsCall[0].completionStats;
-
- // Verify stats structure for any project that exists
- Object.keys(stats).forEach((project) => {
- expect(stats[project]).toHaveProperty('completed');
- expect(stats[project]).toHaveProperty('total');
- expect(stats[project]).toHaveProperty('percentage');
- expect(typeof stats[project].completed).toBe('number');
- expect(typeof stats[project].total).toBe('number');
- expect(typeof stats[project].percentage).toBe('number');
- expect(stats[project].completed).toBeLessThanOrEqual(
- stats[project].total
+ const projectsCall = MultiSelectFilter.mock.calls.find(
+ (call: any) => call[0].title === 'Projects'
);
- expect(stats[project].percentage).toBeGreaterThanOrEqual(0);
- expect(stats[project].percentage).toBeLessThanOrEqual(100);
+
+ expect(projectsCall).toBeDefined();
+ const stats = projectsCall[0].completionStats;
+
+ // Verify stats structure for any project that exists
+ Object.keys(stats).forEach((project) => {
+ expect(stats[project]).toHaveProperty('completed');
+ expect(stats[project]).toHaveProperty('total');
+ expect(stats[project]).toHaveProperty('percentage');
+ expect(typeof stats[project].completed).toBe('number');
+ expect(typeof stats[project].total).toBe('number');
+ expect(typeof stats[project].percentage).toBe('number');
+ expect(stats[project].completed).toBeLessThanOrEqual(
+ stats[project].total
+ );
+ expect(stats[project].percentage).toBeGreaterThanOrEqual(0);
+ expect(stats[project].percentage).toBeLessThanOrEqual(100);
+ });
});
});
@@ -1737,4 +1753,178 @@ describe('Tasks Component', () => {
expect(task1Row).toBeInTheDocument();
});
});
+
+ describe('Keyboard Navigation', () => {
+ describe('Arrow Key Navigation', () => {
+ test('ArrowDown key moves selection to next task', async () => {
+ render();
+ await screen.findByText('Task 1');
+
+ fireEvent.keyDown(window, { key: 'ArrowDown' });
+ fireEvent.keyDown(window, { key: 'Enter' });
+
+ const dialog = await screen.findByRole('dialog');
+ expect(within(dialog).getByText('Deleted Task 1')).toBeInTheDocument();
+ });
+
+ test('ArrowUp moves selection back to previous task', async () => {
+ render();
+ await screen.findByText('Task 1');
+
+ fireEvent.keyDown(window, { key: 'ArrowDown' });
+ fireEvent.keyDown(window, { key: 'ArrowDown' });
+ fireEvent.keyDown(window, { key: 'ArrowUp' });
+ fireEvent.keyDown(window, { key: 'Enter' });
+
+ const dialog = await screen.findByRole('dialog');
+ expect(within(dialog).getByText('Deleted Task 1')).toBeInTheDocument();
+ });
+
+ test('ArrowDown stops at last task on page', async () => {
+ render();
+ await screen.findByText('Task 1');
+
+ for (let i = 0; i < 20; i++) {
+ fireEvent.keyDown(window, { key: 'ArrowDown' });
+ }
+
+ fireEvent.keyDown(window, { key: 'Enter' });
+
+ const dialog = await screen.findByRole('dialog');
+ expect(within(dialog).getByText('Task 9')).toBeInTheDocument();
+ });
+
+ test('ArrowUp stops at index zero task', async () => {
+ render();
+ await screen.findByText('Task 1');
+
+ for (let i = 0; i < 5; i++) {
+ fireEvent.keyDown(window, { key: 'ArrowUp' });
+ }
+
+ fireEvent.keyDown(window, { key: 'Enter' });
+
+ const dialog = await screen.findByRole('dialog');
+ expect(within(dialog).getByText('tag1')).toBeInTheDocument();
+ expect(within(dialog).getByText('Overdue')).toBeInTheDocument();
+ });
+ });
+
+ describe('Hotkey Shortcuts', () => {
+ test('pressing "a" opens the Add Task dialog', async () => {
+ render();
+ await screen.findByText('Task 1');
+
+ fireEvent.keyDown(window, { key: 'a' });
+
+ expect(
+ await screen.findByText(
+ /fill in the details below to add a new task/i
+ )
+ ).toBeInTheDocument();
+ });
+
+ test.each([
+ ['c', 'complete'],
+ ['d', 'delete'],
+ ])(
+ 'pressing %s attempts to open task dialog and trigger %s action',
+ async (key, _action) => {
+ jest.useFakeTimers();
+
+ render();
+ await screen.findByText('Task 1');
+
+ fireEvent.keyDown(window, { key });
+
+ expect(await screen.findByText('Tags:')).toBeInTheDocument();
+
+ act(() => {
+ jest.advanceTimersByTime(200);
+ });
+
+ expect(await screen.findByText('Are you')).toBeInTheDocument();
+
+ jest.useRealTimers();
+ }
+ );
+
+ test('pressing "Enter" key opens the selected task dialog', async () => {
+ render();
+ await screen.findByText('Task 1');
+
+ fireEvent.keyDown(window, { key: 'Enter' });
+
+ expect(await screen.findByText('Description:')).toBeInTheDocument();
+ });
+
+ test('pressing "f" focuses the search input', async () => {
+ render();
+ await screen.findByText('Task 1');
+
+ fireEvent.keyDown(window, { key: 'f' });
+
+ const searchInput = screen.getByPlaceholderText('Search tasks...');
+ expect(document.activeElement).toBe(searchInput);
+ });
+
+ test('pressing "r" triggers sync', async () => {
+ render();
+ await screen.findByText('Task 1');
+
+ fireEvent.keyDown(window, { key: 'r' });
+
+ expect(mockProps.setIsLoading).toHaveBeenCalledWith(true);
+ });
+
+ test.each([
+ ['p', 'projects'],
+ ['s', 'status'],
+ ['t', 'tags'],
+ ])('pressing "%s" opens the %s filter', async (key, filterName) => {
+ render();
+ await screen.findByText('Task 1');
+
+ const filterButton = screen.getByTestId(`multi-select-${filterName}`);
+ expect(filterButton).toHaveAttribute('aria-expanded', 'false');
+
+ fireEvent.keyDown(window, { key });
+
+ expect(filterButton).toHaveAttribute('aria-expanded', 'true');
+ });
+
+ test('hotkeys are disabled when input is focused', async () => {
+ render();
+ await screen.findByText('Task 1');
+
+ const searchInput = screen.getByPlaceholderText('Search tasks...');
+ searchInput.focus();
+
+ fireEvent.keyDown(searchInput, { key: 'r' });
+
+ expect(mockProps.setIsLoading).not.toHaveBeenCalledWith(true);
+ });
+ });
+
+ describe('Complete Hotkey When Dialog Open', () => {
+ test.each([
+ ['c', 'complete'],
+ ['d', 'delete'],
+ ])(
+ 'pressing "%s" when dialog is already open triggers %s confirmation',
+ async (key, _action) => {
+ render();
+ await screen.findByText('Task 1');
+
+ fireEvent.click(screen.getByText('Task 1'));
+
+ expect(await screen.findByRole('dialog')).toBeInTheDocument();
+
+ fireEvent.keyDown(window, { key });
+
+ expect(await screen.findByText('Are you')).toBeInTheDocument();
+ }
+ );
+ });
+ });
});