datajunction-ui
Version:
DataJunction UI
406 lines (330 loc) • 10.9 kB
JSX
import React from 'react';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { CreateServiceAccountModal } from '../CreateServiceAccountModal';
describe('CreateServiceAccountModal', () => {
const mockOnClose = vi.fn();
const mockOnCreate = vi.fn();
beforeEach(() => {
vi.clearAllMocks();
});
it('does not render when isOpen is false', () => {
render(
<CreateServiceAccountModal
isOpen={false}
onClose={mockOnClose}
onCreate={mockOnCreate}
/>,
);
expect(
screen.queryByText('Create Service Account'),
).not.toBeInTheDocument();
});
it('renders modal when isOpen is true', () => {
render(
<CreateServiceAccountModal
isOpen={true}
onClose={mockOnClose}
onCreate={mockOnCreate}
/>,
);
expect(screen.getByText('Create Service Account')).toBeInTheDocument();
expect(screen.getByLabelText('Name')).toBeInTheDocument();
expect(screen.getByText('Cancel')).toBeInTheDocument();
expect(screen.getByText('Create')).toBeInTheDocument();
});
it('calls onClose when close button is clicked', () => {
render(
<CreateServiceAccountModal
isOpen={true}
onClose={mockOnClose}
onCreate={mockOnCreate}
/>,
);
fireEvent.click(screen.getByTitle('Close'));
expect(mockOnClose).toHaveBeenCalled();
});
it('calls onClose when Cancel button is clicked', () => {
render(
<CreateServiceAccountModal
isOpen={true}
onClose={mockOnClose}
onCreate={mockOnCreate}
/>,
);
fireEvent.click(screen.getByText('Cancel'));
expect(mockOnClose).toHaveBeenCalled();
});
it('calls onClose when overlay is clicked', () => {
render(
<CreateServiceAccountModal
isOpen={true}
onClose={mockOnClose}
onCreate={mockOnCreate}
/>,
);
// Click on overlay (modal-overlay class)
const overlay = document.querySelector('.modal-overlay');
fireEvent.click(overlay);
expect(mockOnClose).toHaveBeenCalled();
});
it('does not close when modal content is clicked', () => {
render(
<CreateServiceAccountModal
isOpen={true}
onClose={mockOnClose}
onCreate={mockOnCreate}
/>,
);
const content = document.querySelector('.modal-content');
fireEvent.click(content);
expect(mockOnClose).not.toHaveBeenCalled();
});
it('disables Create button when name is empty', () => {
render(
<CreateServiceAccountModal
isOpen={true}
onClose={mockOnClose}
onCreate={mockOnCreate}
/>,
);
const createButton = screen.getByText('Create');
expect(createButton).toBeDisabled();
});
it('enables Create button when name is entered', () => {
render(
<CreateServiceAccountModal
isOpen={true}
onClose={mockOnClose}
onCreate={mockOnCreate}
/>,
);
const input = screen.getByLabelText('Name');
fireEvent.change(input, { target: { value: 'my-new-account' } });
const createButton = screen.getByText('Create');
expect(createButton).not.toBeDisabled();
});
it('calls onCreate with trimmed name on submit', async () => {
mockOnCreate.mockResolvedValue({ client_id: 'test-id' });
render(
<CreateServiceAccountModal
isOpen={true}
onClose={mockOnClose}
onCreate={mockOnCreate}
/>,
);
const input = screen.getByLabelText('Name');
fireEvent.change(input, { target: { value: ' my-account ' } });
fireEvent.click(screen.getByText('Create'));
await waitFor(() => {
expect(mockOnCreate).toHaveBeenCalledWith('my-account');
});
});
it('shows credentials after successful creation', async () => {
const credentials = {
name: 'my-account',
client_id: 'abc-123',
client_secret: 'secret-xyz',
};
mockOnCreate.mockResolvedValue(credentials);
render(
<CreateServiceAccountModal
isOpen={true}
onClose={mockOnClose}
onCreate={mockOnCreate}
/>,
);
const input = screen.getByLabelText('Name');
fireEvent.change(input, { target: { value: 'my-account' } });
fireEvent.click(screen.getByText('Create'));
await waitFor(() => {
expect(screen.getByText('Service Account Created!')).toBeInTheDocument();
});
expect(screen.getByText('abc-123')).toBeInTheDocument();
expect(screen.getByText('secret-xyz')).toBeInTheDocument();
expect(
screen.getByText(/client secret will not be shown again/i),
).toBeInTheDocument();
expect(screen.getByText('Done')).toBeInTheDocument();
});
it('shows alert when creation returns error message', async () => {
window.alert = vi.fn();
mockOnCreate.mockResolvedValue({ message: 'Account already exists' });
render(
<CreateServiceAccountModal
isOpen={true}
onClose={mockOnClose}
onCreate={mockOnCreate}
/>,
);
const input = screen.getByLabelText('Name');
fireEvent.change(input, { target: { value: 'my-account' } });
fireEvent.click(screen.getByText('Create'));
await waitFor(() => {
expect(window.alert).toHaveBeenCalledWith('Account already exists');
});
});
it('shows alert when creation throws error', async () => {
window.alert = vi.fn();
mockOnCreate.mockRejectedValue(new Error('Network error'));
render(
<CreateServiceAccountModal
isOpen={true}
onClose={mockOnClose}
onCreate={mockOnCreate}
/>,
);
const input = screen.getByLabelText('Name');
fireEvent.change(input, { target: { value: 'my-account' } });
fireEvent.click(screen.getByText('Create'));
await waitFor(() => {
expect(window.alert).toHaveBeenCalledWith(
'Failed to create service account',
);
});
});
it('shows Creating... while request is in progress', async () => {
let resolveCreate;
mockOnCreate.mockImplementation(
() => new Promise(resolve => (resolveCreate = resolve)),
);
render(
<CreateServiceAccountModal
isOpen={true}
onClose={mockOnClose}
onCreate={mockOnCreate}
/>,
);
const input = screen.getByLabelText('Name');
fireEvent.change(input, { target: { value: 'my-account' } });
fireEvent.click(screen.getByText('Create'));
expect(screen.getByText('Creating...')).toBeInTheDocument();
// Resolve the promise
resolveCreate({ client_id: 'test' });
await waitFor(() => {
expect(screen.queryByText('Creating...')).not.toBeInTheDocument();
});
});
it('closes and resets state when Done is clicked after creation', async () => {
const credentials = {
name: 'my-account',
client_id: 'abc-123',
client_secret: 'secret-xyz',
};
mockOnCreate.mockResolvedValue(credentials);
render(
<CreateServiceAccountModal
isOpen={true}
onClose={mockOnClose}
onCreate={mockOnCreate}
/>,
);
const input = screen.getByLabelText('Name');
fireEvent.change(input, { target: { value: 'my-account' } });
fireEvent.click(screen.getByText('Create'));
await waitFor(() => {
expect(screen.getByText('Done')).toBeInTheDocument();
});
fireEvent.click(screen.getByText('Done'));
expect(mockOnClose).toHaveBeenCalled();
});
it('copies client ID to clipboard when copy button is clicked', async () => {
const credentials = {
name: 'my-account',
client_id: 'abc-123',
client_secret: 'secret-xyz',
};
mockOnCreate.mockResolvedValue(credentials);
Object.assign(navigator, {
clipboard: {
writeText: vi.fn().mockResolvedValue(),
},
});
render(
<CreateServiceAccountModal
isOpen={true}
onClose={mockOnClose}
onCreate={mockOnCreate}
/>,
);
const input = screen.getByLabelText('Name');
fireEvent.change(input, { target: { value: 'my-account' } });
fireEvent.click(screen.getByText('Create'));
await waitFor(() => {
expect(screen.getByText('abc-123')).toBeInTheDocument();
});
const copyButtons = screen.getAllByTitle('Copy');
fireEvent.click(copyButtons[0]);
expect(navigator.clipboard.writeText).toHaveBeenCalledWith('abc-123');
});
it('copies client secret to clipboard when copy button is clicked', async () => {
const credentials = {
name: 'my-account',
client_id: 'abc-123',
client_secret: 'secret-xyz',
};
mockOnCreate.mockResolvedValue(credentials);
Object.assign(navigator, {
clipboard: {
writeText: vi.fn().mockResolvedValue(),
},
});
render(
<CreateServiceAccountModal
isOpen={true}
onClose={mockOnClose}
onCreate={mockOnCreate}
/>,
);
const input = screen.getByLabelText('Name');
fireEvent.change(input, { target: { value: 'my-account' } });
fireEvent.click(screen.getByText('Create'));
await waitFor(() => {
expect(screen.getByText('secret-xyz')).toBeInTheDocument();
});
// Click the second copy button (client secret)
const copyButtons = screen.getAllByTitle('Copy');
fireEvent.click(copyButtons[1]);
expect(navigator.clipboard.writeText).toHaveBeenCalledWith('secret-xyz');
});
it('does not call onCreate when form is submitted with only whitespace', async () => {
render(
<CreateServiceAccountModal
isOpen={true}
onClose={mockOnClose}
onCreate={mockOnCreate}
/>,
);
const input = screen.getByLabelText('Name');
// Enter only whitespace
fireEvent.change(input, { target: { value: ' ' } });
// Get the form and submit it directly
const form = document.querySelector('form');
fireEvent.submit(form);
// onCreate should not be called for whitespace-only names
expect(mockOnCreate).not.toHaveBeenCalled();
});
it('clears name input after successful creation', async () => {
const credentials = {
name: 'my-account',
client_id: 'abc-123',
client_secret: 'secret-xyz',
};
mockOnCreate.mockResolvedValue(credentials);
render(
<CreateServiceAccountModal
isOpen={true}
onClose={mockOnClose}
onCreate={mockOnCreate}
/>,
);
const input = screen.getByLabelText('Name');
fireEvent.change(input, { target: { value: 'my-account' } });
fireEvent.click(screen.getByText('Create'));
await waitFor(() => {
expect(screen.getByText('Service Account Created!')).toBeInTheDocument();
});
// After showing credentials view, the name input is no longer visible
// The name was cleared after successful creation (line 21)
expect(mockOnCreate).toHaveBeenCalledWith('my-account');
});
});