From 267e0af090c46b1a2b0cf1e0ac4168512fa892d1 Mon Sep 17 00:00:00 2001 From: Alisha Zaman Date: Sun, 8 Mar 2026 19:57:43 -0400 Subject: [PATCH 1/2] 858: Added daterange popover 858: Redid tests 858: Resolved warnings 858: Resolved tests --- .../UserSubmissions/DateRangeIndicator.tsx | 70 +++++++++++ .../UserSubmissions/UserSubmissions.test.tsx | 117 +++++++++++++++--- .../UserSubmissions/UserSubmissions.tsx | 83 +++++-------- 3 files changed, 205 insertions(+), 65 deletions(-) create mode 100644 js/src/app/user/[userId]/submissions/_components/UserSubmissions/DateRangeIndicator.tsx diff --git a/js/src/app/user/[userId]/submissions/_components/UserSubmissions/DateRangeIndicator.tsx b/js/src/app/user/[userId]/submissions/_components/UserSubmissions/DateRangeIndicator.tsx new file mode 100644 index 000000000..e1a2ca8c9 --- /dev/null +++ b/js/src/app/user/[userId]/submissions/_components/UserSubmissions/DateRangeIndicator.tsx @@ -0,0 +1,70 @@ +import { Box, Tooltip } from "@mantine/core"; +import { useMediaQuery } from "@mantine/hooks"; +import { IconClock } from "@tabler/icons-react"; + +type DateValue = Date | string | null | undefined; + +interface DateRangeIndicatorProps { + readonly startDate: DateValue; + readonly endDate: DateValue; +} + +function formatDateRange(startDate: DateValue, endDate: DateValue): string { + const fmt = (d: Date | string) => + new Date(d).toLocaleDateString(undefined, { + month: "short", + day: "numeric", + year: "numeric", + }); + + if (startDate && endDate) + return `Filtered: ${fmt(startDate)} – ${fmt(endDate)}`; + if (startDate) return `Filtered: From ${fmt(startDate)}`; + if (endDate) return `Filtered: Until ${fmt(endDate)}`; + return ""; +} + +export default function DateRangeIndicator({ + startDate, + endDate, +}: Readonly) { + const isMobile = useMediaQuery("(max-width: 768px)"); + const isActive = Boolean(startDate || endDate); + + if (!isActive) return null; + + const label = formatDateRange(startDate, endDate); + + const icon = ( + + + + ); + + if (isMobile) return icon; + + return ( + + {icon} + + ); +} diff --git a/js/src/app/user/[userId]/submissions/_components/UserSubmissions/UserSubmissions.test.tsx b/js/src/app/user/[userId]/submissions/_components/UserSubmissions/UserSubmissions.test.tsx index 4e97c7caf..fc0021e14 100644 --- a/js/src/app/user/[userId]/submissions/_components/UserSubmissions/UserSubmissions.test.tsx +++ b/js/src/app/user/[userId]/submissions/_components/UserSubmissions/UserSubmissions.test.tsx @@ -99,33 +99,49 @@ const userSubmissionsSuccessHandler = http.get( }), ); -const userSubmissionsEmptyHandler = http.get( - userSubmissionsUrl.url.toString(), - () => - HttpResponse.json({ - success: true, - message: "Submissions loaded!", - payload: { - hasNextPage: false, - pages: 1, - items: [], - }, - }), -); - const userSubmissionsErrorHandler = http.get( userSubmissionsUrl.url.toString(), () => HttpResponse.error(), ); +const mockUseUserSubmissionsQuery = vi.fn(); + +vi.mock("@/lib/api/queries/user", () => ({ + useUserSubmissionsQuery: (...args: unknown[]) => + mockUseUserSubmissionsQuery(...args), +})); + +const BASE_QUERY_RESULT = { + status: "pending", + page: 1, + goBack: vi.fn(), + goForward: vi.fn(), + isPlaceholderData: false, + goTo: vi.fn(), + searchQuery: "", + setSearchQuery: vi.fn(), + pointFilter: false, + togglePointFilter: vi.fn(), + topics: [], + setTopics: vi.fn(), + clearTopics: vi.fn(), + startDate: undefined, + endDate: undefined, + setStartDate: vi.fn(), + setEndDate: vi.fn(), + data: undefined, +}; + describe("UserSubmissions succeeded", () => { afterEach(() => { cleanup(); + vi.clearAllMocks(); }); let renderProviderFn: TestUtilTypes.RenderWithAllProvidersFn | null = null; beforeEach(() => { renderProviderFn = TestUtils.getRenderWithAllProvidersFn(); + mockUseUserSubmissionsQuery.mockReturnValue(BASE_QUERY_RESULT); }); it("should render skeleton stack of submissions initially", () => { @@ -137,6 +153,54 @@ describe("UserSubmissions succeeded", () => { expect(element).toBeInTheDocument(); expect(element).toBeVisible(); }); + + it("should not render DateRangeIndicator when no date range is set", () => { + mockUseUserSubmissionsQuery.mockReturnValue({ + ...BASE_QUERY_RESULT, + startDate: undefined, + endDate: undefined, + }); + renderProviderFn?.(); + expect( + screen.queryByTestId("date-range-indicator"), + ).not.toBeInTheDocument(); + }); + + it("should render DateRangeIndicator when startDate is set", () => { + mockUseUserSubmissionsQuery.mockReturnValue({ + ...BASE_QUERY_RESULT, + status: "success", + data: { payload: { items: [], pages: 0, hasNextPage: false } }, + startDate: "2026-01-01", + endDate: undefined, + }); + renderProviderFn?.(); + expect(screen.getByTestId("date-range-indicator")).toBeInTheDocument(); + }); + + it("should render DateRangeIndicator when endDate is set", () => { + mockUseUserSubmissionsQuery.mockReturnValue({ + ...BASE_QUERY_RESULT, + status: "success", + data: { payload: { items: [], pages: 0, hasNextPage: false } }, + startDate: undefined, + endDate: "2026-03-01", + }); + renderProviderFn?.(); + expect(screen.getByTestId("date-range-indicator")).toBeInTheDocument(); + }); + + it("should render DateRangeIndicator when both dates are set", () => { + mockUseUserSubmissionsQuery.mockReturnValue({ + ...BASE_QUERY_RESULT, + status: "success", + data: { payload: { items: [], pages: 0, hasNextPage: false } }, + startDate: "2026-01-01", + endDate: "2026-03-01", + }); + renderProviderFn?.(); + expect(screen.getByTestId("date-range-indicator")).toBeInTheDocument(); + }); }); describe("UserSubmissions with successful API", () => { @@ -146,12 +210,25 @@ describe("UserSubmissions with successful API", () => { afterEach(() => { server.resetHandlers(); cleanup(); + vi.clearAllMocks(); }); afterAll(() => server.close()); let renderProviderFn: TestUtilTypes.RenderWithAllProvidersFn | null = null; beforeEach(() => { renderProviderFn = TestUtils.getRenderWithAllProvidersFn(); + + mockUseUserSubmissionsQuery.mockReturnValue({ + ...BASE_QUERY_RESULT, + status: "success", + data: { + payload: { + items: MOCK_SUBMISSIONS, + pages: 1, + hasNextPage: false, + }, + }, + }); }); it("should render submission titles after successful API call", async () => { @@ -219,7 +296,11 @@ describe("UserSubmissions with successful API", () => { }); it("should show Nothing found when empty", async () => { - server.use(userSubmissionsEmptyHandler); + mockUseUserSubmissionsQuery.mockReturnValue({ + ...BASE_QUERY_RESULT, + status: "success", + data: { payload: { items: [], pages: 1, hasNextPage: false } }, + }); renderProviderFn?.(); await waitFor(() => { @@ -235,12 +316,18 @@ describe("UserSubmissions error state", () => { afterEach(() => { server.resetHandlers(); cleanup(); + vi.clearAllMocks(); }); afterAll(() => server.close()); let renderProviderFn: TestUtilTypes.RenderWithAllProvidersFn | null = null; beforeEach(() => { renderProviderFn = TestUtils.getRenderWithAllProvidersFn(); + mockUseUserSubmissionsQuery.mockReturnValue({ + ...BASE_QUERY_RESULT, + status: "error", + data: undefined, + }); }); it("should show error toast when API errors", async () => { diff --git a/js/src/app/user/[userId]/submissions/_components/UserSubmissions/UserSubmissions.tsx b/js/src/app/user/[userId]/submissions/_components/UserSubmissions/UserSubmissions.tsx index d29e1a0cb..385cea91c 100644 --- a/js/src/app/user/[userId]/submissions/_components/UserSubmissions/UserSubmissions.tsx +++ b/js/src/app/user/[userId]/submissions/_components/UserSubmissions/UserSubmissions.tsx @@ -1,5 +1,6 @@ import DateRangePopover from "@/app/user/[userId]/submissions/_components/DateRangePopover/DateRangePopover"; import TopicFilterPopover from "@/app/user/[userId]/submissions/_components/TopicFilters/TopicFilterPopover"; +import DateRangeIndicator from "@/app/user/[userId]/submissions/_components/UserSubmissions/DateRangeIndicator"; import UserSubmissionsSkeleton from "@/app/user/[userId]/submissions/_components/UserSubmissions/UserSubmissionsSkeleton"; import CodebloomCard from "@/components/ui/CodebloomCard"; import FilterDropdown from "@/components/ui/dropdown/FilterDropdown"; @@ -85,6 +86,36 @@ export default function UserSubmissions({ userId }: { userId: string }) { const pageData = data.payload; + const filterDropdown = ( + + + + + Points Received + + } + /> + + + + + ); + return ( {!isMobile && ( - - - - Points Received - - } - /> - - + {filterDropdown} )} - {isMobile && ( - - - - Points Received - - } - /> - - - )} + {isMobile && filterDropdown} {isPlaceholderData && ( From 551175f08fdd3498bf3899f5e4c156dee7881c0a Mon Sep 17 00:00:00 2001 From: Alisha Zaman Date: Tue, 17 Mar 2026 14:53:53 -0400 Subject: [PATCH 2/2] 858: Resolved comments --- .../UserSubmissions/DateRangeIndicator.tsx | 60 +++++++++---------- 1 file changed, 28 insertions(+), 32 deletions(-) diff --git a/js/src/app/user/[userId]/submissions/_components/UserSubmissions/DateRangeIndicator.tsx b/js/src/app/user/[userId]/submissions/_components/UserSubmissions/DateRangeIndicator.tsx index e1a2ca8c9..1b85de066 100644 --- a/js/src/app/user/[userId]/submissions/_components/UserSubmissions/DateRangeIndicator.tsx +++ b/js/src/app/user/[userId]/submissions/_components/UserSubmissions/DateRangeIndicator.tsx @@ -1,5 +1,4 @@ import { Box, Tooltip } from "@mantine/core"; -import { useMediaQuery } from "@mantine/hooks"; import { IconClock } from "@tabler/icons-react"; type DateValue = Date | string | null | undefined; @@ -28,43 +27,40 @@ export default function DateRangeIndicator({ startDate, endDate, }: Readonly) { - const isMobile = useMediaQuery("(max-width: 768px)"); - const isActive = Boolean(startDate || endDate); + const isActive = !!startDate || !!endDate; if (!isActive) return null; const label = formatDateRange(startDate, endDate); - const icon = ( - - - - ); - - if (isMobile) return icon; - return ( - - {icon} + + + + ); }