This Todo List project was developed as an exercise to develop TDD architecture skills in frontend development. An own design pattern was created. The folder structure is as follows:
Each component that has react state has a hook folder to separate the business rule from the TSX component states. This way we were able to test the hooks with the React Testing Library. Additional functions can be created in the utils folder as in the example below:
index.tsx
function AddTaskForm() {
const { task, handleTaskChange, handleClickAddButton } = useAddTaskForm();
return (
<HStack>
<Input
data-testId='list-input'
value={task}
onChange={handleTaskChange}
variant='flushed'
placeholder='New task'
bg='gray.800'
_placeholder={{ color: 'whiteAlpha.600' }}
color='whiteAlpha.800'
fontWeight='700'
outline='0'
flex='2'
borderRadius='5px'
focusBorderColor='yellow.400'
px='18px'
/>
<Button
flex='1'
isDisabled={task === '' ? true : false}
colorScheme='yellow'
onClick={handleClickAddButton}
>
Add
</Button>
</HStack>
);
}
export default AddTaskForm;
useAddCardForm.ts
export const useAddTaskForm = () => {
const [task, setTask] = useState('');
const { addCard } = useList();
const handleTaskChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
setTask(event.target.value);
}, []);
const handleClickAddButton = useCallback(() => {
const format = formatMax(task);
const newCard = { id: uuid(), task: format, completed: false } as CardData;
addCard(newCard);
setTask('');
}, [addCard, task]);
return { task, setTask, handleTaskChange, handleClickAddButton };
};
utils.ts
function formatMax(task: string) {
const max = 28;
const taskLength = task.length;
if (taskLength >= max) {
const part = task.substring(0, max - 3);
return `${part}...`;
} else {
return task;
}
}
export { formatMax };
This way we can test each component part separately:
useAddTaskForm.test.ts
describe('useAddTaskForm hook', () => {
afterEach(() => {
vi.restoreAllMocks();
});
it('Should be inital task value equal ""', () => {
const { result } = renderHook(() => useAddTaskForm());
expect(result.current.task).toBe('');
});
it('Should be task value equal "TEST"', () => {
const { result } = renderHook(() => useAddTaskForm());
act(() => {
result.current.setTask('TEST');
});
expect(result.current.task).toBe('TEST');
});
it('handleTaskChange, Should be task value equal "TASK"', () => {
const { result } = renderHook(() => useAddTaskForm());
const mockedEvent = { target: { value: 'TASK' } } as React.ChangeEvent<HTMLInputElement>;
act(() => {
result.current.handleTaskChange(mockedEvent);
});
expect(result.current.task).toBe('TASK');
});
it('handleClickButton, Should be called once mockedAddList', () => {
const wrapper = ({ children }: BoxProps) => <ListProvider>{children}</ListProvider>;
const { result } = renderHook(() => useAddTaskForm(), {
wrapper,
});
const mockedUseList = vi
.spyOn(hooks, 'useList')
.mockImplementation(() => ({ AddCard: vi.fn() } as any));
act(() => {
result.current.handleClickAddButton();
});
expect(mockedUseList).toBeCalledTimes(1);
});
});
describe('TodoList test', () => {
it('Should add task when write input and click in add button', () => {
const { getByText, getByTestId } = render(<TodoList />);
fireEvent.input(getByTestId('list-input'), { target: { value: 'My List' } });
fireEvent.click(getByText(/add/i));
expect(getByText(/my list/i)).toBeInTheDocument();
});
it('Should be disble add button when input task is empty', () => {
const { getByText, getByTestId } = render(<TodoList />);
fireEvent.input(getByTestId('list-input'), { target: { value: '' } });
expect(getByText(/add/i).closest('button')).toBeDisabled();
});
it('Should it remove task when click in trash button', () => {
const mockedCardId = cards[0].id;
const { getByTestId } = render(<TodoList />);
fireEvent.click(getByTestId(`close-task-${mockedCardId}`));
expect(() => getByTestId(`close-task-${mockedCardId}`)).toThrow();
});
});
Frontend tests help standardize and minimize errors, improving user experience. It facilitates group work between development teams and contributes to a scalable and quality final solution.
yarn && yarn vite
yarn test
Thiago Pacheco de Andrade
π My contacts!