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
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import type { Database } from "@/integrations/supabase/types";

export type Stage = Database["public"]["Tables"]["stages"]["Row"];

// Query key factory
export const stagesKeys = {
all: ["stages"] as const,
byEdition: (editionId: string) => ["stages", { editionId }] as const,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useQuery } from "@tanstack/react-query";
import { queryOptions, useQuery } from "@tanstack/react-query";
import { supabase } from "@/integrations/supabase/client";
import { Stage, stagesKeys } from "./types";
import type { Stage } from "./types";
import { stagesKeys } from "./types";

async function fetchStage(stageId: string): Promise<Stage | null> {
export async function fetchStage(stageId: string): Promise<Stage | null> {
const { data, error } = await supabase
.from("stages")
.select("*")
Expand All @@ -12,7 +13,6 @@ async function fetchStage(stageId: string): Promise<Stage | null> {

if (error) {
if (error.code === "PGRST116") {
// No rows returned
return null;
}
throw new Error("Failed to load stage");
Expand All @@ -21,10 +21,16 @@ async function fetchStage(stageId: string): Promise<Stage | null> {
return data;
}

export function stageQuery(stageId: string) {
return queryOptions({
queryKey: stagesKeys.byId(stageId),
queryFn: () => fetchStage(stageId),
});
}

export function useStageQuery(stageId: string | undefined | null) {
return useQuery({
queryKey: stagesKeys.byId(stageId || ""),
queryFn: () => fetchStage(stageId!),
...stageQuery(stageId ?? ""),
enabled: !!stageId,
});
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { useQuery } from "@tanstack/react-query";
import { queryOptions, useQuery } from "@tanstack/react-query";
import { supabase } from "@/integrations/supabase/client";
import { Stage, stagesKeys } from "./types";
import type { Stage } from "./types";
import { stagesKeys } from "./types";
import { sortStagesByOrder } from "@/lib/stageUtils";

async function fetchStagesByEdition(editionId: string): Promise<Stage[]> {
export async function fetchStagesByEdition(
editionId: string,
): Promise<Stage[]> {
const { data, error } = await supabase
.from("stages")
.select("*")
Expand All @@ -15,14 +18,19 @@ async function fetchStagesByEdition(editionId: string): Promise<Stage[]> {
throw new Error("Failed to load stages for edition");
}

// Apply custom sorting using shared utility
return sortStagesByOrder(data || []);
}

export function stagesByEditionQuery(editionId: string) {
return queryOptions({
queryKey: stagesKeys.byEdition(editionId),
queryFn: () => fetchStagesByEdition(editionId),
});
}

export function useStagesByEditionQuery(editionId: string | undefined) {
return useQuery({
queryKey: stagesKeys.byEdition(editionId || ""),
queryFn: () => fetchStagesByEdition(editionId!),
...stagesByEditionQuery(editionId ?? ""),
enabled: !!editionId,
});
}
4 changes: 2 additions & 2 deletions src/components/Admin/ScheduleImport/ReviewStage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import {
} from "@/services/scheduleImport/types";
import { artistsKeys } from "@/hooks/queries/artists/useArtists";
import { setsKeys } from "@/api/sets/types";
import { stagesKeys } from "@/hooks/queries/stages/types";
import { useStagesByEditionQuery } from "@/hooks/queries/stages/useStagesByEdition";
import { stagesKeys } from "@/api/stages/types";
import { useStagesByEditionQuery } from "@/api/stages/useStagesByEdition";
import type { RevealLevel } from "@/lib/scheduleReveal";
import { DiffReviewStep } from "./DiffReviewStep";

Expand Down
4 changes: 2 additions & 2 deletions src/components/StagePin.test.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { describe, expect, it, vi, beforeEach } from "vitest";
import { render, screen } from "@testing-library/react";
import { StagePin } from "./StagePin";
import * as useStageQueryModule from "@/hooks/queries/stages/useStageQuery";
import * as useStageQueryModule from "@/api/stages/useStageQuery";

type StageQueryResult = ReturnType<typeof useStageQueryModule.useStageQuery>;

vi.mock("@/hooks/queries/stages/useStageQuery");
vi.mock("@/api/stages/useStageQuery");
Comment on lines +4 to +8

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Test mocks usestagequery 📜 Skill insight ▣ Testability

StagePin.test.tsx mocks the internal hook module @/api/stages/useStageQuery, which violates the
requirement to only mock at external system boundaries. This makes the test couple to internal
module structure and breaks under internal refactors.
Agent Prompt
## Issue description
The test mocks an internal collaborator (`@/api/stages/useStageQuery`) via `vi.mock(...)`, which violates the rule that mocks must be used only at system boundaries.

## Issue Context
Instead of mocking `useStageQuery`, render `StagePin` with a real `QueryClientProvider` and seed the query cache for `stagesKeys.byId(stageId)` (or mock the external boundary, e.g., Supabase network calls, via a boundary-level approach).

## Fix Focus Areas
- src/components/StagePin.test.tsx[4-8]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


describe("StagePin", () => {
beforeEach(() => {
Expand Down
2 changes: 1 addition & 1 deletion src/components/StagePin.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useStageQuery } from "@/hooks/queries/stages/useStageQuery";
import { useStageQuery } from "@/api/stages/useStageQuery";
import { MapPin } from "lucide-react";

export function StagePin({ stageId }: { stageId: string | null }) {
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/useScheduleData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useMemo } from "react";
import { formatDateTime } from "@/lib/timeUtils";
import { format, startOfDay } from "date-fns";
import type { FestivalSet } from "@/api/sets/types";
import { Stage } from "./queries/stages/types";
import type { Stage } from "@/api/stages/types";
import { sortStagesByOrder } from "@/lib/stageUtils";

export interface ScheduleDay {
Expand Down
2 changes: 1 addition & 1 deletion src/lib/stageUtils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Stage } from "@/hooks/queries/stages/types";
import type { Stage } from "@/api/stages/types";

/**
* Sorts stages by priority: stages with order > 0 come first (sorted by order),
Expand Down
2 changes: 1 addition & 1 deletion src/lib/timelineCalculator.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { differenceInMinutes } from "date-fns";
import type { ScheduleSet, ScheduleDay } from "@/hooks/useScheduleData";
import type { Stage } from "@/hooks/queries/stages/types";
import type { Stage } from "@/api/stages/types";
import { sortStagesByOrder } from "@/lib/stageUtils";

export interface HorizontalTimelineSet extends ScheduleSet {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { formatDayOnly, formatTimeRange } from "@/lib/timeUtils";
import { GenreBadge } from "@/components/GenreBadge";
import { StageBadge } from "@/components/StageBadge";
import { useFestivalSet } from "../FestivalSetContext";
import { useStageQuery } from "@/hooks/queries/stages/useStageQuery";
import { useStageQuery } from "@/api/stages/useStageQuery";
import { useScheduleReveal } from "@/hooks/useScheduleReveal";

export function SetMetadata() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Button } from "@/components/ui/button";
import type { FilterSortState } from "@/hooks/useUrlState";
import { useStagesByEditionQuery } from "@/hooks/queries/stages/useStagesByEdition";
import { useStagesByEditionQuery } from "@/api/stages/useStagesByEdition";
import { useScheduleReveal } from "@/hooks/useScheduleReveal";

interface DesktopFiltersProps {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
SelectValue,
} from "@/components/ui/select";
import type { FilterSortState } from "@/hooks/useUrlState";
import { useStagesByEditionQuery } from "@/hooks/queries/stages/useStagesByEdition";
import { useStagesByEditionQuery } from "@/api/stages/useStagesByEdition";
import { useScheduleReveal } from "@/hooks/useScheduleReveal";

interface MobileFiltersProps {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Button } from "@/components/ui/button";
import { MapPin } from "lucide-react";
import { useStagesByEditionQuery } from "@/hooks/queries/stages/useStagesByEdition";
import { useStagesByEditionQuery } from "@/api/stages/useStagesByEdition";
import { useFestivalEdition } from "@/contexts/FestivalEditionContext";

interface StageFilterButtonsProps {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { useFestivalEdition } from "@/contexts/FestivalEditionContext";
import { useSetsByEditionQuery as useEditionSetsQuery } from "@/api/sets/useSetsByEdition";
import { useTimelineUrlState } from "@/hooks/useTimelineUrlState";
import { format } from "date-fns";
import { useStagesByEditionQuery } from "@/hooks/queries/stages/useStagesByEdition";
import { useStagesByEditionQuery } from "@/api/stages/useStagesByEdition";
import { useScheduleReveal } from "@/hooks/useScheduleReveal";
import { ScheduleNotRevealedPlaceholder } from "../ScheduleNotRevealedPlaceholder";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { isSameDay, format } from "date-fns";
import { TimeSlotGroup } from "./TimeSlotGroup";
import type { ScheduleSet } from "@/hooks/useScheduleData";
import { useTimelineUrlState } from "@/hooks/useTimelineUrlState";
import { useStagesByEditionQuery } from "@/hooks/queries/stages/useStagesByEdition";
import { useStagesByEditionQuery } from "@/api/stages/useStagesByEdition";
import { useScheduleReveal } from "@/hooks/useScheduleReveal";
import { ScheduleNotRevealedPlaceholder } from "../ScheduleNotRevealedPlaceholder";

Expand Down
2 changes: 1 addition & 1 deletion src/pages/ExploreSetPage/SetExploreCard/SetCardHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Badge } from "@/components/ui/badge";
import { Clock } from "lucide-react";
import { StageBadge } from "@/components/StageBadge";
import { useStageQuery } from "@/hooks/queries/stages/useStageQuery";
import { useStageQuery } from "@/api/stages/useStageQuery";
import { useScheduleReveal } from "@/hooks/useScheduleReveal";

interface SetCardHeaderProps {
Expand Down
2 changes: 1 addition & 1 deletion src/pages/admin/festivals/SetsTable.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useStagesByEditionQuery } from "@/hooks/queries/stages/useStagesByEdition";
import { useStagesByEditionQuery } from "@/api/stages/useStagesByEdition";
import {
Table,
TableBody,
Expand Down
6 changes: 3 additions & 3 deletions src/pages/admin/festivals/StageManagement.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useState } from "react";
import { useParams } from "@tanstack/react-router";
import { useStagesByEditionQuery } from "@/hooks/queries/stages/useStagesByEdition";
import { useDeleteStageMutation } from "@/hooks/queries/stages/useDeleteStage";
import { Stage } from "@/hooks/queries/stages/types";
import { useStagesByEditionQuery } from "@/api/stages/useStagesByEdition";
import { useDeleteStageMutation } from "@/api/stages/useDeleteStage";
import type { Stage } from "@/api/stages/types";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Loader2, MapPin } from "lucide-react";
import { StagesTable } from "./StageManagement/StagesTable";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useState } from "react";
import { useCreateStageMutation } from "@/hooks/queries/stages/useCreateStage";
import { useCreateStageMutation } from "@/api/stages/useCreateStage";
import {
Dialog,
DialogContent,
Expand Down
4 changes: 2 additions & 2 deletions src/pages/admin/festivals/StageManagement/EditStageDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useUpdateStageMutation } from "@/hooks/queries/stages/useUpdateStage";
import { Stage } from "@/hooks/queries/stages/types";
import { useUpdateStageMutation } from "@/api/stages/useUpdateStage";
import type { Stage } from "@/api/stages/types";
import {
Dialog,
DialogContent,
Expand Down
2 changes: 1 addition & 1 deletion src/pages/admin/festivals/StageManagement/StagesTable.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Stage } from "@/hooks/queries/stages/types";
import type { Stage } from "@/api/stages/types";
import {
Table,
TableBody,
Expand Down
2 changes: 1 addition & 1 deletion src/pages/admin/festivals/StageSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { useStagesByEditionQuery } from "@/hooks/queries/stages/useStagesByEdition";
import { useStagesByEditionQuery } from "@/api/stages/useStagesByEdition";

export function StageSelector({
value,
Expand Down
Loading