mirror of https://github.com/buster-so/buster.git
wrote simple unit tests
Update InvitePeopleModal.test.tsx
This commit is contained in:
parent
1892f812f0
commit
9924120267
|
@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -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(() => {
|
||||||
|
|
Loading…
Reference in New Issue