Skip to content

tpaphysics/tdd-todo-list

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

36 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Yarn vite vitest React Typescript Chakra-ui


πŸ’» Project

desktop-app

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:

tree

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);
  });
});

🧐 Test TodoList.tsx component

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();
  });
});

πŸ’₯ Considerations

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.

Get Started

yarn && yarn vite

πŸ› οΈ Test

yarn test

πŸ‘¨β€πŸš€ Author

Thiago Pacheco
Thiago Pacheco de Andrade

πŸ‘‹ My contacts!

Linkedin Badge Gmail Badge

About

Design pattern for front end react development

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published