Refactor hooks and hook tests error handling and state management

This commit is contained in:
2025-05-28 22:01:27 +02:00
parent 907dffe362
commit d814c365ea
4 changed files with 728 additions and 104 deletions

View File

@@ -62,9 +62,7 @@ describe('useFileNavigation', () => {
}); });
expect(mockLastOpenedFile.loadLastOpenedFile).toHaveBeenCalled(); expect(mockLastOpenedFile.loadLastOpenedFile).toHaveBeenCalled();
expect(mockLastOpenedFile.saveLastOpenedFile).toHaveBeenCalledWith( expect(mockLastOpenedFile.saveLastOpenedFile).not.toHaveBeenCalled();
'documents/readme.md'
);
}); });
it('stays with default file when no last opened file exists', async () => { it('stays with default file when no last opened file exists', async () => {
@@ -90,8 +88,11 @@ describe('useFileNavigation', () => {
await result.current.handleFileSelect('notes/todo.md'); await result.current.handleFileSelect('notes/todo.md');
}); });
expect(result.current.selectedFile).toBe('notes/todo.md'); await waitFor(() => {
expect(result.current.isNewFile).toBe(false); expect(result.current.selectedFile).toBe('notes/todo.md');
expect(result.current.isNewFile).toBe(false);
});
expect(mockLastOpenedFile.saveLastOpenedFile).toHaveBeenCalledWith( expect(mockLastOpenedFile.saveLastOpenedFile).toHaveBeenCalledWith(
'notes/todo.md' 'notes/todo.md'
); );
@@ -109,7 +110,7 @@ describe('useFileNavigation', () => {
expect(mockLastOpenedFile.saveLastOpenedFile).not.toHaveBeenCalled(); expect(mockLastOpenedFile.saveLastOpenedFile).not.toHaveBeenCalled();
}); });
it('handles empty string file selection', async () => { it('handles empty string file selection with default file', async () => {
const { result } = renderHook(() => useFileNavigation()); const { result } = renderHook(() => useFileNavigation());
await act(async () => { await act(async () => {
@@ -121,6 +122,32 @@ describe('useFileNavigation', () => {
expect(mockLastOpenedFile.saveLastOpenedFile).not.toHaveBeenCalled(); expect(mockLastOpenedFile.saveLastOpenedFile).not.toHaveBeenCalled();
}); });
it('preserves current selection when passed empty string with existing selection', async () => {
const { result } = renderHook(() => useFileNavigation());
// First select a valid file
await act(async () => {
await result.current.handleFileSelect('existing-file.md');
});
await waitFor(() => {
expect(result.current.selectedFile).toBe('existing-file.md');
expect(result.current.isNewFile).toBe(false);
});
vi.clearAllMocks();
// Now send empty string
await act(async () => {
await result.current.handleFileSelect('');
});
// Selection should be preserved
expect(result.current.selectedFile).toBe('existing-file.md');
expect(result.current.isNewFile).toBe(false);
expect(mockLastOpenedFile.saveLastOpenedFile).not.toHaveBeenCalled();
});
it('handles different file path formats', async () => { it('handles different file path formats', async () => {
const { result } = renderHook(() => useFileNavigation()); const { result } = renderHook(() => useFileNavigation());
@@ -138,8 +165,11 @@ describe('useFileNavigation', () => {
await result.current.handleFileSelect(filePath); await result.current.handleFileSelect(filePath);
}); });
expect(result.current.selectedFile).toBe(filePath); await waitFor(() => {
expect(result.current.isNewFile).toBe(false); expect(result.current.selectedFile).toBe(filePath);
expect(result.current.isNewFile).toBe(false);
});
expect(mockLastOpenedFile.saveLastOpenedFile).toHaveBeenCalledWith( expect(mockLastOpenedFile.saveLastOpenedFile).toHaveBeenCalledWith(
filePath filePath
); );
@@ -155,16 +185,22 @@ describe('useFileNavigation', () => {
const files = ['file1.md', 'file2.md', 'file3.md']; const files = ['file1.md', 'file2.md', 'file3.md'];
await act(async () => { // Use sequential state updates instead of Promise.all for more predictable results
await Promise.all( for (const file of files) {
files.map((file) => result.current.handleFileSelect(file)) await act(async () => {
); await result.current.handleFileSelect(file);
});
}
// After all updates, we should have the last file selected
await waitFor(() => {
expect(result.current.selectedFile).toBe(files[files.length - 1]);
expect(result.current.isNewFile).toBe(false);
}); });
// Should end up with the last file (depending on async timing) expect(mockLastOpenedFile.saveLastOpenedFile).toHaveBeenCalledTimes(
expect(files).toContain(result.current.selectedFile); files.length
expect(result.current.isNewFile).toBe(false); );
expect(mockLastOpenedFile.saveLastOpenedFile).toHaveBeenCalledTimes(3);
}); });
it('handles file selection errors gracefully', async () => { it('handles file selection errors gracefully', async () => {
@@ -179,8 +215,11 @@ describe('useFileNavigation', () => {
await result.current.handleFileSelect('error-file.md'); await result.current.handleFileSelect('error-file.md');
}); });
expect(result.current.selectedFile).toBe('error-file.md'); // Wait for state update despite the error
expect(result.current.isNewFile).toBe(false); await waitFor(() => {
expect(result.current.selectedFile).toBe('error-file.md');
expect(result.current.isNewFile).toBe(false);
});
}); });
}); });
@@ -324,15 +363,22 @@ describe('useFileNavigation', () => {
await result.current.handleFileSelect('real-file.md'); await result.current.handleFileSelect('real-file.md');
}); });
expect(result.current.isNewFile).toBe(false); // Wait for state to update
await waitFor(() => {
expect(result.current.selectedFile).toBe('real-file.md');
expect(result.current.isNewFile).toBe(false);
});
// Go back to null (should default to default file) // Go back to null (should default to default file)
await act(async () => { await act(async () => {
await result.current.handleFileSelect(null); await result.current.handleFileSelect(null);
}); });
expect(result.current.selectedFile).toBe(DEFAULT_FILE.path); // Wait for state to update again
expect(result.current.isNewFile).toBe(true); await waitFor(() => {
expect(result.current.selectedFile).toBe(DEFAULT_FILE.path);
expect(result.current.isNewFile).toBe(true);
});
}); });
it('maintains correct isNewFile state for regular files', async () => { it('maintains correct isNewFile state for regular files', async () => {
@@ -345,8 +391,11 @@ describe('useFileNavigation', () => {
await result.current.handleFileSelect(file); await result.current.handleFileSelect(file);
}); });
expect(result.current.selectedFile).toBe(file); // Wait for each file selection to complete
expect(result.current.isNewFile).toBe(false); await waitFor(() => {
expect(result.current.selectedFile).toBe(file);
expect(result.current.isNewFile).toBe(false);
});
} }
}); });
}); });
@@ -414,8 +463,10 @@ describe('useFileNavigation', () => {
}); });
// State should still be updated despite save error // State should still be updated despite save error
expect(result.current.selectedFile).toBe('test-file.md'); await waitFor(() => {
expect(result.current.isNewFile).toBe(false); expect(result.current.selectedFile).toBe('test-file.md');
expect(result.current.isNewFile).toBe(false);
});
}); });
}); });
}); });

