Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions frontend/src/components/dashboard/CalendarView.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { describe, it, expect } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import { CalendarView } from "./CalendarView";

describe("CalendarView", () => {
it("disables the Today button when already viewing the current month and enables it when navigating away", () => {
render(<CalendarView streams={[]} />);

const todayButton = screen.getByRole("button", { name: "Today" });
expect(todayButton).toBeDisabled();

const nextMonthButton = screen.getByRole("button", { name: "Next month" });
fireEvent.click(nextMonthButton);

expect(todayButton).not.toBeDisabled();
});

it("resets the displayed month and year to the current system date when clicking Today", () => {
render(<CalendarView streams={[]} />);

const now = new Date();
const expectedLabel = now.toLocaleString("default", {
month: "long",
year: "numeric",
});

const monthHeading = screen.getByTestId("calendar-current-month");
expect(monthHeading).toHaveTextContent(expectedLabel);

const nextMonthButton = screen.getByRole("button", { name: "Next month" });
fireEvent.click(nextMonthButton);
fireEvent.click(nextMonthButton);
fireEvent.click(nextMonthButton);

const todayButton = screen.getByRole("button", { name: "Today" });
expect(todayButton).not.toBeDisabled();

fireEvent.click(todayButton);

expect(monthHeading).toHaveTextContent(expectedLabel);
expect(todayButton).toBeDisabled();
});

it("highlights the current day cell with an accent border ring and a Today pill badge", () => {
render(<CalendarView streams={[]} />);

const todayCell = screen.getByTestId("calendar-today-cell");
expect(todayCell).toBeInTheDocument();
expect(todayCell).toHaveAttribute("aria-current", "date");
expect(todayCell.className).toContain("ring-2");
expect(todayCell.className).toContain("ring-accent");

const todayBadge = screen.getByTestId("calendar-today-badge");
expect(todayBadge).toBeInTheDocument();
expect(todayBadge).toHaveTextContent("Today");
});

it("navigates to previous month and returns to current month via Today button", () => {
render(<CalendarView streams={[]} />);

const now = new Date();
const expectedLabel = now.toLocaleString("default", {
month: "long",
year: "numeric",
});

const prevMonthButton = screen.getByRole("button", {
name: "Previous month",
});
fireEvent.click(prevMonthButton);

const todayButton = screen.getByRole("button", { name: "Today" });
expect(todayButton).not.toBeDisabled();

fireEvent.click(todayButton);
expect(screen.getByTestId("calendar-current-month")).toHaveTextContent(
expectedLabel,
);
expect(todayButton).toBeDisabled();
});
});
61 changes: 46 additions & 15 deletions frontend/src/components/dashboard/CalendarView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,11 @@ export function CalendarView({
return days;
}, [currentDate, filteredStreams]);

const now = new Date();
const isViewingCurrentMonth =
currentDate.getFullYear() === now.getFullYear() &&
currentDate.getMonth() === now.getMonth();

const goToPrevMonth = () => {
setCurrentDate(new Date(currentDate.getFullYear(), currentDate.getMonth() - 1, 1));
};
Expand Down Expand Up @@ -194,24 +199,34 @@ export function CalendarView({
{/* Navigation */}
<div className="flex items-center justify-between mb-4">
<button
type="button"
onClick={goToPrevMonth}
aria-label="Previous month"
className="p-2 rounded-lg hover:bg-white/5 transition-colors"
>
<ChevronLeft className="h-5 w-5" />
</button>
<div className="flex items-center gap-4">
<h4 className="text-lg font-semibold">
<h4 className="text-lg font-semibold" data-testid="calendar-current-month">
{currentDate.toLocaleString("default", { month: "long", year: "numeric" })}
</h4>
<button
type="button"
onClick={goToToday}
className="px-3 py-1 text-sm rounded-lg border border-slate-700 hover:border-accent transition-colors"
disabled={isViewingCurrentMonth}
className={`px-3 py-1 text-sm rounded-lg border transition-colors ${
isViewingCurrentMonth
? "border-slate-800 text-slate-500 opacity-50 cursor-not-allowed"
: "border-accent/50 bg-accent/10 text-accent hover:bg-accent/20 hover:border-accent"
}`}
>
Today
</button>
</div>
<button
type="button"
onClick={goToNextMonth}
aria-label="Next month"
className="p-2 rounded-lg hover:bg-white/5 transition-colors"
>
<ChevronRight className="h-5 w-5" />
Expand All @@ -232,22 +247,38 @@ export function CalendarView({
{calendarDays.map((day, index) => (
<div
key={index}
className={`bg-slate-900/30 p-2 min-h-[100px] ${
data-testid={day.isToday ? "calendar-today-cell" : undefined}
aria-current={day.isToday ? "date" : undefined}
className={`p-2 min-h-[100px] transition-colors ${
!day.isCurrentMonth ? "opacity-50" : ""
} ${day.isToday ? "ring-1 ring-accent" : ""}`}
} ${
day.isToday
? "bg-accent/10 dark:bg-accent/15 ring-2 ring-inset ring-accent"
: "bg-slate-900/30"
}`}
>
<div className="flex items-center justify-between mb-2">
<span
className={`text-sm ${
day.isToday
? "text-accent font-bold"
: day.isCurrentMonth
? "text-slate-300"
: "text-slate-600"
}`}
>
{day.date.getDate()}
</span>
<div className="flex items-center gap-1.5">
<span
className={`text-sm ${
day.isToday
? "text-accent font-bold"
: day.isCurrentMonth
? "text-slate-700 dark:text-slate-300"
: "text-slate-400 dark:text-slate-600"
}`}
>
{day.date.getDate()}
</span>
{day.isToday && (
<span
data-testid="calendar-today-badge"
className="rounded-full bg-accent/20 px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-accent"
>
Today
</span>
)}
</div>
{day.streams.length > 0 && (
<span className="text-xs text-slate-500">
{day.streams.length}
Expand Down
Loading