mirror of
https://github.com/lordmathis/lemma.git
synced 2025-11-05 15:44:21 +00:00
Add tests for AppearanceSettings, DangerZoneSettings, EditorSettings, GeneralSettings, GitSettings, and WorkspaceSettings components
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { render as rtlRender, screen, fireEvent } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { MantineProvider } from '@mantine/core';
|
||||
import AppearanceSettings from './AppearanceSettings';
|
||||
import { Theme } from '@/types/models';
|
||||
|
||||
const mockUpdateColorScheme = vi.fn();
|
||||
|
||||
vi.mock('../../../contexts/ThemeContext', () => ({
|
||||
useTheme: vi.fn(),
|
||||
}));
|
||||
|
||||
const TestWrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<MantineProvider defaultColorScheme="light">{children}</MantineProvider>
|
||||
);
|
||||
|
||||
const render = (ui: React.ReactElement) => {
|
||||
return rtlRender(ui, { wrapper: TestWrapper });
|
||||
};
|
||||
|
||||
describe('AppearanceSettings', () => {
|
||||
const mockOnThemeChange = vi.fn();
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.clearAllMocks();
|
||||
const { useTheme } = await import('../../../contexts/ThemeContext');
|
||||
vi.mocked(useTheme).mockReturnValue({
|
||||
colorScheme: 'light',
|
||||
updateColorScheme: mockUpdateColorScheme,
|
||||
});
|
||||
});
|
||||
|
||||
it('renders dark mode toggle with correct state', () => {
|
||||
render(<AppearanceSettings onThemeChange={mockOnThemeChange} />);
|
||||
|
||||
expect(screen.getByText('Dark Mode')).toBeInTheDocument();
|
||||
const toggle = screen.getByRole('switch');
|
||||
expect(toggle).not.toBeChecked();
|
||||
});
|
||||
|
||||
it('shows toggle as checked when in dark mode', async () => {
|
||||
const { useTheme } = await import('../../../contexts/ThemeContext');
|
||||
vi.mocked(useTheme).mockReturnValue({
|
||||
colorScheme: 'dark',
|
||||
updateColorScheme: mockUpdateColorScheme,
|
||||
});
|
||||
|
||||
render(<AppearanceSettings onThemeChange={mockOnThemeChange} />);
|
||||
|
||||
const toggle = screen.getByRole('switch');
|
||||
expect(toggle).toBeChecked();
|
||||
});
|
||||
|
||||
it('toggles theme from light to dark', () => {
|
||||
render(<AppearanceSettings onThemeChange={mockOnThemeChange} />);
|
||||
|
||||
const toggle = screen.getByRole('switch');
|
||||
fireEvent.click(toggle);
|
||||
|
||||
expect(mockUpdateColorScheme).toHaveBeenCalledWith(Theme.Dark);
|
||||
expect(mockOnThemeChange).toHaveBeenCalledWith(Theme.Dark);
|
||||
});
|
||||
|
||||
it('toggles theme from dark to light', async () => {
|
||||
const { useTheme } = await import('../../../contexts/ThemeContext');
|
||||
vi.mocked(useTheme).mockReturnValue({
|
||||
colorScheme: 'dark',
|
||||
updateColorScheme: mockUpdateColorScheme,
|
||||
});
|
||||
|
||||
render(<AppearanceSettings onThemeChange={mockOnThemeChange} />);
|
||||
|
||||
const toggle = screen.getByRole('switch');
|
||||
fireEvent.click(toggle);
|
||||
|
||||
expect(mockUpdateColorScheme).toHaveBeenCalledWith(Theme.Light);
|
||||
expect(mockOnThemeChange).toHaveBeenCalledWith(Theme.Light);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,283 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import {
|
||||
render as rtlRender,
|
||||
screen,
|
||||
fireEvent,
|
||||
waitFor,
|
||||
} from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { MantineProvider } from '@mantine/core';
|
||||
import DangerZoneSettings from './DangerZoneSettings';
|
||||
import { Theme } from '@/types/models';
|
||||
|
||||
const mockDeleteCurrentWorkspace = vi.fn();
|
||||
|
||||
vi.mock('../../../hooks/useWorkspace', () => ({
|
||||
useWorkspace: vi.fn(),
|
||||
}));
|
||||
|
||||
const mockSetSettingsModalVisible = vi.fn();
|
||||
vi.mock('../../../contexts/ModalContext', () => ({
|
||||
useModalContext: () => ({
|
||||
setSettingsModalVisible: mockSetSettingsModalVisible,
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('../../modals/workspace/DeleteWorkspaceModal', () => ({
|
||||
default: ({
|
||||
opened,
|
||||
onClose,
|
||||
onConfirm,
|
||||
workspaceName,
|
||||
}: {
|
||||
opened: boolean;
|
||||
onClose: () => void;
|
||||
onConfirm: () => Promise<void>;
|
||||
workspaceName: string | undefined;
|
||||
}) =>
|
||||
opened ? (
|
||||
<div data-testid="delete-workspace-modal">
|
||||
<span data-testid="workspace-name">{workspaceName}</span>
|
||||
<button onClick={onClose} data-testid="modal-close">
|
||||
Close
|
||||
</button>
|
||||
<button onClick={() => void onConfirm()} data-testid="modal-confirm">
|
||||
Confirm Delete
|
||||
</button>
|
||||
</div>
|
||||
) : null,
|
||||
}));
|
||||
|
||||
// Helper wrapper component for testing
|
||||
const TestWrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<MantineProvider defaultColorScheme="light">{children}</MantineProvider>
|
||||
);
|
||||
|
||||
// Custom render function
|
||||
const render = (ui: React.ReactElement) => {
|
||||
return rtlRender(ui, { wrapper: TestWrapper });
|
||||
};
|
||||
|
||||
describe('DangerZoneSettings (Workspace)', () => {
|
||||
beforeEach(async () => {
|
||||
vi.clearAllMocks();
|
||||
mockDeleteCurrentWorkspace.mockResolvedValue(undefined);
|
||||
|
||||
const { useWorkspace } = await import('../../../hooks/useWorkspace');
|
||||
vi.mocked(useWorkspace).mockReturnValue({
|
||||
currentWorkspace: {
|
||||
id: 1,
|
||||
userId: 1,
|
||||
name: 'Test Workspace',
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
theme: Theme.Light,
|
||||
autoSave: false,
|
||||
showHiddenFiles: false,
|
||||
gitEnabled: false,
|
||||
gitUrl: '',
|
||||
gitUser: '',
|
||||
gitToken: '',
|
||||
gitAutoCommit: false,
|
||||
gitCommitMsgTemplate: '',
|
||||
gitCommitName: '',
|
||||
gitCommitEmail: '',
|
||||
},
|
||||
workspaces: [
|
||||
{
|
||||
id: 1,
|
||||
userId: 1,
|
||||
name: 'Workspace 1',
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
theme: Theme.Light,
|
||||
autoSave: false,
|
||||
showHiddenFiles: false,
|
||||
gitEnabled: false,
|
||||
gitUrl: '',
|
||||
gitUser: '',
|
||||
gitToken: '',
|
||||
gitAutoCommit: false,
|
||||
gitCommitMsgTemplate: '',
|
||||
gitCommitName: '',
|
||||
gitCommitEmail: '',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
userId: 1,
|
||||
name: 'Workspace 2',
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
theme: Theme.Light,
|
||||
autoSave: false,
|
||||
showHiddenFiles: false,
|
||||
gitEnabled: false,
|
||||
gitUrl: '',
|
||||
gitUser: '',
|
||||
gitToken: '',
|
||||
gitAutoCommit: false,
|
||||
gitCommitMsgTemplate: '',
|
||||
gitCommitName: '',
|
||||
gitCommitEmail: '',
|
||||
},
|
||||
],
|
||||
settings: {
|
||||
id: 1,
|
||||
userId: 1,
|
||||
name: 'Test Workspace',
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
theme: Theme.Light,
|
||||
autoSave: false,
|
||||
showHiddenFiles: false,
|
||||
gitEnabled: false,
|
||||
gitUrl: '',
|
||||
gitUser: '',
|
||||
gitToken: '',
|
||||
gitAutoCommit: false,
|
||||
gitCommitMsgTemplate: '',
|
||||
gitCommitName: '',
|
||||
gitCommitEmail: '',
|
||||
},
|
||||
updateSettings: vi.fn(),
|
||||
loading: false,
|
||||
colorScheme: 'light',
|
||||
updateColorScheme: vi.fn(),
|
||||
switchWorkspace: vi.fn(),
|
||||
deleteCurrentWorkspace: mockDeleteCurrentWorkspace,
|
||||
});
|
||||
});
|
||||
|
||||
it('renders delete button when multiple workspaces exist', () => {
|
||||
render(<DangerZoneSettings />);
|
||||
|
||||
const deleteButton = screen.getByRole('button', {
|
||||
name: 'Delete Workspace',
|
||||
});
|
||||
expect(deleteButton).toBeInTheDocument();
|
||||
expect(deleteButton).not.toBeDisabled();
|
||||
});
|
||||
|
||||
it('disables delete button when only one workspace exists', async () => {
|
||||
const { useWorkspace } = await import('../../../hooks/useWorkspace');
|
||||
vi.mocked(useWorkspace).mockReturnValue({
|
||||
currentWorkspace: {
|
||||
id: 1,
|
||||
userId: 1,
|
||||
name: 'Last Workspace',
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
theme: Theme.Light,
|
||||
autoSave: false,
|
||||
showHiddenFiles: false,
|
||||
gitEnabled: false,
|
||||
gitUrl: '',
|
||||
gitUser: '',
|
||||
gitToken: '',
|
||||
gitAutoCommit: false,
|
||||
gitCommitMsgTemplate: '',
|
||||
gitCommitName: '',
|
||||
gitCommitEmail: '',
|
||||
},
|
||||
workspaces: [
|
||||
{
|
||||
id: 1,
|
||||
userId: 1,
|
||||
name: 'Last Workspace',
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
theme: Theme.Light,
|
||||
autoSave: false,
|
||||
showHiddenFiles: false,
|
||||
gitEnabled: false,
|
||||
gitUrl: '',
|
||||
gitUser: '',
|
||||
gitToken: '',
|
||||
gitAutoCommit: false,
|
||||
gitCommitMsgTemplate: '',
|
||||
gitCommitName: '',
|
||||
gitCommitEmail: '',
|
||||
},
|
||||
],
|
||||
settings: {
|
||||
id: 1,
|
||||
userId: 1,
|
||||
name: 'Last Workspace',
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
theme: Theme.Light,
|
||||
autoSave: false,
|
||||
showHiddenFiles: false,
|
||||
gitEnabled: false,
|
||||
gitUrl: '',
|
||||
gitUser: '',
|
||||
gitToken: '',
|
||||
gitAutoCommit: false,
|
||||
gitCommitMsgTemplate: '',
|
||||
gitCommitName: '',
|
||||
gitCommitEmail: '',
|
||||
},
|
||||
updateSettings: vi.fn(),
|
||||
loading: false,
|
||||
colorScheme: 'light',
|
||||
updateColorScheme: vi.fn(),
|
||||
switchWorkspace: vi.fn(),
|
||||
deleteCurrentWorkspace: mockDeleteCurrentWorkspace,
|
||||
});
|
||||
|
||||
render(<DangerZoneSettings />);
|
||||
|
||||
const deleteButton = screen.getByRole('button', {
|
||||
name: 'Delete Workspace',
|
||||
});
|
||||
expect(deleteButton).toBeDisabled();
|
||||
expect(deleteButton).toHaveAttribute(
|
||||
'title',
|
||||
'Cannot delete the last workspace'
|
||||
);
|
||||
});
|
||||
|
||||
it('opens and closes delete modal', () => {
|
||||
render(<DangerZoneSettings />);
|
||||
|
||||
const deleteButton = screen.getByRole('button', {
|
||||
name: 'Delete Workspace',
|
||||
});
|
||||
fireEvent.click(deleteButton);
|
||||
|
||||
expect(screen.getByTestId('delete-workspace-modal')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('workspace-name')).toHaveTextContent(
|
||||
'Test Workspace'
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByTestId('modal-close'));
|
||||
expect(
|
||||
screen.queryByTestId('delete-workspace-modal')
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('completes workspace deletion flow', async () => {
|
||||
render(<DangerZoneSettings />);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Delete Workspace' }));
|
||||
fireEvent.click(screen.getByTestId('modal-confirm'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockDeleteCurrentWorkspace).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSetSettingsModalVisible).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
expect(
|
||||
screen.queryByTestId('delete-workspace-modal')
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('allows cancellation of deletion process', () => {
|
||||
render(<DangerZoneSettings />);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Delete Workspace' }));
|
||||
fireEvent.click(screen.getByTestId('modal-close'));
|
||||
|
||||
expect(
|
||||
screen.queryByTestId('delete-workspace-modal')
|
||||
).not.toBeInTheDocument();
|
||||
expect(mockDeleteCurrentWorkspace).not.toHaveBeenCalled();
|
||||
expect(mockSetSettingsModalVisible).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { render as rtlRender, screen, fireEvent } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { MantineProvider } from '@mantine/core';
|
||||
import EditorSettings from './EditorSettings';
|
||||
|
||||
// Helper wrapper component for testing
|
||||
const TestWrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<MantineProvider defaultColorScheme="light">{children}</MantineProvider>
|
||||
);
|
||||
|
||||
// Custom render function
|
||||
const render = (ui: React.ReactElement) => {
|
||||
return rtlRender(ui, { wrapper: TestWrapper });
|
||||
};
|
||||
|
||||
describe('EditorSettings', () => {
|
||||
const mockOnAutoSaveChange = vi.fn();
|
||||
const mockOnShowHiddenFilesChange = vi.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders both toggle switches with labels', () => {
|
||||
render(
|
||||
<EditorSettings
|
||||
autoSave={false}
|
||||
showHiddenFiles={false}
|
||||
onAutoSaveChange={mockOnAutoSaveChange}
|
||||
onShowHiddenFilesChange={mockOnShowHiddenFilesChange}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText('Auto Save')).toBeInTheDocument();
|
||||
expect(screen.getByText('Show Hidden Files')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows correct toggle states', () => {
|
||||
render(
|
||||
<EditorSettings
|
||||
autoSave={true}
|
||||
showHiddenFiles={false}
|
||||
onAutoSaveChange={mockOnAutoSaveChange}
|
||||
onShowHiddenFilesChange={mockOnShowHiddenFilesChange}
|
||||
/>
|
||||
);
|
||||
|
||||
const toggles = screen.getAllByRole('switch');
|
||||
const autoSaveToggle = toggles[0];
|
||||
const hiddenFilesToggle = toggles[1];
|
||||
|
||||
expect(autoSaveToggle).toBeChecked();
|
||||
expect(hiddenFilesToggle).not.toBeChecked();
|
||||
});
|
||||
|
||||
it('calls onShowHiddenFilesChange when toggle is clicked', () => {
|
||||
render(
|
||||
<EditorSettings
|
||||
autoSave={false}
|
||||
showHiddenFiles={false}
|
||||
onAutoSaveChange={mockOnAutoSaveChange}
|
||||
onShowHiddenFilesChange={mockOnShowHiddenFilesChange}
|
||||
/>
|
||||
);
|
||||
|
||||
// Get the show hidden files toggle by finding the one that's not disabled
|
||||
const toggles = screen.getAllByRole('switch');
|
||||
const hiddenFilesToggle = toggles.find(
|
||||
(toggle) => !toggle.hasAttribute('disabled')
|
||||
);
|
||||
|
||||
expect(hiddenFilesToggle).toBeDefined();
|
||||
fireEvent.click(hiddenFilesToggle!);
|
||||
|
||||
expect(mockOnShowHiddenFilesChange).toHaveBeenCalledWith(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,61 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { render as rtlRender, screen, fireEvent } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { MantineProvider } from '@mantine/core';
|
||||
import GeneralSettings from './GeneralSettings';
|
||||
|
||||
// Helper wrapper component for testing
|
||||
const TestWrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<MantineProvider defaultColorScheme="light">{children}</MantineProvider>
|
||||
);
|
||||
|
||||
// Custom render function
|
||||
const render = (ui: React.ReactElement) => {
|
||||
return rtlRender(ui, { wrapper: TestWrapper });
|
||||
};
|
||||
|
||||
describe('GeneralSettings', () => {
|
||||
const mockOnInputChange = vi.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders workspace name input with current value', () => {
|
||||
render(
|
||||
<GeneralSettings name="My Workspace" onInputChange={mockOnInputChange} />
|
||||
);
|
||||
|
||||
const nameInput = screen.getByDisplayValue('My Workspace');
|
||||
expect(nameInput).toBeInTheDocument();
|
||||
expect(screen.getByText('Workspace Name')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders with empty name', () => {
|
||||
render(<GeneralSettings name="" onInputChange={mockOnInputChange} />);
|
||||
|
||||
const nameInput = screen.getByPlaceholderText('Enter workspace name');
|
||||
expect(nameInput).toHaveValue('');
|
||||
});
|
||||
|
||||
it('calls onInputChange when name is modified', () => {
|
||||
render(
|
||||
<GeneralSettings name="Old Name" onInputChange={mockOnInputChange} />
|
||||
);
|
||||
|
||||
const nameInput = screen.getByDisplayValue('Old Name');
|
||||
fireEvent.change(nameInput, { target: { value: 'New Workspace Name' } });
|
||||
|
||||
expect(mockOnInputChange).toHaveBeenCalledWith(
|
||||
'name',
|
||||
'New Workspace Name'
|
||||
);
|
||||
});
|
||||
|
||||
it('has required attribute on input', () => {
|
||||
render(<GeneralSettings name="Test" onInputChange={mockOnInputChange} />);
|
||||
|
||||
const nameInput = screen.getByDisplayValue('Test');
|
||||
expect(nameInput).toHaveAttribute('required');
|
||||
});
|
||||
});
|
||||
134
app/src/components/settings/workspace/GitSettings.test.tsx
Normal file
134
app/src/components/settings/workspace/GitSettings.test.tsx
Normal file
@@ -0,0 +1,134 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { render as rtlRender, screen, fireEvent } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { MantineProvider } from '@mantine/core';
|
||||
import GitSettings from './GitSettings';
|
||||
|
||||
// Helper wrapper component for testing
|
||||
const TestWrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<MantineProvider defaultColorScheme="light">{children}</MantineProvider>
|
||||
);
|
||||
|
||||
// Custom render function
|
||||
const render = (ui: React.ReactElement) => {
|
||||
return rtlRender(ui, { wrapper: TestWrapper });
|
||||
};
|
||||
|
||||
describe('GitSettings', () => {
|
||||
const mockOnInputChange = vi.fn();
|
||||
|
||||
const defaultProps = {
|
||||
gitEnabled: false,
|
||||
gitUrl: '',
|
||||
gitUser: '',
|
||||
gitToken: '',
|
||||
gitAutoCommit: false,
|
||||
gitCommitMsgTemplate: '',
|
||||
gitCommitName: '',
|
||||
gitCommitEmail: '',
|
||||
onInputChange: mockOnInputChange,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders all git settings fields', () => {
|
||||
render(<GitSettings {...defaultProps} />);
|
||||
|
||||
expect(screen.getByText('Enable Git Repository')).toBeInTheDocument();
|
||||
expect(screen.getByText('Git URL')).toBeInTheDocument();
|
||||
expect(screen.getByText('Username')).toBeInTheDocument();
|
||||
expect(screen.getByText('Access Token')).toBeInTheDocument();
|
||||
expect(screen.getByText('Commit on Save')).toBeInTheDocument();
|
||||
expect(screen.getByText('Commit Message Template')).toBeInTheDocument();
|
||||
expect(screen.getByText('Commit Author')).toBeInTheDocument();
|
||||
expect(screen.getByText('Commit Author Email')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('disables all inputs when git is not enabled', () => {
|
||||
render(<GitSettings {...defaultProps} gitEnabled={false} />);
|
||||
|
||||
expect(screen.getByPlaceholderText('Enter Git URL')).toBeDisabled();
|
||||
expect(screen.getByPlaceholderText('Enter Git username')).toBeDisabled();
|
||||
expect(screen.getByPlaceholderText('Enter Git token')).toBeDisabled();
|
||||
|
||||
const switches = screen.getAllByRole('switch');
|
||||
const commitOnSaveSwitch = switches[1]; // Second switch is commit on save
|
||||
expect(commitOnSaveSwitch).toBeDisabled();
|
||||
});
|
||||
|
||||
it('enables all inputs when git is enabled', () => {
|
||||
render(<GitSettings {...defaultProps} gitEnabled={true} />);
|
||||
|
||||
expect(screen.getByPlaceholderText('Enter Git URL')).not.toBeDisabled();
|
||||
expect(
|
||||
screen.getByPlaceholderText('Enter Git username')
|
||||
).not.toBeDisabled();
|
||||
expect(screen.getByPlaceholderText('Enter Git token')).not.toBeDisabled();
|
||||
|
||||
const switches = screen.getAllByRole('switch');
|
||||
const commitOnSaveSwitch = switches[1];
|
||||
expect(commitOnSaveSwitch).not.toBeDisabled();
|
||||
});
|
||||
|
||||
it('calls onInputChange when git enabled toggle is changed', () => {
|
||||
render(<GitSettings {...defaultProps} />);
|
||||
|
||||
const switches = screen.getAllByRole('switch');
|
||||
const gitEnabledSwitch = switches[0];
|
||||
expect(gitEnabledSwitch).toBeDefined();
|
||||
|
||||
fireEvent.click(gitEnabledSwitch!);
|
||||
|
||||
expect(mockOnInputChange).toHaveBeenCalledWith('gitEnabled', true);
|
||||
});
|
||||
|
||||
it('calls onInputChange when git URL is changed', () => {
|
||||
render(<GitSettings {...defaultProps} gitEnabled={true} />);
|
||||
|
||||
const urlInput = screen.getByPlaceholderText('Enter Git URL');
|
||||
fireEvent.change(urlInput, {
|
||||
target: { value: 'https://github.com/user/repo.git' },
|
||||
});
|
||||
|
||||
expect(mockOnInputChange).toHaveBeenCalledWith(
|
||||
'gitUrl',
|
||||
'https://github.com/user/repo.git'
|
||||
);
|
||||
});
|
||||
|
||||
it('calls onInputChange when commit template is changed', () => {
|
||||
render(<GitSettings {...defaultProps} gitEnabled={true} />);
|
||||
|
||||
const templateInput = screen.getByPlaceholderText(
|
||||
'Enter commit message template'
|
||||
);
|
||||
fireEvent.change(templateInput, {
|
||||
target: { value: '${action}: ${filename}' },
|
||||
});
|
||||
|
||||
expect(mockOnInputChange).toHaveBeenCalledWith(
|
||||
'gitCommitMsgTemplate',
|
||||
'${action}: ${filename}'
|
||||
);
|
||||
});
|
||||
|
||||
it('shows current values in form fields', () => {
|
||||
const propsWithValues = {
|
||||
...defaultProps,
|
||||
gitEnabled: true,
|
||||
gitUrl: 'https://github.com/test/repo.git',
|
||||
gitUser: 'testuser',
|
||||
gitCommitMsgTemplate: 'Update ${filename}',
|
||||
};
|
||||
|
||||
render(<GitSettings {...propsWithValues} />);
|
||||
|
||||
expect(
|
||||
screen.getByDisplayValue('https://github.com/test/repo.git')
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByDisplayValue('testuser')).toBeInTheDocument();
|
||||
expect(screen.getByDisplayValue('Update ${filename}')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
212
app/src/components/settings/workspace/WorkspaceSettings.test.tsx
Normal file
212
app/src/components/settings/workspace/WorkspaceSettings.test.tsx
Normal file
@@ -0,0 +1,212 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import {
|
||||
render as rtlRender,
|
||||
screen,
|
||||
fireEvent,
|
||||
waitFor,
|
||||
} from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { MantineProvider } from '@mantine/core';
|
||||
import WorkspaceSettings from './WorkspaceSettings';
|
||||
import { Theme } from '@/types/models';
|
||||
|
||||
const mockUpdateSettings = vi.fn();
|
||||
vi.mock('../../../hooks/useWorkspace', () => ({
|
||||
useWorkspace: vi.fn(),
|
||||
}));
|
||||
|
||||
const mockSetSettingsModalVisible = vi.fn();
|
||||
vi.mock('../../../contexts/ModalContext', () => ({
|
||||
useModalContext: () => ({
|
||||
settingsModalVisible: true,
|
||||
setSettingsModalVisible: mockSetSettingsModalVisible,
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@mantine/notifications', () => ({
|
||||
notifications: {
|
||||
show: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('./GeneralSettings', () => ({
|
||||
default: ({
|
||||
name,
|
||||
onInputChange,
|
||||
}: {
|
||||
name: string;
|
||||
onInputChange: (key: string, value: string) => void;
|
||||
}) => (
|
||||
<div data-testid="general-settings">
|
||||
<input
|
||||
data-testid="workspace-name-input"
|
||||
value={name}
|
||||
onChange={(e) => onInputChange('name', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock('./AppearanceSettings', () => ({
|
||||
default: ({ onThemeChange }: { onThemeChange: (theme: string) => void }) => (
|
||||
<div data-testid="appearance-settings">
|
||||
<button onClick={() => onThemeChange('dark')} data-testid="theme-toggle">
|
||||
Toggle Theme
|
||||
</button>
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock('./EditorSettings', () => ({
|
||||
default: () => <div data-testid="editor-settings">Editor Settings</div>,
|
||||
}));
|
||||
|
||||
vi.mock('./GitSettings', () => ({
|
||||
default: () => <div data-testid="git-settings">Git Settings</div>,
|
||||
}));
|
||||
|
||||
vi.mock('./DangerZoneSettings', () => ({
|
||||
default: () => <div data-testid="danger-zone-settings">Danger Zone</div>,
|
||||
}));
|
||||
|
||||
// Helper wrapper component for testing
|
||||
const TestWrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<MantineProvider defaultColorScheme="light">{children}</MantineProvider>
|
||||
);
|
||||
|
||||
// Custom render function
|
||||
const render = (ui: React.ReactElement) => {
|
||||
return rtlRender(ui, { wrapper: TestWrapper });
|
||||
};
|
||||
|
||||
describe('WorkspaceSettings', () => {
|
||||
beforeEach(async () => {
|
||||
vi.clearAllMocks();
|
||||
mockUpdateSettings.mockResolvedValue(undefined);
|
||||
|
||||
const { useWorkspace } = await import('../../../hooks/useWorkspace');
|
||||
vi.mocked(useWorkspace).mockReturnValue({
|
||||
currentWorkspace: {
|
||||
name: 'Test Workspace',
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
theme: Theme.Light,
|
||||
autoSave: false,
|
||||
showHiddenFiles: false,
|
||||
gitEnabled: false,
|
||||
gitUrl: '',
|
||||
gitUser: '',
|
||||
gitToken: '',
|
||||
gitAutoCommit: false,
|
||||
gitCommitMsgTemplate: '',
|
||||
gitCommitName: '',
|
||||
gitCommitEmail: '',
|
||||
},
|
||||
workspaces: [],
|
||||
settings: {
|
||||
id: 1,
|
||||
userId: 1,
|
||||
name: 'Test Workspace',
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
theme: Theme.Light,
|
||||
autoSave: false,
|
||||
showHiddenFiles: false,
|
||||
gitEnabled: false,
|
||||
gitUrl: '',
|
||||
gitUser: '',
|
||||
gitToken: '',
|
||||
gitAutoCommit: false,
|
||||
gitCommitMsgTemplate: '',
|
||||
gitCommitName: '',
|
||||
gitCommitEmail: '',
|
||||
},
|
||||
updateSettings: mockUpdateSettings,
|
||||
loading: false,
|
||||
colorScheme: 'light',
|
||||
updateColorScheme: vi.fn(),
|
||||
switchWorkspace: vi.fn(),
|
||||
deleteCurrentWorkspace: vi.fn(),
|
||||
});
|
||||
});
|
||||
|
||||
it('renders modal with all setting sections', () => {
|
||||
render(<WorkspaceSettings />);
|
||||
|
||||
expect(screen.getByText('Workspace Settings')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('general-settings')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('appearance-settings')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('editor-settings')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('git-settings')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('danger-zone-settings')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows unsaved changes badge when settings are modified', () => {
|
||||
render(<WorkspaceSettings />);
|
||||
|
||||
const nameInput = screen.getByTestId('workspace-name-input');
|
||||
fireEvent.change(nameInput, { target: { value: 'Updated Workspace' } });
|
||||
|
||||
expect(screen.getByText('Unsaved Changes')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('saves settings successfully', async () => {
|
||||
render(<WorkspaceSettings />);
|
||||
|
||||
const nameInput = screen.getByTestId('workspace-name-input');
|
||||
fireEvent.change(nameInput, { target: { value: 'Updated Workspace' } });
|
||||
|
||||
const saveButton = screen.getByRole('button', { name: 'Save Changes' });
|
||||
expect(saveButton).toBeDefined();
|
||||
fireEvent.click(saveButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockUpdateSettings).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ name: 'Updated Workspace' })
|
||||
);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSetSettingsModalVisible).toHaveBeenCalledWith(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('handles theme changes', () => {
|
||||
render(<WorkspaceSettings />);
|
||||
|
||||
const themeToggle = screen.getByTestId('theme-toggle');
|
||||
fireEvent.click(themeToggle);
|
||||
|
||||
expect(screen.getByText('Unsaved Changes')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('closes modal when cancel is clicked', () => {
|
||||
render(<WorkspaceSettings />);
|
||||
|
||||
const cancelButton = screen.getByRole('button', { name: 'Cancel' });
|
||||
fireEvent.click(cancelButton);
|
||||
|
||||
expect(mockSetSettingsModalVisible).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
it('prevents saving with empty workspace name', async () => {
|
||||
const { notifications } = await import('@mantine/notifications');
|
||||
|
||||
render(<WorkspaceSettings />);
|
||||
|
||||
const nameInput = screen.getByTestId('workspace-name-input');
|
||||
fireEvent.change(nameInput, { target: { value: ' ' } }); // Empty/whitespace
|
||||
|
||||
const saveButton = screen.getByRole('button', { name: 'Save Changes' });
|
||||
fireEvent.click(saveButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(notifications.show).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
message: 'Workspace name cannot be empty',
|
||||
color: 'red',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
expect(mockUpdateSettings).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -235,7 +235,7 @@ const WorkspaceSettings: React.FC = () => {
|
||||
<Button variant="default" onClick={handleClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={() => void handleSubmit}>Save Changes</Button>
|
||||
<Button onClick={() => void handleSubmit()}>Save Changes</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Modal>
|
||||
|
||||
Reference in New Issue
Block a user