View File

@@ -17,35 +17,55 @@ export const useFileNavigation = (): UseFileNavigationResult => {
const handleFileSelect = useCallback( const handleFileSelect = useCallback(
async (filePath: string | null): Promise<void> => { async (filePath: string | null): Promise<void> => {
const newPath = filePath || DEFAULT_FILE.path; // Consider empty string as null
setSelectedFile(newPath); const effectiveFilePath = filePath === '' ? null : filePath;
setIsNewFile(!filePath);
if (filePath) { if (effectiveFilePath) {
await saveLastOpenedFile(filePath); setSelectedFile(effectiveFilePath);
setIsNewFile(false);
try {
// Try to save the last opened file
await saveLastOpenedFile(effectiveFilePath);
} catch (err) {
// Silently handle the error so state still updates
console.error('Failed to save last opened file:', err);
}
} else if (selectedFile === DEFAULT_FILE.path || filePath === null) {
setSelectedFile(DEFAULT_FILE.path);
setIsNewFile(true);
} }
}, },
[saveLastOpenedFile] [saveLastOpenedFile, selectedFile]
); );
// Load last opened file when workspace changes // Load last opened file when workspace changes
useEffect(() => { useEffect(() => {
const initializeFile = async (): Promise<void> => { const initializeFile = async (): Promise<void> => {
setSelectedFile(DEFAULT_FILE.path); try {
setIsNewFile(true); setSelectedFile(DEFAULT_FILE.path);
setIsNewFile(true);
const lastFile = await loadLastOpenedFile(); const lastFile = await loadLastOpenedFile();
if (lastFile) {
await handleFileSelect(lastFile); if (lastFile) {
} else { setSelectedFile(lastFile);
await handleFileSelect(null); setIsNewFile(false);
}
} catch (err) {
console.error('Failed to load last opened file:', err);
setSelectedFile(DEFAULT_FILE.path);
setIsNewFile(true);
} }
}; };
if (currentWorkspace) { if (currentWorkspace) {
void initializeFile(); void initializeFile();
} else {
setSelectedFile(DEFAULT_FILE.path);
setIsNewFile(true);
} }
}, [currentWorkspace, loadLastOpenedFile, handleFileSelect]); }, [currentWorkspace, loadLastOpenedFile, saveLastOpenedFile]);
return { selectedFile, isNewFile, handleFileSelect }; return { selectedFile, isNewFile, handleFileSelect };
}; };

