feat(ui): Orca Dark / Orca Light themes and notification sounds
Two optional named themes from Orca's neutral palette join the Appearance picker (Orca Dark keeps a blue accent so white-on-accent buttons stay legible; Orca Light's status colours are darkened to clear 4.5:1 on their chips). A shown notification can play a sound — None (default), Chime, Pop or Alert — chosen globally and per notification type in Settings → Notifications; the WAVs are generated by scripts/gen-notification-sounds.js.
This commit is contained in:
@@ -3,7 +3,8 @@
|
||||
* browser notification, and which preference gates each one. Covers issue #29:
|
||||
* "Claude needs confirmation" and "Claude finished a turn" must fire out of the
|
||||
* box once notifications are enabled, since those are the two moments the user
|
||||
* is the blocker.
|
||||
* is the blocker. Also: a shown notification plays its type's sound, else the
|
||||
* default one, and "none" plays nothing.
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
|
||||
@@ -104,6 +105,46 @@ describe("useNotifications", () => {
|
||||
expect(Notification).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("plays the type's own sound, else the default, and nothing for none", async () => {
|
||||
const played: string[] = [];
|
||||
vi.spyOn(HTMLMediaElement.prototype, "play").mockImplementation(function (
|
||||
this: HTMLMediaElement
|
||||
) {
|
||||
played.push(this.src.split("/").pop()!);
|
||||
return Promise.resolve();
|
||||
});
|
||||
// The hook's own lane list must read as a list, or a later event finds none.
|
||||
fetchMock.mockImplementation(async (url: string) => ({
|
||||
ok: true,
|
||||
json: async () =>
|
||||
url === "/api/push/send"
|
||||
? { ok: true, native: true, pushed: 1 }
|
||||
: { lanes: [], counts: {} },
|
||||
}));
|
||||
enable({ sound: "chime", soundByType: { onTurnComplete: "pop", onAwaitingInput: "none" } });
|
||||
renderHook(() => useNotifications());
|
||||
|
||||
emit("Stop", "Done");
|
||||
await vi.waitFor(() => expect(played).toEqual(["pop.wav"]));
|
||||
emit("Notification", "needs permission");
|
||||
await vi.waitFor(() => expect(sentTitles()).toHaveLength(2));
|
||||
await new Promise((r) => setTimeout(r, 20));
|
||||
expect(played).toEqual(["pop.wav"]);
|
||||
|
||||
localStorage.setItem(
|
||||
NOTIF_KEY,
|
||||
JSON.stringify({
|
||||
...defaultNotifPrefs,
|
||||
enabled: true,
|
||||
sound: "chime",
|
||||
onSessionComplete: true,
|
||||
})
|
||||
);
|
||||
emit("SessionEnd", "Closed");
|
||||
await vi.waitFor(() => expect(played).toEqual(["pop.wav", "chime.wav"]));
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("stays silent while notifications are disabled", async () => {
|
||||
renderHook(() => useNotifications());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user