refactor(frontend): migrate remaining confirm() to ConfirmDialog (6 files)
Replaced 7 native confirm() calls with styled <ConfirmDialog>: - settings/git (2: delete config + delete repo) - settings/automation (delete policy) - TaskHistory (clear tasks) - ApiKeysTab (revoke key) - ConversationList (delete conversation) - Settings page (delete environment) Updated 2 test files to verify ConfirmDialog interactions.
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
import Input from "$lib/ui/Input.svelte";
|
||||
import Button from "$lib/ui/Button.svelte";
|
||||
import Icon from "$lib/ui/Icon.svelte";
|
||||
import { ConfirmDialog } from "$lib/ui";
|
||||
|
||||
let {
|
||||
conversations = [],
|
||||
@@ -37,6 +38,8 @@
|
||||
|
||||
let searchQuery = $state("");
|
||||
let searchTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
let showDeleteConfirm = $state(false);
|
||||
let pendingDeleteId: string | null = $state(null);
|
||||
|
||||
// Group conversations by date on client
|
||||
let groupedConversations = $derived.by(() => {
|
||||
@@ -76,9 +79,8 @@
|
||||
|
||||
function handleDelete(e: Event, id: string) {
|
||||
e.stopPropagation();
|
||||
if (confirm($t.assistant?.delete_confirm || "Delete this conversation?")) {
|
||||
ondelete?.(id);
|
||||
}
|
||||
pendingDeleteId = id;
|
||||
showDeleteConfirm = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -148,4 +150,16 @@
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ConfirmDialog
|
||||
bind:show={showDeleteConfirm}
|
||||
title={$t.assistant?.delete_title || "Delete conversation?"}
|
||||
message={$t.assistant?.delete_confirm || "This conversation will be permanently deleted."}
|
||||
variant="destructive"
|
||||
confirmLabel={$t.assistant?.delete || "Delete"}
|
||||
cancelLabel={$t.common?.cancel || "Cancel"}
|
||||
onConfirm={() => { if (pendingDeleteId) ondelete?.(pendingDeleteId); }}
|
||||
onCancel={() => { pendingDeleteId = null; }}
|
||||
/>
|
||||
|
||||
<!-- #endregion AgentChat.ConversationList -->
|
||||
|
||||
@@ -163,12 +163,9 @@ describe("ConversationList — search", () => {
|
||||
// #endregion TestAgentChat.ConversationList.Search
|
||||
|
||||
// #region TestAgentChat.ConversationList.Delete [C:2] [TYPE Test]
|
||||
// @UX_STATE loaded — delete button calls ondelete after window.confirm.
|
||||
// @UX_STATE loaded — delete button opens ConfirmDialog, confirm triggers ondelete.
|
||||
describe("ConversationList — delete action", () => {
|
||||
it("calls ondelete after window.confirm returns true", () => {
|
||||
// Set up window.confirm to return true
|
||||
const originalConfirm = window.confirm;
|
||||
window.confirm = vi.fn(() => true);
|
||||
it("calls ondelete after confirming in the dialog", () => {
|
||||
const ondelete = vi.fn();
|
||||
|
||||
render(ConversationList, {
|
||||
@@ -186,14 +183,15 @@ describe("ConversationList — delete action", () => {
|
||||
expect(deleteBtns.length).toBeGreaterThan(0);
|
||||
fireEvent.click(deleteBtns[0]);
|
||||
|
||||
expect(window.confirm).toHaveBeenCalled();
|
||||
expect(ondelete).toHaveBeenCalledWith("c1");
|
||||
// ConfirmDialog appears — click the "Delete" button inside it
|
||||
const confirmBtn = screen.getByText("Delete");
|
||||
expect(confirmBtn).toBeTruthy();
|
||||
fireEvent.click(confirmBtn);
|
||||
|
||||
window.confirm = originalConfirm;
|
||||
expect(ondelete).toHaveBeenCalledWith("c1");
|
||||
});
|
||||
|
||||
it("does not call ondelete when window.confirm returns false", () => {
|
||||
window.confirm = vi.fn(() => false);
|
||||
it("does not call ondelete when dialog is cancelled", () => {
|
||||
const ondelete = vi.fn();
|
||||
|
||||
render(ConversationList, {
|
||||
@@ -209,7 +207,11 @@ describe("ConversationList — delete action", () => {
|
||||
const deleteBtns = screen.getAllByTitle(/delete/i);
|
||||
fireEvent.click(deleteBtns[0]);
|
||||
|
||||
expect(window.confirm).toHaveBeenCalled();
|
||||
// ConfirmDialog appears — click "Cancel" button
|
||||
const cancelBtn = screen.getByText("Cancel");
|
||||
expect(cancelBtn).toBeTruthy();
|
||||
fireEvent.click(cancelBtn);
|
||||
|
||||
expect(ondelete).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
import { t } from '$lib/i18n/index.svelte.js';
|
||||
import { api } from '$lib/api.js';
|
||||
import { addToast } from '$lib/toasts.svelte.js';
|
||||
import { ConfirmDialog } from '$lib/ui';
|
||||
|
||||
let keys = $state([]);
|
||||
let environments = $state([]);
|
||||
@@ -27,6 +28,8 @@
|
||||
let isGenerating = $state(false);
|
||||
let revokingId = $state(null);
|
||||
let revealedKey = $state(null);
|
||||
let showRevokeConfirm = $state(false);
|
||||
let pendingRevokeKey = $state(null);
|
||||
|
||||
// Generate form state
|
||||
let formName = $state('');
|
||||
@@ -137,9 +140,8 @@
|
||||
}
|
||||
|
||||
function confirmRevoke(key) {
|
||||
if (confirm('All tools using this key will get 401. Are you sure?')) {
|
||||
handleRevoke(key.id);
|
||||
}
|
||||
pendingRevokeKey = key.id;
|
||||
showRevokeConfirm = true;
|
||||
}
|
||||
|
||||
async function copyToClipboard(text) {
|
||||
@@ -394,4 +396,16 @@
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<ConfirmDialog
|
||||
bind:show={showRevokeConfirm}
|
||||
title={$t.api_keys?.revoke?.title || 'Revoke API key?'}
|
||||
message="All tools using this key will get 401. Are you sure?"
|
||||
variant="destructive"
|
||||
confirmLabel={$t.api_keys?.revoke?.revoke || 'Revoke'}
|
||||
cancelLabel={$t.common?.cancel || 'Cancel'}
|
||||
onConfirm={() => { if (pendingRevokeKey) handleRevoke(pendingRevokeKey); }}
|
||||
onCancel={() => { pendingRevokeKey = null; }}
|
||||
/>
|
||||
|
||||
<!-- #endregion ApiKeysTab -->
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
import { getSettings, updateGlobalSettings, addEnvironment, updateEnvironment, deleteEnvironment, testEnvironmentConnection } from '../lib/api';
|
||||
import { addToast } from '$lib/toasts.svelte.js';
|
||||
import { t } from '$lib/i18n/index.svelte.js';
|
||||
import { ConfirmDialog } from '$lib/ui';
|
||||
// [/SECTION]
|
||||
|
||||
let settings = {
|
||||
@@ -53,6 +54,8 @@
|
||||
};
|
||||
|
||||
let editingEnvId = null;
|
||||
let showDeleteEnv = $state(false);
|
||||
let pendingDeleteEnvId = $state(null);
|
||||
|
||||
// #region loadSettings:Function [TYPE Function]
|
||||
// @ingroup Module
|
||||
@@ -129,18 +132,23 @@
|
||||
* @post Environment is removed from backend and list is reloaded.
|
||||
* @param {string} id - The ID of the environment to delete.
|
||||
*/
|
||||
async function handleDeleteEnv(id) {
|
||||
if (confirm($t.settings?.env_delete_confirm)) {
|
||||
try {
|
||||
console.log(`[Settings.handleDeleteEnv][Action] Deleting environment: ${id}`);
|
||||
await deleteEnvironment(id);
|
||||
addToast($t.settings?.env_deleted, 'success');
|
||||
await loadSettings();
|
||||
console.log("[Settings.handleDeleteEnv][Coherence:OK] Environment deleted.");
|
||||
} catch (error) {
|
||||
console.error("[Settings.handleDeleteEnv][Coherence:Failed] Failed to delete environment:", error);
|
||||
addToast($t.settings?.env_delete_failed, 'error');
|
||||
}
|
||||
function handleDeleteEnv(id) {
|
||||
pendingDeleteEnvId = id;
|
||||
showDeleteEnv = true;
|
||||
}
|
||||
|
||||
async function confirmDeleteEnv() {
|
||||
const id = pendingDeleteEnvId;
|
||||
pendingDeleteEnvId = null;
|
||||
try {
|
||||
console.log(`[Settings.handleDeleteEnv][Action] Deleting environment: ${id}`);
|
||||
await deleteEnvironment(id);
|
||||
addToast($t.settings?.env_deleted, 'success');
|
||||
await loadSettings();
|
||||
console.log("[Settings.handleDeleteEnv][Coherence:OK] Environment deleted.");
|
||||
} catch (error) {
|
||||
console.error("[Settings.handleDeleteEnv][Coherence:Failed] Failed to delete environment:", error);
|
||||
addToast($t.settings?.env_delete_failed, 'error');
|
||||
}
|
||||
}
|
||||
// #endregion handleDeleteEnv:Function
|
||||
@@ -352,4 +360,15 @@
|
||||
</div>
|
||||
<!-- [/SECTION] -->
|
||||
|
||||
<ConfirmDialog
|
||||
bind:show={showDeleteEnv}
|
||||
title={$t.settings?.env_delete_title || 'Delete environment?'}
|
||||
message={$t.settings?.env_delete_confirm}
|
||||
variant="destructive"
|
||||
confirmLabel={$t.common?.delete || 'Delete'}
|
||||
cancelLabel={$t.common?.cancel || 'Cancel'}
|
||||
onConfirm={confirmDeleteEnv}
|
||||
onCancel={() => { pendingDeleteEnvId = null; }}
|
||||
/>
|
||||
|
||||
<!-- #endregion SettingsPage -->
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<!-- @UX_RECOVERY: Retry load via refresh. -->
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { Button, PageHeader, Card, EmptyState } from '$lib/ui';
|
||||
import { Button, PageHeader, Card, EmptyState, ConfirmDialog } from '$lib/ui';
|
||||
import PolicyForm from '$lib/components/health/PolicyForm.svelte';
|
||||
import {
|
||||
getValidationPolicies,
|
||||
@@ -31,6 +31,8 @@
|
||||
let isLoading = $state(true);
|
||||
let showForm = $state(false);
|
||||
let selectedPolicy = $state(null);
|
||||
let showDeletePolicy = $state(false);
|
||||
let pendingDeletePolicyId = $state(null);
|
||||
|
||||
onMount(async () => {
|
||||
await loadData();
|
||||
@@ -84,7 +86,13 @@
|
||||
}
|
||||
|
||||
async function handleDelete(id) {
|
||||
if (!confirm(t.settings?.delete_confirm || 'Are you sure you want to delete this policy?')) return;
|
||||
pendingDeletePolicyId = id;
|
||||
showDeletePolicy = true;
|
||||
}
|
||||
|
||||
async function confirmDeletePolicy() {
|
||||
const id = pendingDeletePolicyId;
|
||||
pendingDeletePolicyId = null;
|
||||
try {
|
||||
await deleteValidationPolicy(id);
|
||||
addToast(t.settings?.policy_deleted || 'Policy deleted successfully', 'success');
|
||||
@@ -254,4 +262,16 @@
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<ConfirmDialog
|
||||
bind:show={showDeletePolicy}
|
||||
title={$t.settings?.delete_confirm_title || 'Delete policy?'}
|
||||
message={$t.settings?.delete_confirm || 'Are you sure you want to delete this policy?'}
|
||||
variant="destructive"
|
||||
confirmLabel={$t.common?.delete || 'Delete'}
|
||||
cancelLabel={$t.common?.cancel || 'Cancel'}
|
||||
onConfirm={confirmDeletePolicy}
|
||||
onCancel={() => { pendingDeletePolicyId = null; }}
|
||||
/>
|
||||
|
||||
<!-- #endregion AutomationPage -->
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { t } from '$lib/i18n/index.svelte.js';
|
||||
import { Button, Input, Card, PageHeader, Select } from '$lib/ui';
|
||||
import { Button, Input, Card, PageHeader, Select, ConfirmDialog } from '$lib/ui';
|
||||
import HelpTooltip from '$lib/ui/HelpTooltip.svelte';
|
||||
import { GitConfigModel } from '$lib/models/GitConfigModel.svelte.ts';
|
||||
|
||||
@@ -23,6 +23,22 @@
|
||||
$effect(() => {
|
||||
if (model.selectedGiteaConfigId) void model.loadGiteaRepos();
|
||||
});
|
||||
|
||||
let showDeleteConfig = $state(false);
|
||||
let pendingDeleteConfigId: string | null = $state(null);
|
||||
|
||||
let showDeleteRepo = $state(false);
|
||||
let pendingDeleteRepo: object | null = $state(null);
|
||||
|
||||
function handleDeleteConfig(id: string) {
|
||||
pendingDeleteConfigId = id;
|
||||
showDeleteConfig = true;
|
||||
}
|
||||
|
||||
function handleDeleteRepo(repo: object) {
|
||||
pendingDeleteRepo = repo;
|
||||
showDeleteRepo = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="p-6 max-w-6xl mx-auto">
|
||||
@@ -48,7 +64,7 @@
|
||||
<Button variant="ghost" class="text-text-subtle hover:text-primary transition-colors" onclick={() => { model.startEdit(config.id); window.scrollTo({ top: 0, behavior: 'smooth' }); }} title={$t.common?.edit || "Edit"}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"><path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z"/></svg>
|
||||
</Button>
|
||||
<Button variant="ghost" class="text-text-subtle hover:text-destructive transition-colors" onclick={() => { if (confirm($t.settings?.git_delete_confirm)) model.deleteConfig(config.id); }} title={$t.common?.delete}>
|
||||
<Button variant="ghost" class="text-text-subtle hover:text-destructive transition-colors" onclick={() => handleDeleteConfig(config.id)} title={$t.common?.delete}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd"/></svg>
|
||||
</Button>
|
||||
</div>
|
||||
@@ -137,7 +153,7 @@
|
||||
<div class="font-medium text-text truncate">{repo.full_name || repo.name}</div>
|
||||
<div class="text-xs text-text-muted truncate">{repo.clone_url || repo.html_url}</div>
|
||||
</div>
|
||||
<Button variant="destructive" size="sm" onclick={() => { if (confirm(`Delete repository ${repo.full_name || repo.name}?`)) model.deleteRemoteRepo(repo); }} disabled={model.giteaDeleting}>Delete</Button>
|
||||
<Button variant="destructive" size="sm" onclick={() => handleDeleteRepo(repo)} disabled={model.giteaDeleting}>Delete</Button>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
@@ -147,4 +163,27 @@
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ConfirmDialog
|
||||
bind:show={showDeleteConfig}
|
||||
title="Delete Git server?"
|
||||
message={$t.settings?.git_delete_confirm}
|
||||
variant="destructive"
|
||||
confirmLabel="Delete"
|
||||
cancelLabel="Cancel"
|
||||
onConfirm={() => { if (pendingDeleteConfigId) model.deleteConfig(pendingDeleteConfigId); }}
|
||||
onCancel={() => { pendingDeleteConfigId = null; }}
|
||||
/>
|
||||
|
||||
<ConfirmDialog
|
||||
bind:show={showDeleteRepo}
|
||||
title="Delete repository?"
|
||||
message={pendingDeleteRepo ? `Delete repository ${pendingDeleteRepo.full_name || pendingDeleteRepo.name}?` : ''}
|
||||
variant="destructive"
|
||||
confirmLabel="Delete"
|
||||
cancelLabel="Cancel"
|
||||
onConfirm={() => { if (pendingDeleteRepo) model.deleteRemoteRepo(pendingDeleteRepo); }}
|
||||
onCancel={() => { pendingDeleteRepo = null; }}
|
||||
/>
|
||||
|
||||
<!-- #endregion GitDashboardPage -->
|
||||
|
||||
@@ -79,8 +79,6 @@ describe('GitSettingsPage UX Contracts', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
// Default confirm to always accept for tests
|
||||
global.confirm = vi.fn(() => true);
|
||||
});
|
||||
|
||||
// @UX_STATE: Initial Load
|
||||
@@ -164,12 +162,15 @@ describe('GitSettingsPage UX Contracts', () => {
|
||||
render(GitSettingsPage);
|
||||
await waitFor(() => expect(screen.getByText('Dev GitLab')).toBeTruthy());
|
||||
|
||||
// Click delete button
|
||||
// Click delete button to open ConfirmDialog
|
||||
const deleteButton = screen.getByTitle('Delete');
|
||||
await fireEvent.click(deleteButton);
|
||||
|
||||
// Click the "Delete" button in the ConfirmDialog
|
||||
await waitFor(() => expect(screen.getByText('Delete')).toBeTruthy());
|
||||
await fireEvent.click(screen.getByText('Delete'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(global.confirm).toHaveBeenCalledWith('Are you sure?');
|
||||
expect(gitService.deleteConfig).toHaveBeenCalledWith('conf-1');
|
||||
expect(addToast).toHaveBeenCalledWith('Git configuration deleted', 'success');
|
||||
expect(screen.queryByText('Dev GitLab')).toBeNull(); // Should be removed from DOM
|
||||
|
||||
Reference in New Issue
Block a user