Skip to content

Commit 89f70e2

Browse files
committed
refactor(hooks): update session handling and transform logic in useFetchTalks and useFetchLiveView
1 parent b07fd76 commit 89f70e2

File tree

6 files changed

+195
-86
lines changed

6 files changed

+195
-86
lines changed

src/hooks/useFetchTalks.test.tsx

Lines changed: 127 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import { IGroup } from "../types/sessions";
1010
import {
1111
createMockAxiosResponse,
1212
createMockGroup,
13-
createMockLiveview,
1413
createMockSession,
1514
getQueryClientWrapper,
1615
SESSION_URLS,
@@ -156,6 +155,60 @@ describe("useFetchTalksById", () => {
156155
const expectedData = mockData[0].sessions[0];
157156
expect(result.current.data).toEqual(expectedData);
158157
});
158+
159+
it("should use 2024 URL when '2024' is provided", async () => {
160+
const mockSession = createMockSession({
161+
track: "",
162+
description: "",
163+
startsAt: "2024-01-01T00:00:00",
164+
});
165+
const mockData: IGroup[] = [
166+
createMockGroup({
167+
groupName: "test ",
168+
sessions: [mockSession],
169+
}),
170+
];
171+
const payload = createMockAxiosResponse(mockData);
172+
173+
mockedAxios.get.mockResolvedValue(payload);
174+
175+
const { wrapper } = getQueryClientWrapper();
176+
const { result } = renderHook(() => useFetchTalksById("123", "2024"), {
177+
wrapper,
178+
});
179+
180+
await waitFor(() => result.current.isSuccess);
181+
await waitFor(() => !result.current.isLoading);
182+
183+
expect(mockedAxios.get).toHaveBeenCalledWith(SESSION_URLS["2024"]);
184+
const expectedData = mockData[0].sessions[0];
185+
expect(result.current.data).toEqual(expectedData);
186+
});
187+
188+
it("should use custom URL when a URL is provided", async () => {
189+
const mockSession = createMockSession();
190+
const mockData: IGroup[] = [
191+
createMockGroup({
192+
sessions: [mockSession],
193+
}),
194+
];
195+
const payload = createMockAxiosResponse(mockData);
196+
197+
mockedAxios.get.mockResolvedValue(payload);
198+
199+
const { wrapper } = getQueryClientWrapper();
200+
const customUrl = "https://example.com/api/sessions";
201+
const { result } = renderHook(() => useFetchTalksById("123", customUrl), {
202+
wrapper,
203+
});
204+
205+
await waitFor(() => result.current.isSuccess);
206+
await waitFor(() => !result.current.isLoading);
207+
208+
expect(mockedAxios.get).toHaveBeenCalledWith(customUrl);
209+
const expectedData = mockData[0].sessions[0];
210+
expect(result.current.data).toEqual(expectedData);
211+
});
159212
});
160213

