wrote simple unit tests

Update InvitePeopleModal.test.tsx
This commit is contained in:
Nate Kelley 2025-04-21 13:07:23 -06:00
parent 1892f812f0
commit 9924120267
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
2 changed files with 165 additions and 3 deletions

View File

@ -0,0 +1,153 @@
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { InvitePeopleModal } from './InvitePeopleModal';
import { useInviteUser } from '@/api/buster_rest/users';
import { useBusterNotifications } from '@/context/BusterNotifications';
// Mock the hooks
jest.mock('@/api/buster_rest/users', () => ({
useInviteUser: jest.fn()
}));
jest.mock('@/context/BusterNotifications', () => ({
useBusterNotifications: jest.fn()
}));
describe('InvitePeopleModal', () => {
const mockOnClose = jest.fn();
const mockMutateAsync = jest.fn();
const mockOpenErrorMessage = jest.fn();
beforeEach(() => {
jest.clearAllMocks();
(useInviteUser as jest.Mock).mockReturnValue({
mutateAsync: mockMutateAsync,
isPending: false
});
(useBusterNotifications as jest.Mock).mockReturnValue({
openErrorMessage: mockOpenErrorMessage
});
});
it('renders correctly when open', () => {
render(<InvitePeopleModal open={true} onClose={mockOnClose} />);
expect(screen.getByText('Invite others to join your workspace')).toBeInTheDocument();
expect(screen.getByPlaceholderText(/buster@bluthbananas.com/)).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Send invites' })).toBeDisabled();
});
it('handles valid email input', async () => {
render(<InvitePeopleModal open={true} onClose={mockOnClose} />);
const input = screen.getByPlaceholderText(/buster@bluthbananas.com/);
fireEvent.change(input, { target: { value: 'test@example.com' } });
fireEvent.keyDown(input, { key: 'Enter' });
expect(screen.getByText('test@example.com')).toBeInTheDocument();
expect(screen.getByText('Send invites')).toBeEnabled();
});
it('handles invalid email input', () => {
render(<InvitePeopleModal open={true} onClose={mockOnClose} />);
const input = screen.getByPlaceholderText(/buster@bluthbananas.com/);
fireEvent.change(input, { target: { value: 'invalid-email' } });
fireEvent.keyDown(input, { key: 'Enter' });
expect(mockOpenErrorMessage).toHaveBeenCalledWith('Invalid email');
expect(screen.queryByText('invalid-email')).not.toBeInTheDocument();
});
it('handles multiple email inputs', () => {
render(<InvitePeopleModal open={true} onClose={mockOnClose} />);
const input = screen.getByPlaceholderText(/buster@bluthbananas.com/);
fireEvent.change(input, { target: { value: 'test1@example.com, test2@example.com' } });
fireEvent.keyDown(input, { key: 'Enter' });
expect(screen.getByText('test1@example.com')).toBeInTheDocument();
expect(screen.getByText('test2@example.com')).toBeInTheDocument();
});
it('removes email tag when clicked', async () => {
render(<InvitePeopleModal open={true} onClose={mockOnClose} />);
const input = screen.getByPlaceholderText(/buster@bluthbananas.com/);
fireEvent.change(input, { target: { value: 'test@example.com' } });
fireEvent.keyDown(input, { key: 'Enter' });
const tag = screen.getByText('test@example.com').closest('[data-tag="true"]');
expect(tag).toBeInTheDocument();
const removeButton = tag?.querySelector('button');
expect(removeButton).toBeInTheDocument();
fireEvent.pointerDown(removeButton!);
await waitFor(() => {
expect(screen.queryByText('test@example.com')).not.toBeInTheDocument();
});
});
it('sends invites when submit button is clicked', async () => {
render(<InvitePeopleModal open={true} onClose={mockOnClose} />);
const input = screen.getByPlaceholderText(/buster@bluthbananas.com/);
fireEvent.change(input, { target: { value: 'test@example.com' } });
fireEvent.keyDown(input, { key: 'Enter' });
const submitButton = screen.getByText('Send invites');
fireEvent.click(submitButton);
await waitFor(() => {
expect(mockMutateAsync).toHaveBeenCalledWith({
emails: ['test@example.com']
});
expect(mockOnClose).toHaveBeenCalled();
});
});
it('deduplicates email addresses', async () => {
render(<InvitePeopleModal open={true} onClose={mockOnClose} />);
const input = screen.getByPlaceholderText(/buster@bluthbananas.com/);
fireEvent.change(input, { target: { value: 'test@example.com, test@example.com' } });
fireEvent.keyDown(input, { key: 'Enter' });
const submitButton = screen.getByText('Send invites');
fireEvent.click(submitButton);
await waitFor(() => {
expect(mockMutateAsync).toHaveBeenCalledWith({
emails: ['test@example.com']
});
});
});
it('handles pasting multiple email addresses', async () => {
render(<InvitePeopleModal open={true} onClose={mockOnClose} />);
const input = screen.getByPlaceholderText(/buster@bluthbananas.com/);
const pastedEmails = 'test1@example.com, test2@example.com, test3@example.com';
fireEvent.paste(input, {
clipboardData: {
getData: () => pastedEmails
}
});
expect(screen.getByText('test1@example.com')).toBeInTheDocument();
expect(screen.getByText('test2@example.com')).toBeInTheDocument();
expect(screen.getByText('test3@example.com')).toBeInTheDocument();
// Remove the first email
const firstTag = screen.getByText('test1@example.com').closest('[data-tag="true"]');
const removeButton = firstTag?.querySelector('button');
fireEvent.pointerDown(removeButton!);
await waitFor(() => {
expect(screen.queryByText('test1@example.com')).not.toBeInTheDocument();
expect(screen.getByText('test2@example.com')).toBeInTheDocument();
expect(screen.getByText('test3@example.com')).toBeInTheDocument();
});
});
});

View File

@ -6,6 +6,7 @@ import { useInviteUser } from '@/api/buster_rest/users';
import { validate } from 'email-validator'; import { validate } from 'email-validator';
import { useBusterNotifications } from '@/context/BusterNotifications'; import { useBusterNotifications } from '@/context/BusterNotifications';
import uniq from 'lodash/uniq'; import uniq from 'lodash/uniq';
import { timeout } from '@/lib';
export const InvitePeopleModal: React.FC<{ export const InvitePeopleModal: React.FC<{
open: boolean; open: boolean;
@ -14,12 +15,20 @@ export const InvitePeopleModal: React.FC<{
const [emails, setEmails] = React.useState<string[]>([]); const [emails, setEmails] = React.useState<string[]>([]);
const { mutateAsync: inviteUsers, isPending: inviting } = useInviteUser(); const { mutateAsync: inviteUsers, isPending: inviting } = useInviteUser();
const [inputText, setInputText] = React.useState<string>(''); const [inputText, setInputText] = React.useState<string>('');
const { openErrorMessage } = useBusterNotifications(); const { openErrorMessage, openSuccessMessage } = useBusterNotifications();
const handleInvite = useMemoizedFn(async () => { const handleInvite = useMemoizedFn(async () => {
const allEmails = uniq([...emails, inputText].filter((email) => !!email && validate(email))); const allEmails = uniq([...emails, inputText].filter((email) => !!email && validate(email)));
try {
await inviteUsers({ emails: allEmails }); await inviteUsers({ emails: allEmails });
onClose(); onClose();
openSuccessMessage('Invites sent');
await timeout(330);
setEmails([]);
setInputText('');
} catch (error) {
openErrorMessage('Failed to invite users');
}
}); });
const memoizedHeader = useMemo(() => { const memoizedHeader = useMemo(() => {