View File

@@ -5,6 +5,7 @@ import {
deleteUser as adminDeleteUser, deleteUser as adminDeleteUser,
} from '../api/admin'; } from '../api/admin';
import { notifications } from '@mantine/notifications'; import { notifications } from '@mantine/notifications';
import { useCallback } from 'react';
import type { User } from '@/types/models'; import type { User } from '@/types/models';
import type { CreateUserRequest, UpdateUserRequest } from '@/types/api'; import type { CreateUserRequest, UpdateUserRequest } from '@/types/api';
@@ -20,73 +21,77 @@ interface UseUserAdminResult {
export const useUserAdmin = (): UseUserAdminResult => { export const useUserAdmin = (): UseUserAdminResult => {
const { data: users, loading, error, reload } = useAdminData('users'); const { data: users, loading, error, reload } = useAdminData('users');
const handleCreate = async ( const handleCreate = useCallback(
userData: CreateUserRequest async (userData: CreateUserRequest): Promise<boolean> => {
): Promise<boolean> => { try {
try { await createUser(userData);
await createUser(userData); notifications.show({
notifications.show({ title: 'Success',
title: 'Success', message: 'User created successfully',
message: 'User created successfully', color: 'green',
color: 'green', });
}); await reload();
await reload(); return true;
return true; } catch (err) {
} catch (err) { const message = err instanceof Error ? err.message : String(err);
const message = err instanceof Error ? err.message : String(err); notifications.show({
notifications.show({ title: 'Error',
title: 'Error', message: `Failed to create user: ${message}`,
message: `Failed to create user: ${message}`, color: 'red',
color: 'red', });
}); return false;
return false; }
} },
}; [reload]
);
const handleUpdate = async ( const handleUpdate = useCallback(
userId: number, async (userId: number, userData: UpdateUserRequest): Promise<boolean> => {
userData: UpdateUserRequest try {
): Promise<boolean> => { await updateUser(userId, userData);
try { notifications.show({
await updateUser(userId, userData); title: 'Success',
notifications.show({ message: 'User updated successfully',
title: 'Success', color: 'green',
message: 'User updated successfully', });
color: 'green', await reload();
}); return true;
await reload(); } catch (err) {
return true; const message = err instanceof Error ? err.message : String(err);
} catch (err) { notifications.show({
const message = err instanceof Error ? err.message : String(err); title: 'Error',
notifications.show({ message: `Failed to update user: ${message}`,
title: 'Error', color: 'red',
message: `Failed to update user: ${message}`, });
color: 'red', return false;
}); }
return false; },
} [reload]
}; );
const handleDelete = async (userId: number): Promise<boolean> => { const handleDelete = useCallback(
try { async (userId: number): Promise<boolean> => {
await adminDeleteUser(userId); try {
notifications.show({ await adminDeleteUser(userId);
title: 'Success', notifications.show({
message: 'User deleted successfully', title: 'Success',
color: 'green', message: 'User deleted successfully',
}); color: 'green',
await reload(); });
return true; await reload();
} catch (err) { return true;
const message = err instanceof Error ? err.message : String(err); } catch (err) {
notifications.show({ const message = err instanceof Error ? err.message : String(err);
title: 'Error', notifications.show({
message: `Failed to delete user: ${message}`, title: 'Error',
color: 'red', message: `Failed to delete user: ${message}`,
}); color: 'red',
return false; });
} return false;
}; }
},
[reload]
);
return { return {
users, users,

View File

@@ -0,0 +1,548 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { renderHook } from '@testing-library/react';
import { useWorkspace } from './useWorkspace';
import {
Theme,
type Workspace,
DEFAULT_WORKSPACE_SETTINGS,
} from '@/types/models';
import type { MantineColorScheme } from '@mantine/core';
// Mock the constituent hooks
const mockWorkspaceData = {
currentWorkspace: null as Workspace | null,
workspaces: [] as Workspace[],
settings: DEFAULT_WORKSPACE_SETTINGS,
loading: false,
};
const mockTheme = {
colorScheme: 'light' as MantineColorScheme,
updateColorScheme: vi.fn(),
};
const mockWorkspaceOperations = {
switchWorkspace: vi.fn(),
deleteCurrentWorkspace: vi.fn(),
updateSettings: vi.fn(),
};
vi.mock('../contexts/WorkspaceDataContext', () => ({
useWorkspaceData: () => mockWorkspaceData,
}));
vi.mock('../contexts/ThemeContext', () => ({
useTheme: () => mockTheme,
}));
vi.mock('./useWorkspaceOperations', () => ({
useWorkspaceOperations: () => mockWorkspaceOperations,
}));
// Mock workspace data
const mockWorkspace: Workspace = {
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: '${action} ${filename}',
gitCommitName: '',
gitCommitEmail: '',
};
const mockWorkspaces: Workspace[] = [
mockWorkspace,
{
id: 2,
userId: 1,
name: 'second-workspace',
createdAt: '2024-01-02T00:00:00Z',
theme: Theme.Dark,
autoSave: true,
showHiddenFiles: true,
gitEnabled: true,
gitUrl: 'https://github.com/user/repo.git',
gitUser: 'user',
gitToken: 'token',
gitAutoCommit: true,
gitCommitMsgTemplate: 'auto: ${action} ${filename}',
gitCommitName: 'Test User',
gitCommitEmail: 'test@example.com',
},
];
describe('useWorkspace', () => {
beforeEach(() => {
vi.clearAllMocks();
// Reset mock data to defaults
mockWorkspaceData.currentWorkspace = null;
mockWorkspaceData.workspaces = [];
mockWorkspaceData.settings = DEFAULT_WORKSPACE_SETTINGS;
mockWorkspaceData.loading = false;
mockTheme.colorScheme = 'light';
});
afterEach(() => {
vi.restoreAllMocks();
});
describe('initial state', () => {
it('returns default values when no workspace is loaded', () => {
const { result } = renderHook(() => useWorkspace());
expect(result.current.currentWorkspace).toBeNull();
expect(result.current.workspaces).toEqual([]);
expect(result.current.settings).toEqual(DEFAULT_WORKSPACE_SETTINGS);
expect(result.current.loading).toBe(false);
expect(result.current.colorScheme).toBe('light');
});
it('provides all expected functions', () => {
const { result } = renderHook(() => useWorkspace());
expect(typeof result.current.updateSettings).toBe('function');
expect(typeof result.current.updateColorScheme).toBe('function');
expect(typeof result.current.switchWorkspace).toBe('function');
expect(typeof result.current.deleteCurrentWorkspace).toBe('function');
});
});
describe('workspace data integration', () => {
it('returns current workspace data', () => {
mockWorkspaceData.currentWorkspace = mockWorkspace;
mockWorkspaceData.workspaces = mockWorkspaces;
mockWorkspaceData.settings = mockWorkspace;
const { result } = renderHook(() => useWorkspace());
expect(result.current.currentWorkspace).toEqual(mockWorkspace);
expect(result.current.workspaces).toEqual(mockWorkspaces);
expect(result.current.settings).toEqual(mockWorkspace);
});
it('returns loading state from workspace data', () => {
mockWorkspaceData.loading = true;
const { result } = renderHook(() => useWorkspace());
expect(result.current.loading).toBe(true);
});
it('uses default settings when no current workspace', () => {
mockWorkspaceData.currentWorkspace = null;
mockWorkspaceData.settings = DEFAULT_WORKSPACE_SETTINGS;
const { result } = renderHook(() => useWorkspace());
expect(result.current.settings).toEqual(DEFAULT_WORKSPACE_SETTINGS);
});
it('uses current workspace as settings when available', () => {
mockWorkspaceData.currentWorkspace = mockWorkspace;
mockWorkspaceData.settings = mockWorkspace;
const { result } = renderHook(() => useWorkspace());
expect(result.current.settings).toEqual(mockWorkspace);
});
});
describe('theme integration', () => {
it('returns color scheme from theme context', () => {
mockTheme.colorScheme = 'dark';
const { result } = renderHook(() => useWorkspace());
expect(result.current.colorScheme).toBe('dark');
});
it('provides updateColorScheme function from theme context', () => {
const { result } = renderHook(() => useWorkspace());
expect(result.current.updateColorScheme).toBe(
mockTheme.updateColorScheme
);
});
it('handles light theme', () => {
mockTheme.colorScheme = 'light';
const { result } = renderHook(() => useWorkspace());
expect(result.current.colorScheme).toBe('light');
});
it('handles auto theme', () => {
mockTheme.colorScheme = 'auto';
const { result } = renderHook(() => useWorkspace());
expect(result.current.colorScheme).toBe('auto');
});
});
describe('workspace operations integration', () => {
it('provides switchWorkspace function from operations', () => {
const { result } = renderHook(() => useWorkspace());
expect(result.current.switchWorkspace).toBe(
mockWorkspaceOperations.switchWorkspace
);
});
it('provides deleteCurrentWorkspace function from operations', () => {
const { result } = renderHook(() => useWorkspace());
expect(result.current.deleteCurrentWorkspace).toBe(
mockWorkspaceOperations.deleteCurrentWorkspace
);
});
it('provides updateSettings function from operations', () => {
const { result } = renderHook(() => useWorkspace());
expect(result.current.updateSettings).toBe(
mockWorkspaceOperations.updateSettings
);
});
});
describe('data consistency', () => {
it('returns consistent data across multiple renders', () => {
mockWorkspaceData.currentWorkspace = mockWorkspace;
mockWorkspaceData.workspaces = mockWorkspaces;
mockWorkspaceData.settings = mockWorkspace;
mockTheme.colorScheme = 'dark';
const { result, rerender } = renderHook(() => useWorkspace());
const firstResult = { ...result.current };
rerender();
expect(result.current.currentWorkspace).toEqual(
firstResult.currentWorkspace
);
expect(result.current.workspaces).toEqual(firstResult.workspaces);
expect(result.current.settings).toEqual(firstResult.settings);
expect(result.current.colorScheme).toEqual(firstResult.colorScheme);
});
it('reflects changes in underlying data', () => {
const { result, rerender } = renderHook(() => useWorkspace());
// Initially no workspace
expect(result.current.currentWorkspace).toBeNull();
expect(result.current.workspaces).toEqual([]);
// Add workspace data
mockWorkspaceData.currentWorkspace = mockWorkspace;
mockWorkspaceData.workspaces = mockWorkspaces;
mockWorkspaceData.settings = mockWorkspace;
rerender();
expect(result.current.currentWorkspace).toEqual(mockWorkspace);
expect(result.current.workspaces).toEqual(mockWorkspaces);
expect(result.current.settings).toEqual(mockWorkspace);
});
it('reflects theme changes', () => {
const { result, rerender } = renderHook(() => useWorkspace());
// Initially light theme
expect(result.current.colorScheme).toBe('light');
// Change to dark theme
mockTheme.colorScheme = 'dark';
rerender();
expect(result.current.colorScheme).toBe('dark');
});
it('reflects loading state changes', () => {
const { result, rerender } = renderHook(() => useWorkspace());
// Initially not loading
expect(result.current.loading).toBe(false);
// Change to loading
mockWorkspaceData.loading = true;
rerender();
expect(result.current.loading).toBe(true);
});
});
describe('function stability', () => {
it('maintains stable function references across re-renders', () => {
const { result, rerender } = renderHook(() => useWorkspace());
const initialFunctions = {
updateSettings: result.current.updateSettings,
updateColorScheme: result.current.updateColorScheme,
switchWorkspace: result.current.switchWorkspace,
deleteCurrentWorkspace: result.current.deleteCurrentWorkspace,
};
rerender();
expect(result.current.updateSettings).toBe(
initialFunctions.updateSettings
);
expect(result.current.updateColorScheme).toBe(
initialFunctions.updateColorScheme
);
expect(result.current.switchWorkspace).toBe(
initialFunctions.switchWorkspace
);
expect(result.current.deleteCurrentWorkspace).toBe(
initialFunctions.deleteCurrentWorkspace
);
});
it('maintains stable function references when data changes', () => {
const { result, rerender } = renderHook(() => useWorkspace());
const initialFunctions = {
updateSettings: result.current.updateSettings,
updateColorScheme: result.current.updateColorScheme,
switchWorkspace: result.current.switchWorkspace,
deleteCurrentWorkspace: result.current.deleteCurrentWorkspace,
};
// Change data
mockWorkspaceData.currentWorkspace = mockWorkspace;
mockWorkspaceData.workspaces = mockWorkspaces;
mockTheme.colorScheme = 'dark';
rerender();
expect(result.current.updateSettings).toBe(
initialFunctions.updateSettings
);
expect(result.current.updateColorScheme).toBe(
initialFunctions.updateColorScheme
);
expect(result.current.switchWorkspace).toBe(
initialFunctions.switchWorkspace
);
expect(result.current.deleteCurrentWorkspace).toBe(
initialFunctions.deleteCurrentWorkspace
);
});
});
describe('hook interface', () => {
it('returns correct interface structure', () => {
const { result } = renderHook(() => useWorkspace());
const expectedKeys = [
'currentWorkspace',
'workspaces',
'settings',
'updateSettings',
'loading',
'colorScheme',
'updateColorScheme',
'switchWorkspace',
'deleteCurrentWorkspace',
];
expectedKeys.forEach((key) => {
expect(key in result.current).toBe(true);
});
});
it('returns correct types for all properties', () => {
const { result } = renderHook(() => useWorkspace());
expect(
result.current.currentWorkspace === null ||
typeof result.current.currentWorkspace === 'object'
).toBe(true);
expect(Array.isArray(result.current.workspaces)).toBe(true);
expect(typeof result.current.settings === 'object').toBe(true);
expect(typeof result.current.updateSettings === 'function').toBe(true);
expect(typeof result.current.loading === 'boolean').toBe(true);
expect(typeof result.current.colorScheme === 'string').toBe(true);
expect(typeof result.current.updateColorScheme === 'function').toBe(true);
expect(typeof result.current.switchWorkspace === 'function').toBe(true);
expect(typeof result.current.deleteCurrentWorkspace === 'function').toBe(
true
);
});
});
describe('edge cases', () => {
it('handles undefined workspace data gracefully', () => {
// Simulate undefined data that might occur during loading
mockWorkspaceData.currentWorkspace = null;
mockWorkspaceData.workspaces = [];
mockWorkspaceData.settings = DEFAULT_WORKSPACE_SETTINGS;
const { result } = renderHook(() => useWorkspace());
expect(result.current.currentWorkspace).toBeNull();
expect(result.current.workspaces).toEqual([]);
expect(result.current.settings).toEqual(DEFAULT_WORKSPACE_SETTINGS);
expect(typeof result.current.updateSettings).toBe('function');
});
it('handles empty workspaces array', () => {
mockWorkspaceData.workspaces = [];
const { result } = renderHook(() => useWorkspace());
expect(result.current.workspaces).toEqual([]);
});
it('handles single workspace', () => {
const singleWorkspace = [mockWorkspace];
mockWorkspaceData.workspaces = singleWorkspace;
mockWorkspaceData.currentWorkspace = mockWorkspace;
mockWorkspaceData.settings = mockWorkspace;
const { result } = renderHook(() => useWorkspace());
expect(result.current.workspaces).toEqual(singleWorkspace);
expect(result.current.currentWorkspace).toEqual(mockWorkspace);
});
it('handles workspace with minimal data', () => {
const minimalWorkspace: Workspace = {
name: 'minimal',
createdAt: Date.now(),
...DEFAULT_WORKSPACE_SETTINGS,
};
mockWorkspaceData.currentWorkspace = minimalWorkspace;
mockWorkspaceData.settings = minimalWorkspace;
const { result } = renderHook(() => useWorkspace());
expect(result.current.currentWorkspace).toEqual(minimalWorkspace);
expect(result.current.settings).toEqual(minimalWorkspace);
});
});
describe('integration scenarios', () => {
it('provides complete workspace management interface', () => {
mockWorkspaceData.currentWorkspace = mockWorkspace;
mockWorkspaceData.workspaces = mockWorkspaces;
mockWorkspaceData.settings = mockWorkspace;
mockTheme.colorScheme = 'light';
const { result } = renderHook(() => useWorkspace());
// Should have all data
expect(result.current.currentWorkspace).toEqual(mockWorkspace);
expect(result.current.workspaces).toEqual(mockWorkspaces);
expect(result.current.settings).toEqual(mockWorkspace);
expect(result.current.colorScheme).toBe('light');
// Should have all operations
expect(typeof result.current.updateSettings).toBe('function');
expect(typeof result.current.switchWorkspace).toBe('function');
expect(typeof result.current.deleteCurrentWorkspace).toBe('function');
expect(typeof result.current.updateColorScheme).toBe('function');
});
it('supports workspace switching workflow', () => {
const { result } = renderHook(() => useWorkspace());
// Initially no workspace
expect(result.current.currentWorkspace).toBeNull();
// Should provide switch function
expect(typeof result.current.switchWorkspace).toBe('function');
expect(result.current.switchWorkspace).toBe(
mockWorkspaceOperations.switchWorkspace
);
});
it('supports settings management workflow', () => {
mockWorkspaceData.currentWorkspace = mockWorkspace;
mockWorkspaceData.settings = mockWorkspace;
const { result } = renderHook(() => useWorkspace());
// Should have current settings
expect(result.current.settings).toEqual(mockWorkspace);
// Should provide update function
expect(typeof result.current.updateSettings).toBe('function');
expect(result.current.updateSettings).toBe(
mockWorkspaceOperations.updateSettings
);
});
it('supports theme management workflow', () => {
mockTheme.colorScheme = 'dark';
const { result } = renderHook(() => useWorkspace());
// Should have current color scheme
expect(result.current.colorScheme).toBe('dark');
// Should provide update function
expect(typeof result.current.updateColorScheme).toBe('function');
expect(result.current.updateColorScheme).toBe(
mockTheme.updateColorScheme
);
});
});
describe('mock integration validation', () => {
it('correctly integrates with WorkspaceDataContext mock', () => {
mockWorkspaceData.currentWorkspace = mockWorkspace;
mockWorkspaceData.workspaces = mockWorkspaces;
mockWorkspaceData.settings = mockWorkspace;
mockWorkspaceData.loading = true;
const { result } = renderHook(() => useWorkspace());
expect(result.current.currentWorkspace).toBe(
mockWorkspaceData.currentWorkspace
);
expect(result.current.workspaces).toBe(mockWorkspaceData.workspaces);
expect(result.current.settings).toBe(mockWorkspaceData.settings);
expect(result.current.loading).toBe(mockWorkspaceData.loading);
});
it('correctly integrates with ThemeContext mock', () => {
mockTheme.colorScheme = 'dark';
const { result } = renderHook(() => useWorkspace());
expect(result.current.colorScheme).toBe(mockTheme.colorScheme);
expect(result.current.updateColorScheme).toBe(
mockTheme.updateColorScheme
);
});
it('correctly integrates with useWorkspaceOperations mock', () => {
const { result } = renderHook(() => useWorkspace());
expect(result.current.switchWorkspace).toBe(
mockWorkspaceOperations.switchWorkspace
);
expect(result.current.deleteCurrentWorkspace).toBe(
mockWorkspaceOperations.deleteCurrentWorkspace
);
expect(result.current.updateSettings).toBe(
mockWorkspaceOperations.updateSettings
);
});
});
});