From 11612caac04202b97bd060e9b8731c3ab76a782c Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Tue, 7 Jan 2025 23:57:56 -0500 Subject: [PATCH] Just make a copy when adding a new profile --- .../components/settings/ApiConfigManager.tsx | 8 +- .../__tests__/ApiConfigManager.test.tsx | 154 ++++++++++++++++++ 2 files changed, 158 insertions(+), 4 deletions(-) create mode 100644 webview-ui/src/components/settings/__tests__/ApiConfigManager.test.tsx diff --git a/webview-ui/src/components/settings/ApiConfigManager.tsx b/webview-ui/src/components/settings/ApiConfigManager.tsx index e69865d..e05af14 100644 --- a/webview-ui/src/components/settings/ApiConfigManager.tsx +++ b/webview-ui/src/components/settings/ApiConfigManager.tsx @@ -36,9 +36,9 @@ const ApiConfigManager = ({ setInputValue(""); }, [currentApiConfigName]); - const handleStartNew = () => { - setEditState('new'); - setInputValue(""); + const handleAdd = () => { + const newConfigName = currentApiConfigName + " (copy)"; + onUpsertConfig(newConfigName); }; const handleStartRename = () => { @@ -162,7 +162,7 @@ const ApiConfigManager = ({ ({ + VSCodeButton: ({ children, onClick, title, disabled }: any) => ( + + ), + VSCodeTextField: ({ value, onInput, placeholder }: any) => ( + onInput(e)} + placeholder={placeholder} + ref={undefined} // Explicitly set ref to undefined to avoid warning + /> + ), +})); + +describe('ApiConfigManager', () => { + const mockOnSelectConfig = jest.fn(); + const mockOnDeleteConfig = jest.fn(); + const mockOnRenameConfig = jest.fn(); + const mockOnUpsertConfig = jest.fn(); + + const defaultProps = { + currentApiConfigName: 'Default Config', + listApiConfigMeta: [ + { name: 'Default Config' }, + { name: 'Another Config' } + ], + onSelectConfig: mockOnSelectConfig, + onDeleteConfig: mockOnDeleteConfig, + onRenameConfig: mockOnRenameConfig, + onUpsertConfig: mockOnUpsertConfig, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('immediately creates a copy when clicking add button', () => { + render(); + + // Find and click the add button + const addButton = screen.getByTitle('Add profile'); + fireEvent.click(addButton); + + // Verify that onUpsertConfig was called with the correct name + expect(mockOnUpsertConfig).toHaveBeenCalledTimes(1); + expect(mockOnUpsertConfig).toHaveBeenCalledWith('Default Config (copy)'); + }); + + it('creates copy with correct name when current config has spaces', () => { + render( + + ); + + const addButton = screen.getByTitle('Add profile'); + fireEvent.click(addButton); + + expect(mockOnUpsertConfig).toHaveBeenCalledWith('My Test Config (copy)'); + }); + + it('handles empty current config name gracefully', () => { + render( + + ); + + const addButton = screen.getByTitle('Add profile'); + fireEvent.click(addButton); + + expect(mockOnUpsertConfig).toHaveBeenCalledWith(' (copy)'); + }); + + it('allows renaming the current config', () => { + render(); + + // Start rename + const renameButton = screen.getByTitle('Rename profile'); + fireEvent.click(renameButton); + + // Find input and enter new name + const input = screen.getByDisplayValue('Default Config'); + fireEvent.input(input, { target: { value: 'New Name' } }); + + // Save + const saveButton = screen.getByTitle('Save'); + fireEvent.click(saveButton); + + expect(mockOnRenameConfig).toHaveBeenCalledWith('Default Config', 'New Name'); + }); + + it('allows selecting a different config', () => { + render(); + + const select = screen.getByRole('combobox'); + fireEvent.change(select, { target: { value: 'Another Config' } }); + + expect(mockOnSelectConfig).toHaveBeenCalledWith('Another Config'); + }); + + it('allows deleting the current config when not the only one', () => { + render(); + + const deleteButton = screen.getByTitle('Delete profile'); + expect(deleteButton).not.toBeDisabled(); + + fireEvent.click(deleteButton); + expect(mockOnDeleteConfig).toHaveBeenCalledWith('Default Config'); + }); + + it('disables delete button when only one config exists', () => { + render( + + ); + + const deleteButton = screen.getByTitle('Cannot delete the only profile'); + expect(deleteButton).toHaveAttribute('disabled'); + }); + + it('cancels rename operation when clicking cancel', () => { + render(); + + // Start rename + const renameButton = screen.getByTitle('Rename profile'); + fireEvent.click(renameButton); + + // Find input and enter new name + const input = screen.getByDisplayValue('Default Config'); + fireEvent.input(input, { target: { value: 'New Name' } }); + + // Cancel + const cancelButton = screen.getByTitle('Cancel'); + fireEvent.click(cancelButton); + + // Verify rename was not called + expect(mockOnRenameConfig).not.toHaveBeenCalled(); + + // Verify we're back to normal view + expect(screen.queryByDisplayValue('New Name')).not.toBeInTheDocument(); + }); +}); \ No newline at end of file