-
Notifications
You must be signed in to change notification settings - Fork 53
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(zones): fetch zones using react-query MAASENG-3404 (#5485)
- Enable React Query for managing zone-related data fetching and caching - Add `WebSocketEndpoints` detailing allowed WebSocket endpoint models and methods - Remove unused imports and code related to fetching zones - Refactor code to use new `useZoneById` hook for fetching zones by ID - Update testing utilities to support React Query and WebSocket testing - Add new helper functions in testing/utils for setting up initial state and query data - Modify `renderWithBrowserRouter` to return `store` and `queryClient` for more concise tests
- Loading branch information
1 parent
721878e
commit 9215ed6
Showing
98 changed files
with
1,654 additions
and
1,853 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,46 +1,50 @@ | ||
import type { UseQueryResult } from "@tanstack/react-query"; | ||
import { selectItemsCount, selectById } from "./utils"; | ||
|
||
import { useItemsCount } from "./utils"; | ||
describe("selectItemsCount", () => { | ||
it("should return 0 for undefined input", () => { | ||
const count = selectItemsCount()(undefined); | ||
expect(count).toBe(0); | ||
}); | ||
|
||
import { renderHook } from "@/testing/utils"; | ||
it("should return the correct count for a non-empty array", () => { | ||
const data = [1, 2, 3, 4, 5]; | ||
const count = selectItemsCount()(data); | ||
expect(count).toBe(5); | ||
}); | ||
|
||
it("should return 0 when data is undefined", () => { | ||
const mockUseItems = vi.fn( | ||
() => ({ data: undefined }) as UseQueryResult<any[], unknown> | ||
); | ||
const { result } = renderHook(() => useItemsCount(mockUseItems)); | ||
expect(result.current).toBe(0); | ||
it("should return 0 for an empty array", () => { | ||
const data: number[] = []; | ||
const count = selectItemsCount()(data); | ||
expect(count).toBe(0); | ||
}); | ||
}); | ||
|
||
it("should return the correct count when data is available", () => { | ||
const mockData = [1, 2, 3, 4, 5]; | ||
const mockUseItems = vi.fn( | ||
() => ({ data: mockData }) as UseQueryResult<number[], unknown> | ||
); | ||
const { result } = renderHook(() => useItemsCount(mockUseItems)); | ||
expect(result.current).toBe(5); | ||
}); | ||
describe("selectById", () => { | ||
const testData = [ | ||
{ id: 1, name: "Item 1" }, | ||
{ id: 2, name: "Item 2" }, | ||
{ id: 3, name: "Item 3" }, | ||
{ id: null, name: "Null ID Item" }, | ||
]; | ||
|
||
it("should return 0 when data is an empty array", () => { | ||
const mockUseItems = vi.fn(); | ||
mockUseItems.mockReturnValueOnce({ data: [] } as UseQueryResult<[], unknown>); | ||
const { result } = renderHook(() => useItemsCount(mockUseItems)); | ||
expect(result.current).toBe(0); | ||
}); | ||
it("should return the correct item when given a valid ID", () => { | ||
const item = selectById(2)(testData); | ||
expect(item).toEqual({ id: 2, name: "Item 2" }); | ||
}); | ||
|
||
it("should return null when given an ID that does not exist", () => { | ||
const item = selectById(4)(testData); | ||
expect(item).toBeNull(); | ||
}); | ||
|
||
it("should return the correct item when given a null ID", () => { | ||
const item = selectById(null)(testData); | ||
expect(item).toEqual({ id: null, name: "Null ID Item" }); | ||
}); | ||
|
||
it("should update count when data changes", () => { | ||
const mockUseItems = vi.fn(); | ||
mockUseItems.mockReturnValueOnce({ data: [1, 2, 3] } as UseQueryResult< | ||
number[], | ||
unknown | ||
>); | ||
const { result, rerender } = renderHook(() => useItemsCount(mockUseItems)); | ||
expect(result.current).toBe(3); | ||
|
||
mockUseItems.mockReturnValueOnce({ data: [1, 2, 3, 4] } as UseQueryResult< | ||
number[], | ||
unknown | ||
>); | ||
rerender(); | ||
expect(result.current).toBe(4); | ||
it("should return null when given a null ID and no matching item exists", () => { | ||
const dataWithoutNullId = testData.filter((item) => item.id !== null); | ||
const item = selectById(null)(dataWithoutNullId); | ||
expect(item).toBeNull(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,20 @@ | ||
import { useMemo } from "react"; | ||
|
||
import type { UseQueryResult } from "@tanstack/react-query"; | ||
|
||
type QueryHook<T> = () => UseQueryResult<T[], unknown>; | ||
/** | ||
* Selector function to get the count of items in an array. | ||
* @template T | ||
* @returns {function(T[] | undefined): number} A function that takes an array of items and returns the count of items. | ||
*/ | ||
export const selectItemsCount = <T>() => { | ||
return (data: T[] | undefined) => data?.length ?? 0; | ||
}; | ||
|
||
export const useItemsCount = <T>(useItems: QueryHook<T>) => { | ||
const { data } = useItems(); | ||
return useMemo(() => data?.length ?? 0, [data]); | ||
/** | ||
* Selector function to find an item by its ID. | ||
* @template T | ||
* @param {number | null} id - The ID of the item to find. | ||
* @returns {function(T[]): T | undefined} A function that takes an array of items and returns the item with the specified ID. | ||
*/ | ||
export const selectById = <T extends { id: number | null }>( | ||
id: number | null | ||
) => { | ||
return (data: T[]) => data.find((item) => item.id === id) || null; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,36 +1,70 @@ | ||
import type { JsonBodyType } from "msw"; | ||
import type { UseQueryResult } from "@tanstack/react-query"; | ||
import { type JsonBodyType } from "msw"; | ||
|
||
import { useZonesCount } from "./zones"; | ||
import { useZoneCount, useZoneById, useZones } from "./zones"; | ||
|
||
import { getFullApiUrl } from "@/app/api/base"; | ||
import * as factory from "@/testing/factories"; | ||
import { | ||
renderHookWithQueryClient, | ||
setupMockServer, | ||
waitFor, | ||
} from "@/testing/utils"; | ||
|
||
const { server, http, HttpResponse } = setupMockServer(); | ||
const { mockGet } = setupMockServer(); | ||
|
||
const setupZonesTest = (mockData: JsonBodyType) => { | ||
server.use( | ||
http.get(getFullApiUrl("zones"), () => HttpResponse.json(mockData)) | ||
); | ||
return renderHookWithQueryClient(() => useZonesCount()); | ||
const setupTest = ( | ||
hook: () => ReturnType< | ||
typeof useZoneCount | typeof useZoneById | typeof useZones | ||
>, | ||
mockData: JsonBodyType | ||
) => { | ||
mockGet("zones", mockData); | ||
return renderHookWithQueryClient(() => hook()) as { | ||
result: { current: UseQueryResult<number> }; | ||
}; | ||
}; | ||
|
||
it("should return 0 when zones data is undefined", async () => { | ||
const { result } = setupZonesTest(null); | ||
await waitFor(() => expect(result.current).toBe(0)); | ||
describe("useZones", () => { | ||
it("should return zones data when query succeeds", async () => { | ||
const mockZones = [factory.zone(), factory.zone()]; | ||
const { result } = setupTest(useZones, mockZones); | ||
|
||
await waitFor(() => expect(result.current.isSuccess).toBe(true)); | ||
expect(result.current.data).toEqual(mockZones); | ||
}); | ||
}); | ||
|
||
it("should return the correct count when zones data is available", async () => { | ||
const mockZonesData = [factory.zone(), factory.zone(), factory.zone()]; | ||
const { result } = setupZonesTest(mockZonesData); | ||
await waitFor(() => expect(result.current).toBe(3)); | ||
describe("useZoneById", () => { | ||
it("should return specific zone when query succeeds", async () => { | ||
const mockZones = [factory.zone({ id: 1 }), factory.zone({ id: 2 })]; | ||
const { result } = setupTest(() => useZoneById(1), mockZones); | ||
|
||
await waitFor(() => expect(result.current.isSuccess).toBe(true)); | ||
expect(result.current.data).toEqual(mockZones[0]); | ||
}); | ||
|
||
it("should return null when zone is not found", async () => { | ||
const mockZones = [factory.zone({ id: 1 })]; | ||
const { result } = setupTest(() => useZoneById(2), mockZones); | ||
|
||
await waitFor(() => expect(result.current.isSuccess).toBe(true)); | ||
expect(result.current.data).toBeNull(); | ||
}); | ||
}); | ||
|
||
it("should return 0 when zones data is an empty array", async () => { | ||
const { result } = setupZonesTest([]); | ||
await waitFor(() => expect(result.current).toBe(0)); | ||
describe("useZoneCount", () => { | ||
it("should return correct count when query succeeds", async () => { | ||
const mockZones = [factory.zone(), factory.zone(), factory.zone()]; | ||
const { result } = setupTest(useZoneCount, mockZones); | ||
|
||
await waitFor(() => expect(result.current.isSuccess).toBe(true)); | ||
expect(result.current.data).toBe(3); | ||
}); | ||
|
||
it("should return 0 when zones array is empty", async () => { | ||
const { result } = setupTest(useZoneCount, []); | ||
|
||
await waitFor(() => expect(result.current.isSuccess).toBe(true)); | ||
expect(result.current.data).toBe(0); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,19 @@ | ||
import { selectById } from "./utils"; | ||
|
||
import { fetchZones } from "@/app/api/endpoints"; | ||
import { useWebsocketAwareQuery } from "@/app/api/query/base"; | ||
import { useItemsCount } from "@/app/api/query/utils"; | ||
import type { Zone, ZonePK } from "@/app/store/zone/types"; | ||
|
||
export const useZones = () => { | ||
return useWebsocketAwareQuery(["zones"], fetchZones); | ||
}; | ||
|
||
export const useZonesCount = () => useItemsCount(useZones); | ||
export const useZoneCount = () => | ||
useWebsocketAwareQuery<Zone[], Zone[], number>(["zones"], fetchZones, { | ||
select: (data) => data?.length ?? 0, | ||
}); | ||
|
||
export const useZoneById = (id?: ZonePK | null) => | ||
useWebsocketAwareQuery(["zones"], fetchZones, { | ||
select: selectById<Zone>(id ?? null), | ||
}); |
Oops, something went wrong.