diff --git a/app/src/components/modals/workspace/CreateWorkspaceModal.test.tsx b/app/src/components/modals/workspace/CreateWorkspaceModal.test.tsx index 9243174..d49cad3 100644 --- a/app/src/components/modals/workspace/CreateWorkspaceModal.test.tsx +++ b/app/src/components/modals/workspace/CreateWorkspaceModal.test.tsx @@ -86,13 +86,11 @@ describe('CreateWorkspaceModal', () => { mockOnWorkspaceCreated.mockResolvedValue(undefined); mockSetCreateWorkspaceModalVisible.mockClear(); mockNotificationsShow.mockClear(); - - // Set up default modal context mockUseModalContext.mockReturnValue(mockModalContext); }); - describe('Modal Visibility', () => { - it('renders modal when visible', () => { + describe('Modal Visibility and Basic Interaction', () => { + it('renders modal with form elements when visible', () => { render( ); @@ -100,20 +98,18 @@ describe('CreateWorkspaceModal', () => { expect(screen.getByText('Create New Workspace')).toBeInTheDocument(); expect(screen.getByTestId('workspace-name-input')).toBeInTheDocument(); expect( - screen.getByTestId('cancel-create-workspace-button') + screen.getByRole('button', { name: /cancel/i }) ).toBeInTheDocument(); expect( - screen.getByTestId('confirm-create-workspace-button') + screen.getByRole('button', { name: /create/i }) ).toBeInTheDocument(); }); - it('does not render modal when not visible', () => { - const hiddenModalContext = { + it('does not render when modal is closed', () => { + mockUseModalContext.mockReturnValueOnce({ ...mockModalContext, createWorkspaceModalVisible: false, - }; - - mockUseModalContext.mockReturnValueOnce(hiddenModalContext); + }); render( @@ -124,19 +120,15 @@ describe('CreateWorkspaceModal', () => { ).not.toBeInTheDocument(); }); - it('calls setCreateWorkspaceModalVisible when modal is closed via cancel button', () => { + it('closes modal when cancel button is clicked', () => { render( ); - const cancelButton = screen.getByTestId('cancel-create-workspace-button'); - fireEvent.click(cancelButton); - + fireEvent.click(screen.getByTestId('cancel-create-workspace-button')); expect(mockSetCreateWorkspaceModalVisible).toHaveBeenCalledWith(false); }); - }); - describe('Form Interaction', () => { it('updates workspace name input when typed', () => { render( @@ -147,109 +139,69 @@ describe('CreateWorkspaceModal', () => { expect((nameInput as HTMLInputElement).value).toBe('my-workspace'); }); - - it('handles form submission with valid workspace name', async () => { - render( - - ); - - const nameInput = screen.getByTestId('workspace-name-input'); - const createButton = screen.getByTestId( - 'confirm-create-workspace-button' - ); - - fireEvent.change(nameInput, { target: { value: 'new-workspace' } }); - fireEvent.click(createButton); - - await waitFor(() => { - expect(mockCreateWorkspace).toHaveBeenCalledWith('new-workspace'); - }); - }); - - it('prevents submission with empty workspace name', async () => { - render( - - ); - - const createButton = screen.getByTestId( - 'confirm-create-workspace-button' - ); - fireEvent.click(createButton); - - await waitFor(() => { - expect(mockNotificationsShow).toHaveBeenCalledWith({ - title: 'Error', - message: 'Workspace name is required', - color: 'red', - }); - }); - - expect(mockCreateWorkspace).not.toHaveBeenCalled(); - }); - - it('prevents submission with whitespace-only workspace name', async () => { - render( - - ); - - const nameInput = screen.getByTestId('workspace-name-input'); - const createButton = screen.getByTestId( - 'confirm-create-workspace-button' - ); - - fireEvent.change(nameInput, { target: { value: ' ' } }); - fireEvent.click(createButton); - - await waitFor(() => { - expect(mockNotificationsShow).toHaveBeenCalledWith({ - title: 'Error', - message: 'Workspace name is required', - color: 'red', - }); - }); - - expect(mockCreateWorkspace).not.toHaveBeenCalled(); - }); - - it('closes modal and clears form after successful creation', async () => { - render( - - ); - - const nameInput = screen.getByTestId('workspace-name-input'); - const createButton = screen.getByTestId( - 'confirm-create-workspace-button' - ); - - fireEvent.change(nameInput, { target: { value: 'success-workspace' } }); - fireEvent.click(createButton); - - await waitFor(() => { - expect(mockCreateWorkspace).toHaveBeenCalledWith('success-workspace'); - }); - - await waitFor(() => { - expect(mockSetCreateWorkspaceModalVisible).toHaveBeenCalledWith(false); - }); - - await waitFor(() => { - expect((nameInput as HTMLInputElement).value).toBe(''); - }); - }); }); - describe('Workspace Name Validation', () => { - it('handles various workspace name formats', async () => { - const workspaceNames = [ + describe('Form Validation', () => { + it('prevents submission with empty or whitespace-only names', async () => { + const testCases = ['', ' ', '\t\n ']; + + for (const testValue of testCases) { + const { unmount } = render( + + ); + + const nameInput = screen.getByTestId('workspace-name-input'); + const createButton = screen.getByTestId( + 'confirm-create-workspace-button' + ); + + fireEvent.change(nameInput, { target: { value: testValue } }); + fireEvent.click(createButton); + + await waitFor(() => { + expect(mockNotificationsShow).toHaveBeenCalledWith({ + title: 'Error', + message: 'Workspace name is required', + color: 'red', + }); + }); + + expect(mockCreateWorkspace).not.toHaveBeenCalled(); + + unmount(); + vi.clearAllMocks(); + } + }); + + it('trims whitespace from workspace names before submission', async () => { + render( + + ); + + const nameInput = screen.getByTestId('workspace-name-input'); + const createButton = screen.getByTestId( + 'confirm-create-workspace-button' + ); + + fireEvent.change(nameInput, { target: { value: ' valid-workspace ' } }); + fireEvent.click(createButton); + + await waitFor(() => { + expect(mockCreateWorkspace).toHaveBeenCalledWith('valid-workspace'); + }); + }); + + it('accepts various valid workspace name formats', async () => { + const validNames = [ 'simple', 'workspace-with-dashes', 'workspace_with_underscores', 'workspace with spaces', 'workspace123', - 'Very Long Workspace Name Here', + 'ワークスペース', // Unicode ]; - for (const name of workspaceNames) { + for (const name of validNames) { const { unmount } = render( ); @@ -271,71 +223,11 @@ describe('CreateWorkspaceModal', () => { mockCreateWorkspace.mockResolvedValue(mockWorkspace); } }); - - it('handles unicode characters in workspace names', async () => { - render( - - ); - - const nameInput = screen.getByTestId('workspace-name-input'); - const createButton = screen.getByTestId( - 'confirm-create-workspace-button' - ); - - const unicodeName = 'ワークスペース'; - fireEvent.change(nameInput, { target: { value: unicodeName } }); - fireEvent.click(createButton); - - await waitFor(() => { - expect(mockCreateWorkspace).toHaveBeenCalledWith(unicodeName); - }); - }); - - it('trims whitespace from workspace names', async () => { - render( - - ); - - const nameInput = screen.getByTestId('workspace-name-input'); - const createButton = screen.getByTestId( - 'confirm-create-workspace-button' - ); - - fireEvent.change(nameInput, { - target: { value: ' trimmed-workspace ' }, - }); - fireEvent.click(createButton); - - await waitFor(() => { - expect(mockCreateWorkspace).toHaveBeenCalledWith('trimmed-workspace'); - }); - }); }); - describe('Loading State', () => { - it('shows loading state on create button during creation', async () => { - // Make the API call hang to test loading state - mockCreateWorkspace.mockImplementation(() => new Promise(() => {})); - - render( - - ); - - const nameInput = screen.getByTestId('workspace-name-input'); - const createButton = screen.getByTestId( - 'confirm-create-workspace-button' - ); - - fireEvent.change(nameInput, { target: { value: 'loading-test' } }); - fireEvent.click(createButton); - - await waitFor(() => { - expect(createButton).toHaveAttribute('data-loading', 'true'); - }); - }); - - it('disables form elements during creation', async () => { - mockCreateWorkspace.mockImplementation(() => new Promise(() => {})); + describe('Loading States and UI Behavior', () => { + it('disables form elements and shows loading during workspace creation', async () => { + mockCreateWorkspace.mockImplementation(() => new Promise(() => {})); // Never resolves render( @@ -347,17 +239,18 @@ describe('CreateWorkspaceModal', () => { ); const cancelButton = screen.getByTestId('cancel-create-workspace-button'); - fireEvent.change(nameInput, { target: { value: 'disabled-test' } }); + fireEvent.change(nameInput, { target: { value: 'loading-test' } }); fireEvent.click(createButton); await waitFor(() => { expect(nameInput).toBeDisabled(); expect(createButton).toBeDisabled(); expect(cancelButton).toBeDisabled(); + expect(createButton).toHaveAttribute('data-loading', 'true'); }); }); - it('handles normal state when not loading', () => { + it('maintains normal state when not loading', () => { render( ); @@ -375,8 +268,8 @@ describe('CreateWorkspaceModal', () => { }); }); - describe('Success Handling', () => { - it('shows success notification after workspace creation', async () => { + describe('Successful Workspace Creation', () => { + it('completes full successful creation flow', async () => { render( ); @@ -386,9 +279,15 @@ describe('CreateWorkspaceModal', () => { 'confirm-create-workspace-button' ); - fireEvent.change(nameInput, { target: { value: 'success-workspace' } }); + fireEvent.change(nameInput, { target: { value: 'new-workspace' } }); fireEvent.click(createButton); + // API called with correct name + await waitFor(() => { + expect(mockCreateWorkspace).toHaveBeenCalledWith('new-workspace'); + }); + + // Success notification shown await waitFor(() => { expect(mockNotificationsShow).toHaveBeenCalledWith({ title: 'Success', @@ -396,24 +295,17 @@ describe('CreateWorkspaceModal', () => { color: 'green', }); }); - }); - - it('calls onWorkspaceCreated callback when provided', async () => { - render( - - ); - - const nameInput = screen.getByTestId('workspace-name-input'); - const createButton = screen.getByTestId( - 'confirm-create-workspace-button' - ); - - fireEvent.change(nameInput, { target: { value: 'callback-test' } }); - fireEvent.click(createButton); + // Callback invoked await waitFor(() => { expect(mockOnWorkspaceCreated).toHaveBeenCalledWith(mockWorkspace); }); + + // Modal closed and form cleared + await waitFor(() => { + expect(mockSetCreateWorkspaceModalVisible).toHaveBeenCalledWith(false); + expect((nameInput as HTMLInputElement).value).toBe(''); + }); }); it('works without onWorkspaceCreated callback', async () => { @@ -442,7 +334,7 @@ describe('CreateWorkspaceModal', () => { }); describe('Error Handling', () => { - it('handles creation errors gracefully', async () => { + it('handles API errors gracefully', async () => { mockCreateWorkspace.mockRejectedValue(new Error('Creation failed')); render( @@ -465,65 +357,16 @@ describe('CreateWorkspaceModal', () => { }); }); - // Modal should remain open when creation fails + // Modal remains open and form retains values expect(mockSetCreateWorkspaceModalVisible).not.toHaveBeenCalledWith( false ); expect(screen.getByText('Create New Workspace')).toBeInTheDocument(); - }); - - it('handles network errors', async () => { - mockCreateWorkspace.mockRejectedValue(new Error('Network error')); - - render( - - ); - - const nameInput = screen.getByTestId('workspace-name-input'); - const createButton = screen.getByTestId( - 'confirm-create-workspace-button' - ); - - fireEvent.change(nameInput, { target: { value: 'network-error-test' } }); - fireEvent.click(createButton); - - await waitFor(() => { - expect(mockNotificationsShow).toHaveBeenCalledWith({ - title: 'Error', - message: 'Failed to create workspace', - color: 'red', - }); - }); - - // Should not crash the component - expect(screen.getByText('Create New Workspace')).toBeInTheDocument(); - }); - - it('retains form values when creation fails', async () => { - mockCreateWorkspace.mockRejectedValue(new Error('Creation failed')); - - render( - - ); - - const nameInput = screen.getByTestId('workspace-name-input'); - const createButton = screen.getByTestId( - 'confirm-create-workspace-button' - ); - - fireEvent.change(nameInput, { target: { value: 'persist-error' } }); - fireEvent.click(createButton); - - await waitFor(() => { - expect(mockCreateWorkspace).toHaveBeenCalledWith('persist-error'); - }); - - // Form should retain values when creation fails - expect((nameInput as HTMLInputElement).value).toBe('persist-error'); + expect((nameInput as HTMLInputElement).value).toBe('error-workspace'); }); it('resets loading state after error', async () => { - mockCreateWorkspace.mockRejectedValue(new Error('Creation failed')); + mockCreateWorkspace.mockRejectedValue(new Error('Network error')); render( @@ -548,181 +391,19 @@ describe('CreateWorkspaceModal', () => { }); }); - describe('Accessibility', () => { - it('has proper form labels and structure', () => { - render( - - ); - - const nameInput = screen.getByTestId('workspace-name-input'); - expect(nameInput).toBeInTheDocument(); - expect(nameInput.tagName).toBe('INPUT'); - expect(nameInput).toHaveAttribute('type', 'text'); - expect(nameInput).toHaveAccessibleName(); - }); - - it('has proper button roles', () => { - render( - - ); - - const buttons = screen.getAllByRole('button'); - expect(buttons.length).toBeGreaterThanOrEqual(2); - - const cancelButton = screen.getByRole('button', { name: /cancel/i }); - const createButton = screen.getByRole('button', { name: /create/i }); - - expect(cancelButton).toBeInTheDocument(); - expect(createButton).toBeInTheDocument(); - }); - - it('supports keyboard navigation', () => { + describe('Keyboard Interactions', () => { + it('supports keyboard input in the name field', () => { render( ); const nameInput = screen.getByTestId('workspace-name-input'); - // Check that the input is focusable expect(nameInput).not.toHaveAttribute('disabled'); expect(nameInput).not.toHaveAttribute('readonly'); - // Test keyboard input fireEvent.change(nameInput, { target: { value: 'keyboard-test' } }); expect((nameInput as HTMLInputElement).value).toBe('keyboard-test'); }); - - it('has proper modal structure', () => { - render( - - ); - - expect(screen.getByText('Create New Workspace')).toBeInTheDocument(); - expect(screen.getByTestId('workspace-name-input')).toBeInTheDocument(); - }); - }); - - describe('Component Props', () => { - it('accepts and uses onWorkspaceCreated prop correctly', async () => { - const customCallback = vi.fn().mockResolvedValue(undefined); - - render(); - - const nameInput = screen.getByTestId('workspace-name-input'); - const createButton = screen.getByTestId( - 'confirm-create-workspace-button' - ); - - fireEvent.change(nameInput, { target: { value: 'custom-callback' } }); - fireEvent.click(createButton); - - await waitFor(() => { - expect(customCallback).toHaveBeenCalledWith(mockWorkspace); - }); - }); - - it('handles function props correctly', () => { - const testCallback = vi.fn(); - - expect(() => { - render(); - }).not.toThrow(); - - expect(screen.getByText('Create New Workspace')).toBeInTheDocument(); - }); - }); - - describe('User Interaction Flow', () => { - it('completes full workspace creation flow successfully', async () => { - render( - - ); - - // 1. Modal opens and shows form - expect(screen.getByText('Create New Workspace')).toBeInTheDocument(); - - // 2. User types workspace name - const nameInput = screen.getByTestId('workspace-name-input'); - fireEvent.change(nameInput, { target: { value: 'complete-flow-test' } }); - - // 3. User clicks create - const createButton = screen.getByTestId( - 'confirm-create-workspace-button' - ); - fireEvent.click(createButton); - - // 4. API is called - await waitFor(() => { - expect(mockCreateWorkspace).toHaveBeenCalledWith('complete-flow-test'); - }); - - // 5. Success notification is shown - await waitFor(() => { - expect(mockNotificationsShow).toHaveBeenCalledWith({ - title: 'Success', - message: 'Workspace created successfully', - color: 'green', - }); - }); - - // 6. Callback is called - await waitFor(() => { - expect(mockOnWorkspaceCreated).toHaveBeenCalledWith(mockWorkspace); - }); - - // 7. Modal closes and form clears - await waitFor(() => { - expect(mockSetCreateWorkspaceModalVisible).toHaveBeenCalledWith(false); - }); - - await waitFor(() => { - expect((nameInput as HTMLInputElement).value).toBe(''); - }); - }); - - it('allows user to cancel workspace creation', () => { - render( - - ); - - // User types name but then cancels - const nameInput = screen.getByTestId('workspace-name-input'); - fireEvent.change(nameInput, { target: { value: 'cancelled-workspace' } }); - - const cancelButton = screen.getByTestId('cancel-create-workspace-button'); - fireEvent.click(cancelButton); - - // Should close modal without calling API - expect(mockCreateWorkspace).not.toHaveBeenCalled(); - expect(mockSetCreateWorkspaceModalVisible).toHaveBeenCalledWith(false); - }); - - it('handles validation error flow', async () => { - render( - - ); - - // User tries to submit without entering name - const createButton = screen.getByTestId( - 'confirm-create-workspace-button' - ); - fireEvent.click(createButton); - - // Should show validation error - await waitFor(() => { - expect(mockNotificationsShow).toHaveBeenCalledWith({ - title: 'Error', - message: 'Workspace name is required', - color: 'red', - }); - }); - - // Should not call API or close modal - expect(mockCreateWorkspace).not.toHaveBeenCalled(); - expect(mockSetCreateWorkspaceModalVisible).not.toHaveBeenCalledWith( - false - ); - expect(screen.getByText('Create New Workspace')).toBeInTheDocument(); - }); }); }); diff --git a/app/src/components/modals/workspace/DeleteWorkspaceModal.test.tsx b/app/src/components/modals/workspace/DeleteWorkspaceModal.test.tsx index 76897f5..bf7f093 100644 --- a/app/src/components/modals/workspace/DeleteWorkspaceModal.test.tsx +++ b/app/src/components/modals/workspace/DeleteWorkspaceModal.test.tsx @@ -33,11 +33,10 @@ describe('DeleteWorkspaceModal', () => { beforeEach(() => { vi.clearAllMocks(); mockOnConfirm.mockResolvedValue(undefined); - mockOnClose.mockClear(); }); - describe('Modal Visibility', () => { - it('renders modal when opened with workspace name', () => { + describe('Modal Visibility and Content', () => { + it('renders modal with correct content when opened', () => { render( { ) ).toBeInTheDocument(); expect( - screen.getByTestId('cancel-delete-workspace-button') + screen.getByRole('button', { name: /cancel/i }) ).toBeInTheDocument(); expect( - screen.getByTestId('confirm-delete-workspace-button') + screen.getByRole('button', { name: /delete/i }) ).toBeInTheDocument(); }); - it('does not render modal when closed', () => { + it('does not render when closed', () => { render( { expect(screen.queryByText('Delete Workspace')).not.toBeInTheDocument(); }); - it('renders modal with undefined workspace name', () => { - render( + it('toggles visibility correctly when opened prop changes', () => { + const { rerender } = render( - ); - - expect(screen.getByText('Delete Workspace')).toBeInTheDocument(); - expect( - screen.getByText( - 'Are you sure you want to delete workspace ""? This action cannot be undone and all files in this workspace will be permanently deleted.' - ) - ).toBeInTheDocument(); - }); - - it('calls onClose when modal is closed via cancel button', () => { - render( - ); - const cancelButton = screen.getByTestId('cancel-delete-workspace-button'); - fireEvent.click(cancelButton); + expect(screen.queryByText('Delete Workspace')).not.toBeInTheDocument(); - expect(mockOnClose).toHaveBeenCalled(); + rerender( + + + + ); + + expect(screen.getByText('Delete Workspace')).toBeInTheDocument(); }); }); - describe('Workspace Information Display', () => { - it('displays correct workspace name in confirmation message', () => { - render( - - ); - - expect( - screen.getByText( - 'Are you sure you want to delete workspace "my-workspace"? This action cannot be undone and all files in this workspace will be permanently deleted.' - ) - ).toBeInTheDocument(); - }); - - it('handles various workspace name formats in confirmation message', () => { - const workspaceNames = [ + describe('Workspace Name Display', () => { + it('displays various workspace name formats correctly', () => { + const testCases = [ 'simple', 'workspace-with-dashes', 'workspace_with_underscores', 'workspace with spaces', - 'very-long-workspace-name-here', + 'workspace"with@quotes', + 'ワークスペース', // Unicode + '', // Empty string + undefined, // Undefined ]; - workspaceNames.forEach((workspaceName) => { + testCases.forEach((workspaceName) => { const { unmount } = render( { /> ); + const displayName = workspaceName || ''; expect( screen.getByText( - `Are you sure you want to delete workspace "${workspaceName}"? This action cannot be undone and all files in this workspace will be permanently deleted.` + `Are you sure you want to delete workspace "${displayName}"?`, + { exact: false } ) ).toBeInTheDocument(); unmount(); }); }); - - it('handles workspace with special characters in name', () => { - const specialWorkspace = 'workspace"with@quotes'; - - render( - - ); - - expect( - screen.getByText( - 'Are you sure you want to delete workspace "workspace"with@quotes"? This action cannot be undone and all files in this workspace will be permanently deleted.' - ) - ).toBeInTheDocument(); - }); - - it('handles unicode characters in workspace name', () => { - const unicodeWorkspace = 'ワークスペース'; - - render( - - ); - - expect( - screen.getByText( - 'Are you sure you want to delete workspace "ワークスペース"? This action cannot be undone and all files in this workspace will be permanently deleted.' - ) - ).toBeInTheDocument(); - }); - - it('handles empty workspace name', () => { - render( - - ); - - expect( - screen.getByText( - 'Are you sure you want to delete workspace ""? This action cannot be undone and all files in this workspace will be permanently deleted.' - ) - ).toBeInTheDocument(); - }); }); - describe('Modal Actions', () => { - it('has cancel and delete buttons with correct text', () => { - render( - - ); - - const cancelButton = screen.getByTestId('cancel-delete-workspace-button'); - const deleteButton = screen.getByTestId( - 'confirm-delete-workspace-button' - ); - - expect(cancelButton).toBeInTheDocument(); - expect(deleteButton).toBeInTheDocument(); - - expect(cancelButton).toHaveTextContent('Cancel'); - expect(deleteButton).toHaveTextContent('Delete'); - - expect(cancelButton).toHaveRole('button'); - expect(deleteButton).toHaveRole('button'); - }); - + describe('User Actions', () => { it('calls onConfirm when delete button is clicked', async () => { render( { fireEvent.click(cancelButton); expect(mockOnClose).toHaveBeenCalled(); + expect(mockOnConfirm).not.toHaveBeenCalled(); + }); + + it('handles multiple rapid clicks gracefully', async () => { + render( + + ); + + const deleteButton = screen.getByTestId( + 'confirm-delete-workspace-button' + ); + + // Rapidly click multiple times + fireEvent.click(deleteButton); + fireEvent.click(deleteButton); + fireEvent.click(deleteButton); + + // Component should remain stable + expect(screen.getByText('Delete Workspace')).toBeInTheDocument(); + await waitFor(() => { + expect(mockOnConfirm).toHaveBeenCalled(); + }); }); }); describe('Error Handling', () => { - it('handles deletion errors gracefully', async () => { + it('handles deletion errors gracefully without crashing', async () => { mockOnConfirm.mockRejectedValue(new Error('Deletion failed')); render( @@ -297,383 +223,8 @@ describe('DeleteWorkspaceModal', () => { expect(mockOnConfirm).toHaveBeenCalled(); }); - // Modal should handle the error gracefully (not crash) + // Component should remain stable after error expect(screen.getByText('Delete Workspace')).toBeInTheDocument(); }); - - it('handles network errors', async () => { - mockOnConfirm.mockRejectedValue(new Error('Network error')); - - render( - - ); - - const deleteButton = screen.getByTestId( - 'confirm-delete-workspace-button' - ); - fireEvent.click(deleteButton); - - await waitFor(() => { - expect(mockOnConfirm).toHaveBeenCalled(); - }); - - // Should not crash the component - expect(screen.getByText('Delete Workspace')).toBeInTheDocument(); - }); - }); - - describe('Accessibility', () => { - it('has proper modal structure', () => { - render( - - ); - - // Modal should have proper title - expect(screen.getByText('Delete Workspace')).toBeInTheDocument(); - - // Should have confirmation text - expect( - screen.getByText(/Are you sure you want to delete workspace/) - ).toBeInTheDocument(); - }); - - it('has proper button roles', () => { - render( - - ); - - const buttons = screen.getAllByRole('button'); - expect(buttons.length).toBeGreaterThanOrEqual(2); - - const cancelButton = screen.getByRole('button', { name: /cancel/i }); - const deleteButton = screen.getByRole('button', { - name: /delete/i, - }); - - expect(cancelButton).toBeInTheDocument(); - expect(deleteButton).toBeInTheDocument(); - }); - - it('supports keyboard navigation', () => { - render( - - ); - - const cancelButton = screen.getByTestId('cancel-delete-workspace-button'); - const deleteButton = screen.getByTestId( - 'confirm-delete-workspace-button' - ); - - // Buttons should be focusable - expect(cancelButton).not.toHaveAttribute('disabled'); - expect(deleteButton).not.toHaveAttribute('disabled'); - - // Should handle keyboard events - fireEvent.keyDown(deleteButton, { key: 'Enter', code: 'Enter' }); - fireEvent.keyDown(cancelButton, { key: 'Escape', code: 'Escape' }); - }); - - it('has proper confirmation message structure', () => { - render( - - ); - - // Check that the workspace name is properly quoted in the message - expect( - screen.getByText( - /Are you sure you want to delete workspace "important-workspace"?/ - ) - ).toBeInTheDocument(); - }); - }); - - describe('Component Props', () => { - it('accepts and uses onConfirm prop correctly', async () => { - const customMockConfirm = vi.fn().mockResolvedValue(undefined); - - render( - - ); - - const deleteButton = screen.getByTestId( - 'confirm-delete-workspace-button' - ); - fireEvent.click(deleteButton); - - await waitFor(() => { - expect(customMockConfirm).toHaveBeenCalledTimes(1); - }); - }); - - it('accepts and uses onClose prop correctly', () => { - const customMockClose = vi.fn(); - - render( - - ); - - const cancelButton = screen.getByTestId('cancel-delete-workspace-button'); - fireEvent.click(cancelButton); - - expect(customMockClose).toHaveBeenCalled(); - }); - - it('handles function props correctly', () => { - const testOnConfirm = vi.fn(); - const testOnClose = vi.fn(); - - expect(() => { - render( - - ); - }).not.toThrow(); - - expect(screen.getByText('Delete Workspace')).toBeInTheDocument(); - }); - - it('handles different workspace name types correctly', () => { - const workspaceNames = [ - 'normal-workspace', - 'workspace with spaces', - 'workspace_with_underscores', - 'ワークスペース', - '', - undefined, - ]; - - workspaceNames.forEach((workspaceName) => { - const { unmount } = render( - - ); - - expect(screen.getByText('Delete Workspace')).toBeInTheDocument(); - const displayName = workspaceName || ''; - expect( - screen.getByText( - `Are you sure you want to delete workspace "${displayName}"?`, - { exact: false } - ) - ).toBeInTheDocument(); - unmount(); - }); - }); - - it('handles opened prop correctly', () => { - const { rerender } = render( - - ); - - // Should not be visible when opened is false - expect(screen.queryByText('Delete Workspace')).not.toBeInTheDocument(); - - rerender( - - - - ); - - // Should be visible when opened is true - expect(screen.getByText('Delete Workspace')).toBeInTheDocument(); - }); - }); - - describe('User Interaction Flow', () => { - it('completes full deletion confirmation flow successfully', async () => { - render( - - ); - - // 1. Modal opens and shows workspace information - expect(screen.getByText('Delete Workspace')).toBeInTheDocument(); - expect( - screen.getByText( - 'Are you sure you want to delete workspace "flow-test-workspace"? This action cannot be undone and all files in this workspace will be permanently deleted.' - ) - ).toBeInTheDocument(); - - // 2. User clicks delete - const deleteButton = screen.getByTestId( - 'confirm-delete-workspace-button' - ); - fireEvent.click(deleteButton); - - // 3. Confirmation function is called - await waitFor(() => { - expect(mockOnConfirm).toHaveBeenCalledTimes(1); - }); - }); - - it('allows user to cancel deletion', () => { - render( - - ); - - // User clicks cancel instead of delete - const cancelButton = screen.getByTestId('cancel-delete-workspace-button'); - fireEvent.click(cancelButton); - - // Should close modal without calling confirm function - expect(mockOnConfirm).not.toHaveBeenCalled(); - expect(mockOnClose).toHaveBeenCalled(); - }); - - it('handles multiple rapid clicks gracefully', () => { - render( - - ); - - const deleteButton = screen.getByTestId( - 'confirm-delete-workspace-button' - ); - - // Rapidly click multiple times - should not crash - fireEvent.click(deleteButton); - fireEvent.click(deleteButton); - fireEvent.click(deleteButton); - - // Verify component is still functional - expect(screen.getByText('Delete Workspace')).toBeInTheDocument(); - expect(mockOnConfirm).toHaveBeenCalled(); - }); - }); - - describe('Security Considerations', () => { - it('clearly shows destructive action warning', () => { - render( - - ); - - expect( - screen.getByText( - /This action cannot be undone and all files in this workspace will be permanently deleted/ - ) - ).toBeInTheDocument(); - }); - - it('requires explicit confirmation', () => { - render( - - ); - - // Should show clear delete button - const deleteButton = screen.getByTestId( - 'confirm-delete-workspace-button' - ); - expect(deleteButton).toHaveTextContent('Delete'); - }); - - it('displays workspace name for verification', () => { - render( - - ); - - // User should be able to verify they're deleting the right workspace - expect( - screen.getByText(/delete workspace "verification-workspace"/) - ).toBeInTheDocument(); - }); - - it('warns about file deletion consequences', () => { - render( - - ); - - // Should warn about files being deleted - expect( - screen.getByText( - /all files in this workspace will be permanently deleted/ - ) - ).toBeInTheDocument(); - }); }); });