Files
hikari-desktop/src/lib/stores/toasts.test.ts
T
hikari 6c853ae73d feat: handle StopFailure hook event for API error turns (#224)
Parses [StopFailure Hook] from stderr, emits claude:stop-failure, and
transitions the character to error state with a toast notification.
2026-03-20 09:30:17 -07:00

273 lines
8.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import { get } from "svelte/store";
import { getAchievementRarity, getRarityColour, toastStore } from "./toasts";
import type { AchievementUnlockedEvent } from "$lib/types/achievements";
// ---
describe("getAchievementRarity", () => {
describe("legendary tier", () => {
it("classifies TokenMaster as legendary", () => {
expect(getAchievementRarity("TokenMaster")).toBe("legendary");
});
});
describe("epic tier", () => {
it("classifies CodeMachine as epic", () => {
expect(getAchievementRarity("CodeMachine")).toBe("epic");
});
it("classifies Unstoppable as epic", () => {
expect(getAchievementRarity("Unstoppable")).toBe("epic");
});
});
describe("rare tier", () => {
it("classifies BlossomingCoder as rare", () => {
expect(getAchievementRarity("BlossomingCoder")).toBe("rare");
});
it("classifies CodeWizard as rare", () => {
expect(getAchievementRarity("CodeWizard")).toBe("rare");
});
it("classifies MasterBuilder as rare", () => {
expect(getAchievementRarity("MasterBuilder")).toBe("rare");
});
it("classifies EnduranceChamp as rare", () => {
expect(getAchievementRarity("EnduranceChamp")).toBe("rare");
});
it("classifies DeepDive as rare", () => {
expect(getAchievementRarity("DeepDive")).toBe("rare");
});
it("classifies CreativeCoder as rare", () => {
expect(getAchievementRarity("CreativeCoder")).toBe("rare");
});
});
describe("common tier", () => {
it("classifies unknown IDs as common", () => {
expect(getAchievementRarity("FirstChat")).toBe("common");
expect(getAchievementRarity("SomeNewAchievement")).toBe("common");
expect(getAchievementRarity("")).toBe("common");
});
});
});
describe("getRarityColour", () => {
it("returns yellow-to-orange gradient for legendary", () => {
expect(getRarityColour("legendary")).toBe("from-yellow-400 to-orange-500");
});
it("returns purple-to-pink gradient for epic", () => {
expect(getRarityColour("epic")).toBe("from-purple-400 to-pink-500");
});
it("returns blue-to-indigo gradient for rare", () => {
expect(getRarityColour("rare")).toBe("from-blue-400 to-indigo-500");
});
it("returns green-to-emerald gradient for common", () => {
expect(getRarityColour("common")).toBe("from-green-400 to-emerald-500");
});
it("falls back to green-to-emerald gradient for unknown rarities", () => {
expect(getRarityColour("mythic")).toBe("from-green-400 to-emerald-500");
expect(getRarityColour("")).toBe("from-green-400 to-emerald-500");
});
describe("end-to-end rarity pipeline", () => {
it("produces the correct colour for a legendary achievement", () => {
const colour = getRarityColour(getAchievementRarity("TokenMaster"));
expect(colour).toBe("from-yellow-400 to-orange-500");
});
it("produces the correct colour for an epic achievement", () => {
const colour = getRarityColour(getAchievementRarity("CodeMachine"));
expect(colour).toBe("from-purple-400 to-pink-500");
});
it("produces the correct colour for a rare achievement", () => {
const colour = getRarityColour(getAchievementRarity("CodeWizard"));
expect(colour).toBe("from-blue-400 to-indigo-500");
});
it("produces the correct colour for a common achievement", () => {
const colour = getRarityColour(getAchievementRarity("FirstChat"));
expect(colour).toBe("from-green-400 to-emerald-500");
});
});
});
// ---
describe("toastStore", () => {
beforeEach(() => {
vi.useFakeTimers();
// Clear all toasts before each test
const current = get(toastStore);
for (const toast of current) {
toastStore.remove(toast.id);
}
});
afterEach(() => {
vi.useRealTimers();
});
describe("addInfo", () => {
it("adds an info toast with the correct fields", () => {
toastStore.addInfo("Hello world", "🌍");
const toasts = get(toastStore);
expect(toasts).toHaveLength(1);
const toast = toasts[0];
expect(toast.kind).toBe("info");
if (toast.kind === "info") {
expect(toast.message).toBe("Hello world");
expect(toast.icon).toBe("🌍");
expect(typeof toast.id).toBe("string");
expect(toast.id.length).toBeGreaterThan(0);
}
});
it("uses a default icon when none is provided", () => {
toastStore.addInfo("Default icon test");
const toasts = get(toastStore);
const toast = toasts[0];
if (toast.kind === "info") {
expect(toast.icon).toBe("️");
}
});
it("auto-dismisses after 4000ms", () => {
toastStore.addInfo("Auto-dismiss test");
expect(get(toastStore)).toHaveLength(1);
vi.advanceTimersByTime(3999);
expect(get(toastStore)).toHaveLength(1);
vi.advanceTimersByTime(1);
expect(get(toastStore)).toHaveLength(0);
});
});
describe("addAchievement", () => {
const mockAchievement: AchievementUnlockedEvent["achievement"] = {
id: "FirstMessage",
name: "First Message",
description: "Sent your first message",
icon: "💬",
unlocked_at: "2026-01-01T00:00:00Z",
};
it("adds an achievement toast with the correct fields", () => {
toastStore.addAchievement(mockAchievement);
const toasts = get(toastStore);
expect(toasts).toHaveLength(1);
const toast = toasts[0];
expect(toast.kind).toBe("achievement");
if (toast.kind === "achievement") {
expect(toast.achievement).toEqual(mockAchievement);
expect(typeof toast.id).toBe("string");
}
});
it("auto-dismisses after 5000ms", () => {
toastStore.addAchievement(mockAchievement);
expect(get(toastStore)).toHaveLength(1);
vi.advanceTimersByTime(4999);
expect(get(toastStore)).toHaveLength(1);
vi.advanceTimersByTime(1);
expect(get(toastStore)).toHaveLength(0);
});
});
describe("addError", () => {
it("adds an error toast with the warning icon", () => {
toastStore.addError("Something went wrong");
const toasts = get(toastStore);
expect(toasts).toHaveLength(1);
const toast = toasts[0];
expect(toast.kind).toBe("info");
if (toast.kind === "info") {
expect(toast.message).toBe("Something went wrong");
expect(toast.icon).toBe("⚠️");
expect(typeof toast.id).toBe("string");
expect(toast.id.length).toBeGreaterThan(0);
}
});
it("auto-dismisses after 6000ms", () => {
toastStore.addError("Rate limit reached");
expect(get(toastStore)).toHaveLength(1);
vi.advanceTimersByTime(5999);
expect(get(toastStore)).toHaveLength(1);
vi.advanceTimersByTime(1);
expect(get(toastStore)).toHaveLength(0);
});
});
describe("addUpdate", () => {
it("adds a persistent update toast with the correct fields", () => {
toastStore.addUpdate("2.0.0", "1.9.0", "https://example.com/release");
const toasts = get(toastStore);
expect(toasts).toHaveLength(1);
const toast = toasts[0];
expect(toast.kind).toBe("update");
if (toast.kind === "update") {
expect(toast.latestVersion).toBe("2.0.0");
expect(toast.currentVersion).toBe("1.9.0");
expect(toast.releaseUrl).toBe("https://example.com/release");
expect(typeof toast.id).toBe("string");
}
});
it("does not auto-dismiss after a long time", () => {
toastStore.addUpdate("2.0.0", "1.9.0", "https://example.com/release");
expect(get(toastStore)).toHaveLength(1);
vi.advanceTimersByTime(60000);
expect(get(toastStore)).toHaveLength(1);
});
});
describe("remove", () => {
it("removes a toast by id", () => {
toastStore.addInfo("To be removed");
const toasts = get(toastStore);
expect(toasts).toHaveLength(1);
const id = toasts[0].id;
toastStore.remove(id);
expect(get(toastStore)).toHaveLength(0);
});
it("does not affect other toasts when removing by id", () => {
toastStore.addInfo("First toast");
toastStore.addInfo("Second toast");
const toasts = get(toastStore);
expect(toasts).toHaveLength(2);
toastStore.remove(toasts[0].id);
const remaining = get(toastStore);
expect(remaining).toHaveLength(1);
if (remaining[0].kind === "info") {
expect(remaining[0].message).toBe("Second toast");
}
});
it("is a no-op when the id does not exist", () => {
toastStore.addInfo("Existing toast");
toastStore.remove("non-existent-id");
expect(get(toastStore)).toHaveLength(1);
});
});
});