Clean up: Remove src files
This commit is contained in:
parent
4a47ed2e6a
commit
f6e4b1de7e
@ -1,305 +0,0 @@
|
||||
import React from 'react';
|
||||
import { fireEvent, render, screen, waitFor, within } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import App from './App';
|
||||
import { api } from './services/api';
|
||||
import { Task, User } from './types';
|
||||
|
||||
jest.mock('./services/api', () => ({
|
||||
...jest.requireActual('./services/api'),
|
||||
api: {
|
||||
getCurrentUser: jest.fn(),
|
||||
getUsers: jest.fn(),
|
||||
getTasks: jest.fn(),
|
||||
createTask: jest.fn(),
|
||||
updateTask: jest.fn(),
|
||||
deleteTask: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
const mockApi = api as jest.Mocked<typeof api>;
|
||||
|
||||
const sarah: User = { id: 1, username: 'sarah.chen', full_name: 'Sarah Chen' };
|
||||
const alex: User = { id: 2, username: 'alex.rivera', full_name: 'Alex Rivera' };
|
||||
|
||||
const myTask: Task = {
|
||||
id: 1,
|
||||
title: 'Q4 Marketing Strategy',
|
||||
description: 'Outline key campaigns and budget',
|
||||
assigned_to: sarah,
|
||||
status: 'open',
|
||||
priority: 'medium',
|
||||
due_date: '2026-10-20',
|
||||
created_at: '2026-09-01T10:00:00Z',
|
||||
updated_at: '2026-09-01T10:00:00Z',
|
||||
};
|
||||
|
||||
const blockedTask: Task = {
|
||||
id: 2,
|
||||
title: 'API Integration Debugging',
|
||||
description: 'Fixing errors in payment gateway',
|
||||
assigned_to: alex,
|
||||
status: 'blocked',
|
||||
priority: 'high',
|
||||
due_date: '2026-10-18',
|
||||
created_at: '2026-09-02T10:00:00Z',
|
||||
updated_at: '2026-09-02T10:00:00Z',
|
||||
};
|
||||
|
||||
/** The desktop table and the mobile cards are both in the DOM (CSS picks one),
|
||||
* so row-level assertions are scoped to the table. */
|
||||
const table = () => within(screen.getByRole('table'));
|
||||
|
||||
const renderApp = async (): Promise<void> => {
|
||||
render(<App />);
|
||||
await waitFor(() => expect(screen.getByRole('table')).toBeInTheDocument());
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockApi.getCurrentUser.mockResolvedValue(sarah);
|
||||
mockApi.getUsers.mockResolvedValue([sarah, alex]);
|
||||
mockApi.getTasks.mockResolvedValue([myTask, blockedTask]);
|
||||
});
|
||||
|
||||
describe('loading the dashboard', () => {
|
||||
it('shows a loading state, then the team task table', async () => {
|
||||
render(<App />);
|
||||
|
||||
expect(screen.getByRole('status')).toHaveTextContent(/loading/i);
|
||||
|
||||
await waitFor(() => expect(screen.getByRole('table')).toBeInTheDocument());
|
||||
expect(table().getByText('Q4 Marketing Strategy')).toBeInTheDocument();
|
||||
expect(table().getByText('API Integration Debugging')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('offers a retry when the request fails, and recovers on retry', async () => {
|
||||
mockApi.getTasks.mockRejectedValueOnce(new Error('Network request failed'));
|
||||
|
||||
render(<App />);
|
||||
|
||||
const alert = await screen.findByRole('alert');
|
||||
expect(alert).toHaveTextContent('Network request failed');
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: /try again/i }));
|
||||
|
||||
await waitFor(() => expect(screen.getByRole('table')).toBeInTheDocument());
|
||||
expect(table().getByText('Q4 Marketing Strategy')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('filtering', () => {
|
||||
it('limits My Tasks to the signed-in user', async () => {
|
||||
await renderApp();
|
||||
|
||||
await userEvent.click(screen.getByRole('tab', { name: /my tasks/i }));
|
||||
|
||||
expect(table().getByText('Q4 Marketing Strategy')).toBeInTheDocument();
|
||||
expect(table().queryByText('API Integration Debugging')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('explains why work is blocked in the Blocked view', async () => {
|
||||
await renderApp();
|
||||
|
||||
await userEvent.click(screen.getByRole('tab', { name: /blocked/i }));
|
||||
|
||||
expect(screen.getByRole('heading', { name: /these tasks are blocked/i })).toBeInTheDocument();
|
||||
expect(table().getByText('API Integration Debugging')).toBeInTheDocument();
|
||||
expect(table().queryByText('Q4 Marketing Strategy')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('reports an empty filter without pretending there are no tasks at all', async () => {
|
||||
mockApi.getTasks.mockResolvedValue([myTask]);
|
||||
await renderApp();
|
||||
|
||||
await userEvent.click(screen.getByRole('tab', { name: /blocked/i }));
|
||||
|
||||
expect(screen.getByRole('heading', { name: /no blocked tasks/i })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('creating a task', () => {
|
||||
it('creates and lists a task in three steps', async () => {
|
||||
const created: Task = {
|
||||
...myTask,
|
||||
id: 3,
|
||||
title: 'Update client onboarding documentation',
|
||||
description: '',
|
||||
assigned_to: alex,
|
||||
priority: 'high',
|
||||
due_date: '2026-11-01',
|
||||
};
|
||||
mockApi.createTask.mockResolvedValue(created);
|
||||
|
||||
await renderApp();
|
||||
|
||||
// 1. open the form
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Create task' }));
|
||||
expect(screen.getByRole('dialog', { name: /create new task/i })).toBeInTheDocument();
|
||||
|
||||
// 2. fill in the required fields
|
||||
fireEvent.change(screen.getByLabelText(/task title/i), {
|
||||
target: { value: 'Update client onboarding documentation' },
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText(/assignee/i), { target: { value: '2' } });
|
||||
fireEvent.change(screen.getByLabelText(/due date/i), { target: { value: '2026-11-01' } });
|
||||
fireEvent.change(screen.getByLabelText(/priority/i), { target: { value: 'high' } });
|
||||
|
||||
// 3. save
|
||||
await userEvent.click(screen.getByRole('button', { name: /save task/i }));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mockApi.createTask).toHaveBeenCalledWith({
|
||||
title: 'Update client onboarding documentation',
|
||||
description: '',
|
||||
assignee_id: 2,
|
||||
status: 'open',
|
||||
priority: 'high',
|
||||
due_date: '2026-11-01',
|
||||
})
|
||||
);
|
||||
|
||||
await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument());
|
||||
expect(table().getByText('Update client onboarding documentation')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('explains what to fix instead of submitting an incomplete task', async () => {
|
||||
await renderApp();
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Create task' }));
|
||||
await userEvent.click(screen.getByRole('button', { name: /save task/i }));
|
||||
|
||||
expect(await screen.findByText(/enter a task title/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/choose the team member who owns this task/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/pick a due date/i)).toBeInTheDocument();
|
||||
expect(screen.getByLabelText(/task title/i)).toHaveAttribute('aria-invalid', 'true');
|
||||
expect(mockApi.createTask).not.toHaveBeenCalled();
|
||||
expect(screen.getByRole('dialog')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('keeps the form and its input on screen when saving fails', async () => {
|
||||
mockApi.createTask.mockRejectedValue(new Error('Connection lost before the task was saved.'));
|
||||
|
||||
await renderApp();
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Create task' }));
|
||||
fireEvent.change(screen.getByLabelText(/task title/i), { target: { value: 'Draft that must survive' } });
|
||||
fireEvent.change(screen.getByLabelText(/assignee/i), { target: { value: '2' } });
|
||||
fireEvent.change(screen.getByLabelText(/due date/i), { target: { value: '2026-11-01' } });
|
||||
await userEvent.click(screen.getByRole('button', { name: /save task/i }));
|
||||
|
||||
expect(
|
||||
await screen.findByText('Connection lost before the task was saved.')
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByRole('dialog')).toBeInTheDocument();
|
||||
expect(screen.getByLabelText(/task title/i)).toHaveValue('Draft that must survive');
|
||||
});
|
||||
});
|
||||
|
||||
describe('updating and deleting', () => {
|
||||
it('changes status straight from the list', async () => {
|
||||
mockApi.updateTask.mockResolvedValue({ ...myTask, status: 'in_progress' });
|
||||
|
||||
await renderApp();
|
||||
|
||||
fireEvent.change(table().getByLabelText('Status for Q4 Marketing Strategy'), {
|
||||
target: { value: 'in_progress' },
|
||||
});
|
||||
|
||||
await waitFor(() => expect(mockApi.updateTask).toHaveBeenCalledWith(1, { status: 'in_progress' }));
|
||||
expect(await screen.findByText(/is now in progress/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('leaves the task untouched and says so when an update fails', async () => {
|
||||
mockApi.updateTask.mockRejectedValue(new Error('Could not reach the server.'));
|
||||
|
||||
await renderApp();
|
||||
|
||||
fireEvent.change(table().getByLabelText('Status for Q4 Marketing Strategy'), {
|
||||
target: { value: 'blocked' },
|
||||
});
|
||||
|
||||
expect(await screen.findByText('Could not reach the server.')).toBeInTheDocument();
|
||||
expect(table().getByLabelText('Status for Q4 Marketing Strategy')).toHaveValue('open');
|
||||
});
|
||||
|
||||
it('opens the edit dialog prefilled from the row', async () => {
|
||||
await renderApp();
|
||||
|
||||
await userEvent.click(table().getByRole('button', { name: 'Edit Q4 Marketing Strategy' }));
|
||||
|
||||
expect(screen.getByRole('dialog', { name: /edit task/i })).toBeInTheDocument();
|
||||
expect(screen.getByLabelText(/task title/i)).toHaveValue('Q4 Marketing Strategy');
|
||||
expect(screen.getByLabelText(/assignee/i)).toHaveValue('1');
|
||||
});
|
||||
|
||||
it('asks before deleting and does nothing if the user backs out', async () => {
|
||||
const confirmSpy = jest.spyOn(window, 'confirm').mockReturnValue(false);
|
||||
|
||||
await renderApp();
|
||||
await userEvent.click(table().getByRole('button', { name: 'Delete Q4 Marketing Strategy' }));
|
||||
|
||||
expect(confirmSpy).toHaveBeenCalled();
|
||||
expect(mockApi.deleteTask).not.toHaveBeenCalled();
|
||||
expect(table().getByText('Q4 Marketing Strategy')).toBeInTheDocument();
|
||||
|
||||
confirmSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('deletes once confirmed', async () => {
|
||||
const confirmSpy = jest.spyOn(window, 'confirm').mockReturnValue(true);
|
||||
mockApi.deleteTask.mockResolvedValue(undefined);
|
||||
|
||||
await renderApp();
|
||||
await userEvent.click(table().getByRole('button', { name: 'Delete Q4 Marketing Strategy' }));
|
||||
|
||||
await waitFor(() => expect(mockApi.deleteTask).toHaveBeenCalledWith(1));
|
||||
await waitFor(() => expect(table().queryByText('Q4 Marketing Strategy')).not.toBeInTheDocument());
|
||||
|
||||
confirmSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('keyboard and screen reader support', () => {
|
||||
it('labels the filter tabs as a tablist with counts', async () => {
|
||||
await renderApp();
|
||||
|
||||
const tabs = screen.getAllByRole('tab');
|
||||
expect(tabs).toHaveLength(3);
|
||||
expect(screen.getByRole('tab', { name: /all tasks, 2 tasks/i })).toHaveAttribute(
|
||||
'aria-selected',
|
||||
'true'
|
||||
);
|
||||
});
|
||||
|
||||
it('moves between filter tabs with the arrow keys', async () => {
|
||||
await renderApp();
|
||||
|
||||
const allTab = screen.getByRole('tab', { name: /all tasks/i });
|
||||
allTab.focus();
|
||||
fireEvent.keyDown(allTab, { key: 'ArrowRight' });
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.getByRole('tab', { name: /blocked/i })).toHaveAttribute('aria-selected', 'true')
|
||||
);
|
||||
});
|
||||
|
||||
it('closes the dialog on Escape without saving', async () => {
|
||||
await renderApp();
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Create task' }));
|
||||
fireEvent.keyDown(screen.getByRole('dialog'), { key: 'Escape' });
|
||||
|
||||
await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument());
|
||||
expect(mockApi.createTask).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('exposes every column header and a skip link', async () => {
|
||||
await renderApp();
|
||||
|
||||
['Priority', 'Task Name', 'Assignee', 'Status', 'Due Date', 'Actions'].forEach((header) => {
|
||||
expect(table().getByRole('columnheader', { name: header })).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByRole('link', { name: /skip to task list/i })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user