161214
describe("useFetchLiveView", () => {
@@ -164,8 +217,13 @@ describe("useFetchLiveView", () => {
164217
});
165218

166219
it("should use default URL when no parameter is provided", async () => {
167-
const mockData = createMockLiveview();
168-
const payload = createMockAxiosResponse([mockData]);
220+
const mockSession = createMockSession();
221+
const mockData: IGroup[] = [
222+
createMockGroup({
223+
sessions: [mockSession],
224+
}),
225+
];
226+
const payload = createMockAxiosResponse(mockData);
169227

170228
mockedAxios.get.mockResolvedValue(payload);
171229

@@ -178,12 +236,50 @@ describe("useFetchLiveView", () => {
178236
await waitFor(() => !result.current.isLoading);
179237

180238
expect(mockedAxios.get).toHaveBeenCalledWith(SESSION_URLS.DEFAULT);
181-
expect(result.current.data).toEqual(payload.data[0]);
239+
expect(result.current.data).toEqual([mockSession]);
240+
});
241+
242+
it("should use 2023 URL when '2023' is provided", async () => {
243+
const mockSession = createMockSession({
244+
track: "",
245+
description: "",
246+
startsAt: "2023-01-01T00:00:00",
247+
});
248+
const mockData: IGroup[] = [
249+
createMockGroup({
250+
groupName: "test ",
251+
sessions: [mockSession],
252+
}),
253+
];
254+
const payload = createMockAxiosResponse(mockData);
255+
256+
mockedAxios.get.mockResolvedValue(payload);
257+
258+
const { wrapper } = getQueryClientWrapper();
259+
const { result } = renderHook(() => useFetchLiveView("2023"), {
260+
wrapper,
261+
});
262+
263+
await waitFor(() => result.current.isSuccess);
264+
await waitFor(() => !result.current.isLoading);
265+
266+
expect(mockedAxios.get).toHaveBeenCalledWith(SESSION_URLS["2023"]);
267+
expect(result.current.data).toEqual([mockSession]);
182268
});
183269

184270
it("should use 2024 URL when '2024' is provided", async () => {
185-
const mockData = createMockLiveview();
186-
const payload = createMockAxiosResponse([mockData]);
271+
const mockSession = createMockSession({
272+
track: "",
273+
description: "",
274+
startsAt: "2024-01-01T00:00:00",
275+
});
276+
const mockData: IGroup[] = [
277+
createMockGroup({
278+
groupName: "test ",
279+
sessions: [mockSession],
280+
}),
281+
];
282+
const payload = createMockAxiosResponse(mockData);
187283

188284
mockedAxios.get.mockResolvedValue(payload);
189285

@@ -196,6 +292,30 @@ describe("useFetchLiveView", () => {
196292
await waitFor(() => !result.current.isLoading);
197293

198294
expect(mockedAxios.get).toHaveBeenCalledWith(SESSION_URLS["2024"]);
199-
expect(result.current.data).toEqual(payload.data[0]);
295+
expect(result.current.data).toEqual([mockSession]);
296+
});
297+
298+
it("should use custom URL when a URL is provided", async () => {
299+
const mockSession = createMockSession();
300+
const mockData: IGroup[] = [
301+
createMockGroup({
302+
sessions: [mockSession],
303+
}),
304+
];
305+
const payload = createMockAxiosResponse(mockData);
306+
307+
mockedAxios.get.mockResolvedValue(payload);
308+
309+
const { wrapper } = getQueryClientWrapper();
310+
const customUrl = "https://example.com/api/sessions";
311+
const { result } = renderHook(() => useFetchLiveView(customUrl), {
312+
wrapper,
313+
});
314+
315+
await waitFor(() => result.current.isSuccess);
316+
await waitFor(() => !result.current.isLoading);
317+
318+
expect(mockedAxios.get).toHaveBeenCalledWith(customUrl);
319+
expect(result.current.data).toEqual([mockSession]);
200320
});
201321
});

src/hooks/useFetchTalks.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { useQuery, UseQueryResult } from "react-query";
22
import axios from "axios";
3-
import { Liveview } from "../views/Talks/liveView.types";
4-
import { IGroup, Session } from "../types/sessions";
3+
import { UngroupedSession } from "@views/Talks/liveView.types";
4+
// @ts-expect-error some quirky import
5+
import { IGroup, Session } from "@types/sessions";
56

67
const URLS = {
78
default: "https://sessionize.com/api/v2/xhudniix/view/Sessions",
@@ -41,7 +42,8 @@ const getUrl = (urlOrYear?: string): string => {
4142
const useFetchTalksBase = <T>(
4243
queryKey: string,
4344
urlOrYear?: string,
44-
dataTransformer: (data: any) => T = (data) => data,
45+
dataTransformer: (data: IGroup[]) => T = (data: IGroup[]) =>
46+
data as unknown as T,
4547
): UseQueryResult<T> => {
4648
const url = getUrl(urlOrYear);
4749

@@ -59,7 +61,7 @@ export const useFetchTalksById = (
5961
id: string,
6062
urlOrYear?: string,
6163
): UseQueryResult<Session> => {
62-
return useFetchTalksBase<Session>("talks", urlOrYear, (data: any[]) => {
64+
return useFetchTalksBase<Session>("talks", urlOrYear, (data: IGroup[]) => {
6365
const sessions = data
6466
.map((track: IGroup) => track.sessions)
6567
.flat(1)
@@ -70,8 +72,11 @@ export const useFetchTalksById = (
7072

7173
export const useFetchLiveView = (
7274
urlOrYear?: string,
73-
): UseQueryResult<Liveview> => {
74-
return useFetchTalksBase<Liveview>("api-talks", urlOrYear, (data) =>
75-
data.at(0),
75+
): UseQueryResult<UngroupedSession[]> => {
76+
return useFetchTalksBase<Session[]>(
77+
"api-talks",
78+
urlOrYear,
79+
(data): UngroupedSession[] =>
80+
data.map((track: IGroup) => track.sessions).flat(1),
7681
);
7782
};

src/utils/testing/testUtils.tsx

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@ import React, { FC } from "react";
22
import { QueryClient, QueryClientProvider } from "react-query";
33
import { render, RenderOptions, RenderResult } from "@testing-library/react";
44
import { AxiosHeaders, AxiosResponse } from "axios";
5+
import { MemoryRouter } from "react-router";
56

6-
// Re-export everything from testing-library
77
export * from "@testing-library/react";
88

9-
// Create a custom render function that includes the QueryClientProvider
109
export function renderWithQueryClient(
1110
ui: React.ReactElement,
1211
options?: Omit<RenderOptions, "wrapper">,
@@ -26,7 +25,27 @@ export function renderWithQueryClient(
2625
return render(ui, { wrapper, ...options });
2726
}
2827

29-
// Create a function to get a QueryClient and wrapper for use with renderHook
28+
export function renderWithQueryClientAndRouter(
29+
ui: React.ReactElement,
30+
options?: Omit<RenderOptions, "wrapper">,
31+
): RenderResult {
32+
const queryClient = new QueryClient({
33+
defaultOptions: {
34+
queries: {
35+
retry: false,
36+
},
37+
},
38+
});
39+
40+
const wrapper: FC<React.PropsWithChildren<object>> = ({ children }) => (
41+
<QueryClientProvider client={queryClient}>
42+
<MemoryRouter initialEntries={["/"]}>{children}</MemoryRouter>
43+
</QueryClientProvider>
44+
);
45+
46+
return render(ui, { wrapper, ...options });
47+
}
48+
3049
export function getQueryClientWrapper() {
3150
const queryClient = new QueryClient({
3251
defaultOptions: {
@@ -43,7 +62,6 @@ export function getQueryClientWrapper() {
4362
return { queryClient, wrapper };
4463
}
4564

46-
// Create a function to create a mock axios response
4765
export function createMockAxiosResponse<T>(data: T): AxiosResponse<T> {
4866
const axiosHeaders = new AxiosHeaders();
4967
return {
@@ -57,21 +75,18 @@ export function createMockAxiosResponse<T>(data: T): AxiosResponse<T> {
5775
};
5876
}
5977

60-
// Session URLs
6178
export const SESSION_URLS = {
6279
DEFAULT: "https://sessionize.com/api/v2/xhudniix/view/Sessions",
6380
"2023": "https://sessionize.com/api/v2/ttsitynd/view/Sessions",
6481
"2024": "https://sessionize.com/api/v2/teq4asez/view/Sessions",
6582
};
6683

67-
// Speaker URLs
6884
export const SPEAKER_URLS = {
6985
DEFAULT: "https://sessionize.com/api/v2/xhudniix/view/Speakers",
7086
"2023": "https://sessionize.com/api/v2/ttsitynd/view/Speakers",
7187
"2024": "https://sessionize.com/api/v2/teq4asez/view/Speakers",
7288
};
7389

74-
// Mock data factories
7590
export const createMockSpeaker = (overrides = {}) => ({
7691
id: "1",
7792
fullName: "John Smith",
@@ -137,11 +152,3 @@ export const createMockGroup = (overrides = {}) => ({
137152
sessions: [],
138153
...overrides,
139154
});
140-
141-
export const createMockLiveview = (overrides = {}) => ({
142-
groupID: null,
143-
groupName: "",
144-
isDefault: false,
145-
sessions: [],
146-
...overrides,
147-
});

src/views/Talks/LiveView.test.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import LiveView from "./LiveView";
22
import React from "react";
3-
import { renderWithQueryClient, screen } from "../../utils/testing/testUtils";
3+
import {
4+
renderWithQueryClientAndRouter,
5+
screen,
6+
} from "../../utils/testing/testUtils";
47

58
describe("Live view component", () => {
69
it("renders without crashing", () => {
7-
renderWithQueryClient(<LiveView />);
10+
renderWithQueryClientAndRouter(<LiveView />);
811
const titleElement = screen.getByText(/Live Schedule/);
912
expect(titleElement).toBeInTheDocument();
1013
});

src/views/Talks/LiveView.tsx

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ import Loading from "@components/Loading/Loading";
44
import { UngroupedSession } from "./liveView.types";
55
import conference from "@data/2025.json";
66
import { TalkCard } from "./components/TalkCard";
7-
import { StyledMain } from "./Talks.style";
7+
import { StyledAgenda, StyledMain } from "./Talks.style";
88
import { talkCardAdapter } from "./TalkCardAdapter";
99
import { useSentryErrorReport } from "@hooks/useSentryErrorReport";
1010
import { useDateInterval } from "@hooks/useDateInterval";
1111
import { isWithinInterval } from "date-fns";
12+
import { Link } from "react-router";
13+
import { ROUTE_SCHEDULE } from "@constants/routes";
1214

1315
const LiveView: FC<React.PropsWithChildren<unknown>> = () => {
1416
const { isLoading, error, data } = useFetchLiveView();
@@ -22,7 +24,7 @@ const LiveView: FC<React.PropsWithChildren<unknown>> = () => {
2224
);
2325

2426
const filteredTalks = useMemo(() => {
25-
return data?.sessions?.filter(getPredicate());
27+
return data?.filter(getPredicate());
2628
}, [data, getPredicate]);
2729

2830
useEffect(() => {
@@ -44,10 +46,23 @@ const LiveView: FC<React.PropsWithChildren<unknown>> = () => {
4446

4547
{isLoading && <Loading />}
4648
<article>Live Schedule</article>
49+
4750
{!isConferenceActive && <h4>The live schedule is not ready yet</h4>}
48-
{filteredTalks?.map((session) => (
49-
<TalkCard key={session.id} {...talkCardAdapter(session)} />
50-
))}
51+
<StyledAgenda>
52+
{filteredTalks?.map((session) => (
53+
<TalkCard key={session.id} {...talkCardAdapter(session)} />
54+
))}
55+
</StyledAgenda>
56+
<Link
57+
to={ROUTE_SCHEDULE}
58+
style={{
59+
textDecoration: "none",
60+
fontWeight: "bold",
61+
margin: "0.5rem",
62+
}}
63+
>
64+
📅 Back to schedule
65+
</Link>
5166
</StyledMain>
5267
);
5368
};

0 commit comments

Comments
 (0)