fix unsaved changes showing when no changes in extensions (#6743)

This commit is contained in:
Zane
2026-01-27 11:16:18 -08:00
committed by GitHub
parent b5ec0aea95
commit 5827ea4743
2 changed files with 172 additions and 15 deletions
@@ -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
);
};