Skip to content

Commit 465bde1

Browse files
authored
Merge pull request #224 from refactor-group/160-feature-upcoming-sessions-list-for-today
160 feature upcoming sessions list for today
2 parents 551b3e4 + dda6fd9 commit 465bde1

30 files changed

+3131
-9
lines changed
Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
import { render, screen, fireEvent } from "@testing-library/react";
2+
import { describe, it, expect, vi } from "vitest";
3+
import { TodaySessionCard } from "@/components/ui/dashboard/today-session-card";
4+
import { EnrichedCoachingSession } from "@/types/coaching-session";
5+
import { DateTime } from "ts-luxon";
6+
7+
/**
8+
* Test Suite: TodaySessionCard Component
9+
* Story: "Display a single coaching session card with all relevant information"
10+
*/
11+
12+
// Mock the share session link functionality
13+
vi.mock("@/components/ui/share-session-link", () => ({
14+
copyCoachingSessionLinkWithToast: vi.fn().mockResolvedValue(undefined),
15+
}));
16+
17+
// Mock Next.js Link component
18+
vi.mock("next/link", () => ({
19+
default: ({ children, href }: { children: React.ReactNode; href: string }) => (
20+
<a href={href}>{children}</a>
21+
),
22+
}));
23+
24+
// Mock the auth store
25+
vi.mock("@/lib/providers/auth-store-provider", () => ({
26+
useAuthStore: vi.fn(() => ({
27+
userSession: {
28+
id: "user-1",
29+
first_name: "Test",
30+
last_name: "User",
31+
timezone: "America/Los_Angeles",
32+
},
33+
})),
34+
}));
35+
36+
const createMockEnrichedSession = (
37+
overrides?: Partial<EnrichedCoachingSession>
38+
): EnrichedCoachingSession => ({
39+
id: "session-1",
40+
coaching_relationship_id: "rel-1",
41+
date: DateTime.now().plus({ hours: 2 }).toUTC().toISO(),
42+
created_at: DateTime.now(),
43+
updated_at: DateTime.now(),
44+
relationship: {
45+
id: "rel-1",
46+
coach_id: "coach-1",
47+
coachee_id: "user-1",
48+
organization_id: "org-1",
49+
created_at: DateTime.now(),
50+
updated_at: DateTime.now(),
51+
},
52+
// Coach and coachee are top-level fields, not nested in relationship
53+
coach: {
54+
id: "coach-1",
55+
first_name: "Coach",
56+
last_name: "Person",
57+
display_name: "Coach Person",
58+
email: "coach@example.com",
59+
timezone: "America/Los_Angeles",
60+
role: "coach",
61+
roles: [],
62+
created_at: DateTime.now().toISO() ?? '',
63+
updated_at: DateTime.now().toISO() ?? '',
64+
},
65+
coachee: {
66+
id: "user-1",
67+
first_name: "Caleb",
68+
last_name: "Bourg",
69+
display_name: "Caleb Bourg",
70+
email: "caleb@example.com",
71+
timezone: "America/Los_Angeles",
72+
role: "coach",
73+
roles: [],
74+
created_at: DateTime.now().toISO() ?? '',
75+
updated_at: DateTime.now().toISO() ?? '',
76+
},
77+
organization: {
78+
id: "org-1",
79+
name: "Refactor Group",
80+
created_at: DateTime.now(),
81+
updated_at: DateTime.now(),
82+
},
83+
overarching_goal: {
84+
id: "goal-1",
85+
title: "Q4 Strategy Review",
86+
details: "",
87+
coaching_relationship_id: "rel-1",
88+
created_at: DateTime.now(),
89+
updated_at: DateTime.now(),
90+
},
91+
...overrides,
92+
});
93+
94+
describe("TodaySessionCard", () => {
95+
it("should render session goal title", () => {
96+
const session = createMockEnrichedSession({
97+
overarching_goal: {
98+
id: "goal-1",
99+
title: "Product Launch Planning",
100+
details: "",
101+
coaching_relationship_id: "rel-1",
102+
created_at: DateTime.now(),
103+
updated_at: DateTime.now(),
104+
},
105+
});
106+
107+
render(<TodaySessionCard session={session} />);
108+
109+
expect(screen.getByText("Goal: Product Launch Planning")).toBeInTheDocument();
110+
});
111+
112+
it("should display participant name", () => {
113+
const session: EnrichedCoachingSession = {
114+
...createMockEnrichedSession(),
115+
// Coach is top-level, user-1 is coachee so should show coach name
116+
coach: {
117+
id: "coach-1",
118+
first_name: "Alice",
119+
last_name: "Smith",
120+
display_name: "Alice Smith",
121+
email: "alice@example.com",
122+
timezone: "America/Los_Angeles",
123+
role: "coach",
124+
roles: [],
125+
created_at: DateTime.now().toISO() ?? '',
126+
updated_at: DateTime.now().toISO() ?? '',
127+
},
128+
};
129+
130+
render(<TodaySessionCard session={session} />);
131+
132+
expect(screen.getByText("Alice Smith")).toBeInTheDocument();
133+
expect(screen.getByText("Meeting with:")).toBeInTheDocument();
134+
});
135+
136+
it("should display user role as Coach", () => {
137+
const session: EnrichedCoachingSession = {
138+
...createMockEnrichedSession(),
139+
relationship: {
140+
id: "rel-1",
141+
coach_id: "user-1", // User is the coach
142+
coachee_id: "coachee-2",
143+
organization_id: "org-1",
144+
created_at: DateTime.now(),
145+
updated_at: DateTime.now(),
146+
},
147+
// Coachee is top-level, user-1 is coach so should show coachee name
148+
coachee: {
149+
id: "coachee-2",
150+
first_name: "Caleb",
151+
last_name: "Bourg",
152+
display_name: "Caleb Bourg",
153+
email: "caleb@example.com",
154+
timezone: "America/Los_Angeles",
155+
role: "coach",
156+
roles: [],
157+
created_at: DateTime.now().toISO() ?? '',
158+
updated_at: DateTime.now().toISO() ?? '',
159+
},
160+
};
161+
162+
render(<TodaySessionCard session={session} />);
163+
164+
expect(screen.getByText("Coach")).toBeInTheDocument();
165+
expect(screen.getByText("Your role:")).toBeInTheDocument();
166+
});
167+
168+
it("should display organization name", () => {
169+
const session = createMockEnrichedSession({
170+
organization: {
171+
id: "org-2",
172+
name: "Tech Innovations Inc",
173+
created_at: DateTime.now(),
174+
updated_at: DateTime.now(),
175+
},
176+
});
177+
178+
render(<TodaySessionCard session={session} />);
179+
180+
expect(screen.getByText("Tech Innovations Inc")).toBeInTheDocument();
181+
});
182+
183+
it("should display urgency message for imminent session", () => {
184+
const session = createMockEnrichedSession({
185+
date: DateTime.now().plus({ minutes: 15 }).toUTC().toISO(),
186+
});
187+
188+
render(<TodaySessionCard session={session} />);
189+
190+
expect(screen.getByText(/Starting in \d+ minutes?/)).toBeInTheDocument();
191+
});
192+
193+
it("should apply imminent urgency styles", () => {
194+
const session = createMockEnrichedSession({
195+
date: DateTime.now().plus({ minutes: 15 }).toUTC().toISO(),
196+
});
197+
198+
const { container } = render(<TodaySessionCard session={session} />);
199+
200+
const header = container.querySelector(".bg-amber-50");
201+
expect(header).toBeInTheDocument();
202+
});
203+
204+
it("should apply soon urgency styles", () => {
205+
const session = createMockEnrichedSession({
206+
date: DateTime.now().plus({ hours: 1 }).toUTC().toISO(),
207+
});
208+
209+
const { container } = render(<TodaySessionCard session={session} />);
210+
211+
const header = container.querySelector(".bg-blue-50");
212+
expect(header).toBeInTheDocument();
213+
});
214+
215+
it("should apply later urgency styles", () => {
216+
const session = createMockEnrichedSession({
217+
date: DateTime.now().plus({ hours: 3 }).toUTC().toISO(),
218+
});
219+
220+
const { container } = render(<TodaySessionCard session={session} />);
221+
222+
const header = container.querySelector(".bg-slate-50");
223+
expect(header).toBeInTheDocument();
224+
});
225+
226+
it("should apply past urgency styles", () => {
227+
const session = createMockEnrichedSession({
228+
date: DateTime.now().minus({ hours: 1 }).toUTC().toISO(),
229+
});
230+
231+
const { container } = render(<TodaySessionCard session={session} />);
232+
233+
const header = container.querySelector(".bg-muted");
234+
expect(header).toBeInTheDocument();
235+
});
236+
237+
it("should show 'Join Session' button for upcoming sessions", () => {
238+
const session = createMockEnrichedSession({
239+
date: DateTime.now().plus({ hours: 2 }).toUTC().toISO(),
240+
});
241+
242+
render(<TodaySessionCard session={session} />);
243+
244+
expect(screen.getByText("Join Session")).toBeInTheDocument();
245+
});
246+
247+
it("should show 'View Session' button for past sessions", () => {
248+
const session = createMockEnrichedSession({
249+
date: DateTime.now().minus({ hours: 1 }).toUTC().toISO(),
250+
});
251+
252+
render(<TodaySessionCard session={session} />);
253+
254+
expect(screen.getByText("View Session")).toBeInTheDocument();
255+
});
256+
257+
it("should have a link to the session detail page", () => {
258+
const session = createMockEnrichedSession({
259+
id: "session-123",
260+
});
261+
262+
render(<TodaySessionCard session={session} />);
263+
264+
const link = screen.getByRole("link");
265+
expect(link).toHaveAttribute("href", "/coaching-sessions/session-123");
266+
});
267+
268+
it("should have a share button", () => {
269+
const session = createMockEnrichedSession();
270+
271+
render(<TodaySessionCard session={session} />);
272+
273+
const shareButton = screen.getByText("Copy session link", { selector: ".sr-only" });
274+
expect(shareButton).toBeInTheDocument();
275+
});
276+
277+
it("should call share handler when share button is clicked", async () => {
278+
const { copyCoachingSessionLinkWithToast } = await import(
279+
"@/components/ui/share-session-link"
280+
);
281+
const session = createMockEnrichedSession({
282+
id: "session-456",
283+
});
284+
285+
render(<TodaySessionCard session={session} />);
286+
287+
// Find the button by its sr-only text
288+
const shareText = screen.getByText("Copy session link", { selector: ".sr-only" });
289+
const shareButton = shareText.closest("button");
290+
expect(shareButton).toBeInTheDocument();
291+
292+
fireEvent.click(shareButton!);
293+
expect(copyCoachingSessionLinkWithToast).toHaveBeenCalledWith("session-456");
294+
});
295+
296+
it("should render all icon elements", () => {
297+
const session = createMockEnrichedSession();
298+
299+
const { container } = render(<TodaySessionCard session={session} />);
300+
301+
// Check for presence of icon containers (Lucide icons render as SVGs)
302+
const icons = container.querySelectorAll("svg");
303+
expect(icons.length).toBeGreaterThan(0);
304+
});
305+
});

0 commit comments

Comments
 (0)