diff --git a/apps/desktop-tauri/src/surfaces/settings/tabs/GeneralTab.test.tsx b/apps/desktop-tauri/src/surfaces/settings/tabs/GeneralTab.test.tsx
index 4656c91abf1..f35261dd2c1 100644
--- a/apps/desktop-tauri/src/surfaces/settings/tabs/GeneralTab.test.tsx
+++ b/apps/desktop-tauri/src/surfaces/settings/tabs/GeneralTab.test.tsx
@@ -318,6 +318,26 @@ describe("GeneralTab language picker", () => {
});
});
+ it("cancels the sound preview timer on unmount", () => {
+ vi.useFakeTimers();
+ try {
+ const { unmount } = render(
+ ,
+ );
+ fireEvent.click(
+ screen.getByRole("button", {
+ name: "NotificationSoundEventHighUsage: NotificationTestSound",
+ }),
+ );
+ expect(vi.getTimerCount()).toBeGreaterThan(0);
+
+ unmount();
+ expect(vi.getTimerCount()).toBe(0);
+ } finally {
+ vi.useRealTimers();
+ }
+ });
+
it("assigns and clears a custom WAV for one notification", async () => {
const set = vi.fn();
vi.mocked(open).mockResolvedValue("C:\\sounds\\high-usage.wav");
diff --git a/apps/desktop-tauri/src/surfaces/settings/tabs/GeneralTab.tsx b/apps/desktop-tauri/src/surfaces/settings/tabs/GeneralTab.tsx
index 6778a7b42bd..76be95ac35e 100644
--- a/apps/desktop-tauri/src/surfaces/settings/tabs/GeneralTab.tsx
+++ b/apps/desktop-tauri/src/surfaces/settings/tabs/GeneralTab.tsx
@@ -1,4 +1,4 @@
-import { useCallback, useEffect, useState } from "react";
+import { useCallback, useEffect, useRef, useState } from "react";
import { useLocale } from "../../../hooks/useLocale";
import { invoke } from "@tauri-apps/api/core";
import { open } from "@tauri-apps/plugin-dialog";
@@ -168,15 +168,33 @@ export default function GeneralTab({
.catch(() => {}); // version row degrades to absent, never blocks the tab
}, []);
+ const soundPreviewTimeoutRef = useRef(null);
+
+ useEffect(
+ () => () => {
+ if (soundPreviewTimeoutRef.current !== null) {
+ window.clearTimeout(soundPreviewTimeoutRef.current);
+ }
+ },
+ [],
+ );
+
const handleTestSound = useCallback((event: NotificationSoundEvent) => {
setSoundError(null);
setPlayingSound(event);
- const timeoutId = window.setTimeout(
- () => setPlayingSound(null),
- NOTIFICATION_SOUND_PREVIEW_DURATION_MS,
- );
+ if (soundPreviewTimeoutRef.current !== null) {
+ window.clearTimeout(soundPreviewTimeoutRef.current);
+ }
+ const timeoutId = window.setTimeout(() => {
+ soundPreviewTimeoutRef.current = null;
+ setPlayingSound(null);
+ }, NOTIFICATION_SOUND_PREVIEW_DURATION_MS);
+ soundPreviewTimeoutRef.current = timeoutId;
void playNotificationSound(event).catch((error: unknown) => {
window.clearTimeout(timeoutId);
+ if (soundPreviewTimeoutRef.current === timeoutId) {
+ soundPreviewTimeoutRef.current = null;
+ }
setPlayingSound(null);
setSoundError(error instanceof Error ? error.message : String(error));
});
diff --git a/apps/desktop-tauri/src/surfaces/settings/tabs/HooksTab.test.tsx b/apps/desktop-tauri/src/surfaces/settings/tabs/HooksTab.test.tsx
index ee0fdd667d6..fa22961dc6c 100644
--- a/apps/desktop-tauri/src/surfaces/settings/tabs/HooksTab.test.tsx
+++ b/apps/desktop-tauri/src/surfaces/settings/tabs/HooksTab.test.tsx
@@ -256,11 +256,16 @@ describe("HooksTab", () => {
);
await renderTab(snapshot({ rules: full }));
expect(screen.getAllByTestId("hook-rule")).toHaveLength(32);
- expect(screen.getByRole("button", { name: "+ HooksAddRule" })).toBeDisabled();
- for (const add of screen.getAllByRole("button", { name: "+ HooksAddArgument" })) {
- expect(add).toBeDisabled();
+ // Role queries over 32 × 32 argument rows are slow enough under a loaded
+ // CI runner to hit the default 5 s timeout; text queries find the same
+ // buttons, and the larger timeout covers the heavy render itself.
+ expect(screen.getByText("+ HooksAddRule").closest("button")).toBeDisabled();
+ const addArguments = screen.getAllByText("+ HooksAddArgument");
+ expect(addArguments).toHaveLength(32);
+ for (const add of addArguments) {
+ expect(add.closest("button")).toBeDisabled();
}
- });
+ }, 20_000);
it("keeps an unreadable hooks.json read-only and shows why", async () => {
await renderTab(snapshot({ error: "expected value at line 1 column 1" }));