Triết lý RTL: "test như cách người dùng dùng app", không test nội bộ.
Thứ tự ưu tiên query (từ tốt nhất xuống):
1. Accessible — getByRole (kèm name), getByLabelText, getByPlaceholderText, getByText. Phản ánh đúng cách user và screen reader nhìn UI.
2. Semantic — getByAltText, getByTitle.
3. Test ID — getByTestId, chỉ khi không có cách nào ở trên.
Ưu tiên getByRole('button', { name: /lưu/i }) thay vì bám class hay cấu trúc DOM.
user-event vs fireEvent: dùng @testing-library/user-event — nó mô phỏng chuỗi sự kiện thật (hover → focus → keydown → input → keyup) thay vì bắn một event đơn lẻ như fireEvent. userEvent.click/type là async → phải await.
Tránh implementation details: đừng assert vào state nội bộ, tên hàm, hay class CSS. Test hành vi quan sát được (text hiện ra, button disabled). Như vậy refactor bên trong không làm vỡ test.
await userEvent.type(screen.getByLabelText(/email/i), 'a@b.com')
await userEvent.click(screen.getByRole('button', { name: /gửi/i }))
expect(await screen.findByText(/thành công/i)).toBeInTheDocument()