From 6cfc2822236c55cdb8276b3ef40de56e5b063fa3 Mon Sep 17 00:00:00 2001 From: sounmind Date: Sat, 2 Nov 2024 23:01:40 -0400 Subject: [PATCH] feat: implement useMembers hook --- src/hooks/useMemberInfo.test.ts | 71 +++++++++++++++++++++++++++++++++ src/hooks/useMemberInfo.ts | 53 ++++++++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 src/hooks/useMemberInfo.test.ts create mode 100644 src/hooks/useMemberInfo.ts diff --git a/src/hooks/useMemberInfo.test.ts b/src/hooks/useMemberInfo.test.ts new file mode 100644 index 00000000..42b62d66 --- /dev/null +++ b/src/hooks/useMemberInfo.test.ts @@ -0,0 +1,71 @@ +import { faker } from "@faker-js/faker"; +import { renderHook, waitFor } from "@testing-library/react"; +import { expect, test, vi } from "vitest"; +import useMemberInfo, { type Member } from "./useMemberInfo"; + +test("fetch member info successfully and update state", async () => { + // Generate fake member data using faker + const mockMembers: Member[] = Array.from({ length: 5 }, () => ({ + id: faker.string.uuid(), + name: faker.person.fullName(), + cohort: faker.number.int({ min: 1, max: 10 }), + profileUrl: faker.internet.url(), + totalSubmissions: faker.number.int({ min: 0, max: 100 }), + progress: faker.number.int({ min: 0, max: 100 }), + grade: faker.helpers.arrayElement([ + "SEED", + "SPROUT", + "SMALL_TREE", + "BIG_TREE", + ]), + submissions: Array.from( + { length: faker.number.int({ min: 0, max: 10 }) }, + () => ({ + memberId: faker.string.uuid(), + problemTitle: faker.lorem.words(3), + language: faker.helpers.arrayElement(["JavaScript", "Python", "Java"]), + }), + ), + })); + + // Mock the getMembers function to return the fake data + const getMembers = vi.fn().mockResolvedValue(mockMembers); + + // Use the hook with the mocked getMembers function + const { result } = renderHook(() => useMemberInfo({ getMembers })); + + // Initial state validation + expect(result.current.isLoading).toBe(true); + expect(result.current.members).toBeNull(); + expect(result.current.error).toBeNull(); + + // Wait for the hook to finish fetching data + await waitFor(() => expect(result.current.isLoading).toBe(false)); + + // Validate the updated state + expect(result.current.members).toEqual(mockMembers); + expect(result.current.error).toBeNull(); + expect(getMembers).toHaveBeenCalledTimes(1); +}); + +test("handle error when fetching member info fails", async () => { + // Create a mock error + const mockError = new Error("Fetch error"); + const getMembers = vi.fn().mockRejectedValue(mockError); + + // Use the hook with the mocked getMembers function that rejects + const { result } = renderHook(() => useMemberInfo({ getMembers })); + + // Initial state validation + expect(result.current.isLoading).toBe(true); + expect(result.current.members).toBeNull(); + expect(result.current.error).toBeNull(); + + // Wait for the hook to handle the error + await waitFor(() => expect(result.current.isLoading).toBe(false)); + + // Validate the state after error + expect(result.current.members).toBeNull(); + expect(result.current.error).toEqual(mockError); + expect(getMembers).toHaveBeenCalledTimes(1); +}); diff --git a/src/hooks/useMemberInfo.ts b/src/hooks/useMemberInfo.ts new file mode 100644 index 00000000..2bf82f6c --- /dev/null +++ b/src/hooks/useMemberInfo.ts @@ -0,0 +1,53 @@ +import { useEffect, useState } from "react"; + +export interface Member { + id: string; + name: string; + cohort: number; + profileUrl?: string; + totalSubmissions: number; + progress: number; + grade: "SEED" | "SPROUT" | "SMALL_TREE" | "BIG_TREE"; + submissions: { + memberId: string; + problemTitle: string; + language: string; + }[]; +} + +type UseMemberInfo = (params: { getMembers: () => Promise }) => { + members: Member[] | null; + isLoading: boolean; + error: unknown | null; +}; + +const useMemberInfo: UseMemberInfo = function ({ getMembers }) { + const [members, setMembers] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchMemberInfo = async () => { + setIsLoading(true); + + try { + const members = await getMembers(); + setMembers(members); + } catch (error) { + setError(error); + } finally { + setIsLoading(false); + } + }; + + fetchMemberInfo(); + }, [getMembers]); + + return { + members, + isLoading, + error, + }; +}; + +export default useMemberInfo;