Don't force save-prompt on unedited integrations

This commit is contained in:
Jack Kingsman
2026-03-16 21:47:04 -07:00
parent 23f9bd216c
commit 33e1b527bd
2 changed files with 50 additions and 3 deletions

View File

@@ -324,6 +324,22 @@ function getDetailTypeLabel(detailType: string) {
return TYPE_LABELS[detailType] || detailType;
}
function fanoutDraftHasUnsavedChanges(
original: FanoutConfig | null,
current: {
name: string;
config: Record<string, unknown>;
scope: Record<string, unknown>;
}
) {
if (!original) return false;
return (
current.name !== original.name ||
JSON.stringify(current.config) !== JSON.stringify(original.config) ||
JSON.stringify(current.scope) !== JSON.stringify(original.scope)
);
}
function formatBrokerSummary(
config: Record<string, unknown>,
defaults: { host: string; port: number }
@@ -1547,7 +1563,17 @@ export function SettingsFanoutSection({
};
const handleBackToList = () => {
if (!confirm('Leave without saving?')) return;
const shouldConfirm =
draftType !== null ||
fanoutDraftHasUnsavedChanges(
editingId ? (configs.find((c) => c.id === editingId) ?? null) : null,
{
name: editName,
config: editConfig,
scope: editScope,
}
);
if (shouldConfirm && !confirm('Leave without saving?')) return;
setEditingId(null);
setDraftType(null);
};

View File

@@ -342,11 +342,12 @@ describe('SettingsFanoutSection', () => {
fireEvent.click(screen.getByText('← Back to list'));
expect(window.confirm).toHaveBeenCalledWith('Leave without saving?');
await waitFor(() => expect(screen.queryByText('← Back to list')).not.toBeInTheDocument());
expect(mockedApi.createFanoutConfig).not.toHaveBeenCalled();
});
it('back to list asks for confirmation before leaving', async () => {
it('back to list does not ask for confirmation when an existing integration is unchanged', async () => {
mockedApi.getFanoutConfigs.mockResolvedValue([webhookConfig]);
renderSection();
await waitFor(() => expect(screen.getByText('Test Hook')).toBeInTheDocument());
@@ -356,11 +357,28 @@ describe('SettingsFanoutSection', () => {
fireEvent.click(screen.getByText('← Back to list'));
expect(window.confirm).not.toHaveBeenCalled();
await waitFor(() => expect(screen.queryByText('← Back to list')).not.toBeInTheDocument());
});
it('back to list asks for confirmation after editing an existing integration', async () => {
mockedApi.getFanoutConfigs.mockResolvedValue([webhookConfig]);
renderSection();
await waitFor(() => expect(screen.getByText('Test Hook')).toBeInTheDocument());
fireEvent.click(screen.getByRole('button', { name: 'Edit' }));
await waitFor(() => expect(screen.getByText('← Back to list')).toBeInTheDocument());
fireEvent.change(screen.getByLabelText('URL'), {
target: { value: 'https://example.com/new' },
});
fireEvent.click(screen.getByText('← Back to list'));
expect(window.confirm).toHaveBeenCalledWith('Leave without saving?');
await waitFor(() => expect(screen.queryByText('← Back to list')).not.toBeInTheDocument());
});
it('back to list stays on the edit screen when confirmation is cancelled', async () => {
it('back to list stays on the edit screen when confirmation is cancelled after edits', async () => {
vi.mocked(window.confirm).mockReturnValue(false);
mockedApi.getFanoutConfigs.mockResolvedValue([webhookConfig]);
renderSection();
@@ -369,6 +387,9 @@ describe('SettingsFanoutSection', () => {
fireEvent.click(screen.getByRole('button', { name: 'Edit' }));
await waitFor(() => expect(screen.getByText('← Back to list')).toBeInTheDocument());
fireEvent.change(screen.getByLabelText('URL'), {
target: { value: 'https://example.com/new' },
});
fireEvent.click(screen.getByText('← Back to list'));
expect(window.confirm).toHaveBeenCalledWith('Leave without saving?');