presentercoach / apps /web /tests /dashboardStore.test.ts
nathycodes's picture
Deploy Tech Presenter Coach
ec17af3
Raw History Blame Contribute Delete
5.67 kB
import { beforeEach, describe, expect, it, vi } from "vitest";
import { apiFetch } from "@/lib/api";
import { isCoachingAvailable, isCoachingPending } from "@/lib/dashboard";
import { resetDashboardStore, useDashboardStore } from "@/store/useDashboardStore";
vi.mock("@/lib/api", () => ({
apiFetch: vi.fn(),
}));
const mockedApiFetch = vi.mocked(apiFetch);
describe("dashboard store", () => {
beforeEach(() => {
resetDashboardStore();
mockedApiFetch.mockReset();
});
it("fetches dashboard slices with the JSON apiFetch contract", async () => {
mockedApiFetch.mockImplementation(async (path) => {
if (path === "/dashboard/overview") {
return {
total_sessions: 2,
total_practice_minutes: 12,
current_streak: 1,
longest_streak: 2,
recent_30d: {
session_count: 2,
avg_overall_score: 80,
avg_wpm: 135,
avg_filler_rate: 1.2,
avg_delivery_score: 76,
practice_days: 1,
},
};
}
if (String(path).startsWith("/dashboard/trends/")) {
return [{ date: "2026-06-08", value: 80 }];
}
if (String(path).startsWith("/dashboard/sessions")) {
return [
{
session_id: 1,
recorded_at: "2026-06-08T00:00:00",
duration_seconds: 60,
wpm: 135,
filler_count: 1,
filler_rate: 1,
speech_score: 80,
delivery_score: 75,
overall_score: 78,
eye_contact_score: 0.7,
vocabulary_density: 0.6,
},
];
}
if (String(path).startsWith("/dashboard/heatmap")) {
return [{ date: "2026-06-08", count: 1, minutes: 1 }];
}
if (String(path).startsWith("/dashboard/fillers")) {
return { um: 2 };
}
if (path === "/coaching/latest") {
return { status: "completed", summary: "Good work.", session_id: 1 };
}
if (path === "/dashboard/goals") {
return [
{
id: 1,
metric_name: "wpm",
comparison: "between",
target_value: 120,
target_upper: 150,
is_active: true,
current_value: 135,
achieved: true,
created_at: "2026-09-04T00:00:00",
updated_at: "2026-09-04T00:00:00",
},
];
}
throw new Error(`Unexpected path ${path}`);
});
await useDashboardStore.getState().fetchAll();
const state = useDashboardStore.getState();
expect(state.overview?.total_sessions).toBe(2);
expect(state.trends.avg_overall_score).toHaveLength(1);
expect(state.sessions[0].session_id).toBe(1);
expect(state.fillers.um).toBe(2);
expect(state.latestCoaching?.status).toBe("completed");
expect(state.goals[0].achieved).toBe(true);
});
it("keeps latest coaching unavailable as a normal empty state", async () => {
mockedApiFetch.mockImplementation(async (path) => {
if (path === "/coaching/latest") return { status: "not_available" };
if (path === "/dashboard/overview") {
return {
total_sessions: 0,
total_practice_minutes: 0,
current_streak: 0,
longest_streak: 0,
recent_30d: {
session_count: 0,
avg_overall_score: 0,
avg_wpm: 0,
avg_filler_rate: 0,
avg_delivery_score: 0,
practice_days: 0,
},
};
}
if (String(path).startsWith("/dashboard/trends/")) return [];
if (String(path).startsWith("/dashboard/sessions")) return [];
if (String(path).startsWith("/dashboard/heatmap")) return [];
if (String(path).startsWith("/dashboard/fillers")) return {};
if (path === "/dashboard/goals") return [];
throw new Error(`Unexpected path ${path}`);
});
await useDashboardStore.getState().fetchAll();
expect(useDashboardStore.getState().latestCoaching?.status).toBe("not_available");
expect(isCoachingAvailable(useDashboardStore.getState().latestCoaching)).toBe(false);
});
it("creates, updates, and deletes goals", async () => {
const goal = {
id: 7,
metric_name: "filler_rate" as const,
comparison: "lte" as const,
target_value: 1,
target_upper: null,
is_active: true,
current_value: 2,
achieved: false,
created_at: "2026-09-04T00:00:00",
updated_at: "2026-09-04T00:00:00",
};
mockedApiFetch
.mockResolvedValueOnce(goal)
.mockResolvedValueOnce({ ...goal, is_active: false })
.mockResolvedValueOnce({ status: "deleted" });
await useDashboardStore.getState().createGoal({
metric_name: "filler_rate",
comparison: "lte",
target_value: 1,
target_upper: null,
is_active: true,
});
expect(useDashboardStore.getState().goals).toHaveLength(1);
await useDashboardStore.getState().updateGoal(7, { is_active: false });
expect(useDashboardStore.getState().goals[0].is_active).toBe(false);
await useDashboardStore.getState().deleteGoal(7);
expect(useDashboardStore.getState().goals).toHaveLength(0);
});
});
describe("coaching state helpers", () => {
it("recognizes polling states", () => {
expect(isCoachingPending({ status: "pending" })).toBe(true);
expect(isCoachingPending({ status: "generating" })).toBe(true);
expect(isCoachingPending({ status: "completed" })).toBe(false);
expect(isCoachingAvailable({ status: "failed" })).toBe(true);
expect(isCoachingAvailable({ status: "not_available" })).toBe(false);
});
});