fix unsaved changes showing when no changes in extensions (#6743)
This commit is contained in:
@@ -5,6 +5,162 @@ import ExtensionModal from './ExtensionModal';
|
||||
import { ExtensionFormData } from '../utils';
|
||||
|
||||
describe('ExtensionModal', () => {
|
||||
it('does not show unsaved changes dialog when closing without modifications', async () => {
|
||||
const user = userEvent.setup();
|
||||
const mockOnSubmit = vi.fn();
|
||||
const mockOnClose = vi.fn();
|
||||
|
||||
const initialData: ExtensionFormData = {
|
||||
name: 'Existing Extension',
|
||||
description: 'An existing extension',
|
||||
type: 'stdio',
|
||||
cmd: 'npx some-mcp-server',
|
||||
endpoint: '',
|
||||
enabled: true,
|
||||
timeout: 300,
|
||||
envVars: [
|
||||
{ key: 'API_KEY', value: '••••••••', isEdited: false },
|
||||
{ key: 'OTHER_VAR', value: '••••••••', isEdited: false },
|
||||
],
|
||||
headers: [],
|
||||
};
|
||||
|
||||
render(
|
||||
<ExtensionModal
|
||||
title="Edit Extension"
|
||||
initialData={initialData}
|
||||
onClose={mockOnClose}
|
||||
onSubmit={mockOnSubmit}
|
||||
submitLabel="Save"
|
||||
modalType="edit"
|
||||
/>
|
||||
);
|
||||
|
||||
const cancelButton = screen.getByRole('button', { name: 'Cancel' });
|
||||
await user.click(cancelButton);
|
||||
|
||||
expect(mockOnClose).toHaveBeenCalled();
|
||||
|
||||
expect(screen.queryByText('Unsaved Changes')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows unsaved changes dialog when name is modified', async () => {
|
||||
const user = userEvent.setup();
|
||||
const mockOnSubmit = vi.fn();
|
||||
const mockOnClose = vi.fn();
|
||||
|
||||
const initialData: ExtensionFormData = {
|
||||
name: 'Original Name',
|
||||
description: 'An existing extension',
|
||||
type: 'stdio',
|
||||
cmd: 'npx some-mcp-server',
|
||||
endpoint: '',
|
||||
enabled: true,
|
||||
timeout: 300,
|
||||
envVars: [],
|
||||
headers: [],
|
||||
};
|
||||
|
||||
render(
|
||||
<ExtensionModal
|
||||
title="Edit Extension"
|
||||
initialData={initialData}
|
||||
onClose={mockOnClose}
|
||||
onSubmit={mockOnSubmit}
|
||||
submitLabel="Save"
|
||||
modalType="edit"
|
||||
/>
|
||||
);
|
||||
|
||||
const nameInput = screen.getByPlaceholderText('Enter extension name...');
|
||||
await user.clear(nameInput);
|
||||
await user.type(nameInput, 'New Name');
|
||||
|
||||
const cancelButton = screen.getByRole('button', { name: 'Cancel' });
|
||||
await user.click(cancelButton);
|
||||
|
||||
expect(screen.getByText('Unsaved Changes')).toBeInTheDocument();
|
||||
expect(mockOnClose).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('shows unsaved changes dialog when description is modified', async () => {
|
||||
const user = userEvent.setup();
|
||||
const mockOnSubmit = vi.fn();
|
||||
const mockOnClose = vi.fn();
|
||||
|
||||
const initialData: ExtensionFormData = {
|
||||
name: 'Test Extension',
|
||||
description: 'Original description',
|
||||
type: 'stdio',
|
||||
cmd: 'npx some-mcp-server',
|
||||
endpoint: '',
|
||||
enabled: true,
|
||||
timeout: 300,
|
||||
envVars: [],
|
||||
headers: [],
|
||||
};
|
||||
|
||||
render(
|
||||
<ExtensionModal
|
||||
title="Edit Extension"
|
||||
initialData={initialData}
|
||||
onClose={mockOnClose}
|
||||
onSubmit={mockOnSubmit}
|
||||
submitLabel="Save"
|
||||
modalType="edit"
|
||||
/>
|
||||
);
|
||||
|
||||
const descriptionInput = screen.getByPlaceholderText('Optional description...');
|
||||
await user.clear(descriptionInput);
|
||||
await user.type(descriptionInput, 'New description');
|
||||
|
||||
const cancelButton = screen.getByRole('button', { name: 'Cancel' });
|
||||
await user.click(cancelButton);
|
||||
|
||||
expect(screen.getByText('Unsaved Changes')).toBeInTheDocument();
|
||||
expect(mockOnClose).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('shows unsaved changes dialog when timeout is modified', async () => {
|
||||
const user = userEvent.setup();
|
||||
const mockOnSubmit = vi.fn();
|
||||
const mockOnClose = vi.fn();
|
||||
|
||||
const initialData: ExtensionFormData = {
|
||||
name: 'Test Extension',
|
||||
description: 'An extension',
|
||||
type: 'stdio',
|
||||
cmd: 'npx some-mcp-server',
|
||||
endpoint: '',
|
||||
enabled: true,
|
||||
timeout: 300,
|
||||
envVars: [],
|
||||
headers: [],
|
||||
};
|
||||
|
||||
render(
|
||||
<ExtensionModal
|
||||
title="Edit Extension"
|
||||
initialData={initialData}
|
||||
onClose={mockOnClose}
|
||||
onSubmit={mockOnSubmit}
|
||||
submitLabel="Save"
|
||||
modalType="edit"
|
||||
/>
|
||||
);
|
||||
|
||||
const timeoutInput = screen.getByDisplayValue('300');
|
||||
await user.clear(timeoutInput);
|
||||
await user.type(timeoutInput, '600');
|
||||
|
||||
const cancelButton = screen.getByRole('button', { name: 'Cancel' });
|
||||
await user.click(cancelButton);
|
||||
|
||||
expect(screen.getByText('Unsaved Changes')).toBeInTheDocument();
|
||||
expect(mockOnClose).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('creates a http_streamable extension', async () => {
|
||||
const user = userEvent.setup();
|
||||
const mockOnSubmit = vi.fn();
|
||||
|
||||
@@ -47,6 +47,12 @@ export default function ExtensionModal({
|
||||
|
||||
// Function to check if form has been modified
|
||||
const hasFormChanges = (): boolean => {
|
||||
// Check basic fields
|
||||
const nameChanged = formData.name !== initialData.name;
|
||||
const descriptionChanged = formData.description !== initialData.description;
|
||||
const typeChanged = formData.type !== initialData.type;
|
||||
const timeoutChanged = formData.timeout !== initialData.timeout;
|
||||
|
||||
// Check if command/endpoint has changed
|
||||
const commandChanged =
|
||||
(formData.type === 'stdio' && formData.cmd !== initialData.cmd) ||
|
||||
@@ -54,35 +60,30 @@ export default function ExtensionModal({
|
||||
(formData.type === 'streamable_http' && formData.endpoint !== initialData.endpoint);
|
||||
|
||||
// Check if headers have changed
|
||||
const headersChanged = formData.headers.some((header) => header.isEdited === true);
|
||||
const headersEdited = formData.headers.some((header) => header.isEdited === true);
|
||||
const headersAdded = formData.headers.length > initialData.headers.length;
|
||||
const headersRemoved = formData.headers.length < initialData.headers.length;
|
||||
|
||||
// Check if any environment variables have been modified
|
||||
const envVarsChanged = formData.envVars.some((envVar) => envVar.isEdited === true);
|
||||
|
||||
// Check if new env vars have been added
|
||||
const envVarsAdded = formData.envVars.length > initialData.envVars.length;
|
||||
|
||||
// Check if env vars have been removed
|
||||
const envVarsRemoved = formData.envVars.length < initialData.envVars.length;
|
||||
|
||||
// Check if any environment variable fields have text entered (even if not marked as edited)
|
||||
const envVarsHaveText = formData.envVars.some(
|
||||
(envVar) =>
|
||||
(envVar.key.trim() !== '' || envVar.value.trim() !== '') &&
|
||||
// Don't count placeholder values for existing secrets
|
||||
envVar.value !== '••••••••'
|
||||
);
|
||||
|
||||
// Check if there are pending environment variables or headers being typed
|
||||
const hasPendingInput = hasPendingEnvVars || hasPendingHeaders;
|
||||
|
||||
return (
|
||||
nameChanged ||
|
||||
descriptionChanged ||
|
||||
typeChanged ||
|
||||
timeoutChanged ||
|
||||
commandChanged ||
|
||||
headersChanged ||
|
||||
headersEdited ||
|
||||
headersAdded ||
|
||||
headersRemoved ||
|
||||
envVarsChanged ||
|
||||
envVarsAdded ||
|
||||
envVarsRemoved ||
|
||||
envVarsHaveText ||
|
||||
hasPendingInput
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user