diff --git a/sites/partners/__tests__/pages/listings/lottery.test.tsx b/sites/partners/__tests__/pages/listings/lottery.test.tsx index b426d67ba8..98db79d7a6 100644 --- a/sites/partners/__tests__/pages/listings/lottery.test.tsx +++ b/sites/partners/__tests__/pages/listings/lottery.test.tsx @@ -5,6 +5,7 @@ import { setupServer } from "msw/node" import { listing } from "@bloom-housing/shared-helpers/__tests__/testHelpers" import Lottery from "../../../src/pages/listings/[id]/lottery" import { mockNextRouter, render } from "../../testUtils" +import { ListingMultiselectQuestion } from "@bloom-housing/shared-helpers/src/types/backend-swagger" const server = setupServer() @@ -207,9 +208,7 @@ describe("lottery", () => { expect(header).toBeInTheDocument() expect(getByText("Export lottery data")).toBeInTheDocument() - expect( - getByText("File includes randomized general pool and preference data.") - ).toBeInTheDocument() + expect(getByText("File includes randomized general pool data.")).toBeInTheDocument() expect(getByText("Re-run lottery")).toBeInTheDocument() expect(getByText("Release lottery")).toBeInTheDocument() }) @@ -234,7 +233,7 @@ describe("lottery", () => { }) ) - const { getByText, findByText, findAllByText } = render( + const { getByText, findByText, getAllByText } = render( ) @@ -243,7 +242,7 @@ describe("lottery", () => { fireEvent.click(getByText("Re-run lottery")) expect(await findByText("Are you sure?")).toBeInTheDocument() - expect(await findAllByText("Re-run lottery")).toHaveLength(2) + expect(getAllByText("Re-run lottery")).toHaveLength(2) }) it("should show release modal if user clicks on release", async () => { @@ -276,7 +275,7 @@ describe("lottery", () => { fireEvent.click(getByText("Release lottery")) expect(await findByText("Are you sure?")).toBeInTheDocument() expect( - await findByText( + getByText( "Releasing the lottery will give Partner users access to the lottery data, including the ability to publish results to applicants." ) ).toBeInTheDocument() @@ -310,7 +309,7 @@ describe("lottery", () => { fireEvent.click(getByText("Run lottery")) expect(await findByText("Confirmation needed")).toBeInTheDocument() expect( - await findByText("Make sure to add all paper applications before running the lottery.") + getByText("Make sure to add all paper applications before running the lottery.") ).toBeInTheDocument() }) @@ -334,14 +333,111 @@ describe("lottery", () => { }) ) - const { getByText, findByText, findAllByText } = render() + const { getByText, findByText, getAllByText } = render() const header = await findByText("Partners Portal") expect(header).toBeInTheDocument() fireEvent.click(getByText("Run lottery")) expect(await findByText("Confirmation needed")).toBeInTheDocument() - expect(await findByText("5 unresolved duplicate sets.")).toBeInTheDocument() - expect(await findAllByText("Run lottery")).toHaveLength(2) + expect(getByText("5 unresolved duplicate sets.")).toBeInTheDocument() + expect(getAllByText("Run lottery")).toHaveLength(2) + }) + + it("should show export modal if lottery has been run with no preference text", async () => { + mockNextRouter({ id: "Uvbk5qurpB2WI9V6WnNdH" }) + document.cookie = "access-token-available=True" + server.use( + rest.get("http://localhost/api/adapter/user", (_req, res, ctx) => { + return res( + ctx.json({ + id: "user1", + userRoles: { isAdmin: true }, + }) + ) + }), + rest.post("http://localhost:3100/auth/token", (_req, res, ctx) => { + return res(ctx.json("")) + }), + rest.get("http://localhost/api/adapter/applicationFlaggedSets/meta", (_req, res, ctx) => { + return res(ctx.json({ totalCount: 5, totalPendingCount: 5 })) + }) + ) + + const updatedListing = { ...listing, lotteryLastRunAt: new Date("September 6, 2025 8:15:00") } + const { getByText, findByText, findAllByText, getAllByText } = render( + + ) + + const header = await findByText("Partners Portal") + expect(header).toBeInTheDocument() + + expect(getByText("File includes randomized general pool data.")).toBeInTheDocument() + + fireEvent.click(getByText("Export")) + expect(await findAllByText("Export lottery data")).toHaveLength(2) + + expect( + getByText("This file includes the lottery raw rank for all applications.", { exact: false }) + ).toBeInTheDocument() + expect( + getByText("This data was generated from the lottery that was run on 09/06/2025 at 8:15 am.", { + exact: false, + }) + ).toBeInTheDocument() + expect(getAllByText("Export")).toHaveLength(2) + }) + + it("should show export modal if lottery has been run with preference text", async () => { + mockNextRouter({ id: "Uvbk5qurpB2WI9V6WnNdH" }) + document.cookie = "access-token-available=True" + server.use( + rest.get("http://localhost/api/adapter/user", (_req, res, ctx) => { + return res( + ctx.json({ + id: "user1", + userRoles: { isAdmin: true }, + }) + ) + }), + rest.post("http://localhost:3100/auth/token", (_req, res, ctx) => { + return res(ctx.json("")) + }), + rest.get("http://localhost/api/adapter/applicationFlaggedSets/meta", (_req, res, ctx) => { + return res(ctx.json({ totalCount: 5, totalPendingCount: 5 })) + }) + ) + + const updatedListing = { + ...listing, + lotteryLastRunAt: new Date("September 6, 2025 8:15:00"), + listingMultiselectQuestions: [{ multiselectQuestions: {} } as ListingMultiselectQuestion], + } + const { getByText, findByText, findAllByText, getAllByText } = render( + + ) + + const header = await findByText("Partners Portal") + expect(header).toBeInTheDocument() + + expect( + getByText("File includes randomized general pool and preference data.") + ).toBeInTheDocument() + + fireEvent.click(getByText("Export")) + expect(await findAllByText("Export lottery data")).toHaveLength(2) + + expect( + getByText( + "This file includes the lottery raw rank and preferences data for all applications.", + { exact: false } + ) + ).toBeInTheDocument() + expect( + getByText("This data was generated from the lottery that was run on 09/06/2025 at 8:15 am.", { + exact: false, + }) + ).toBeInTheDocument() + expect(getAllByText("Export")).toHaveLength(2) }) }) diff --git a/sites/partners/page_content/locale_overrides/general.json b/sites/partners/page_content/locale_overrides/general.json index 6afc16c8fb..4287f307a1 100644 --- a/sites/partners/page_content/locale_overrides/general.json +++ b/sites/partners/page_content/locale_overrides/general.json @@ -221,7 +221,11 @@ "listings.lottery.duplicateStringPlural": "%{sets} unresolved duplicate sets.", "listings.lottery.duplicatesConfirm": "You should resolve any duplicates before running the lottery.", "listings.lottery.export": "Export lottery data", + "listings.lottery.exportContent": "This file includes the lottery raw rank and preferences data for all applications.", + "listings.lottery.exportContentNoPreferences": "This file includes the lottery raw rank for all applications.", + "listings.lottery.exportContentTimestamp": "This data was generated from the lottery that was run on %{date} at %{time}.", "listings.lottery.exportFile": "File includes randomized general pool and preference data.", + "listings.lottery.exportFileNoPreferences": "File includes randomized general pool data.", "listings.lottery.history": "History", "listings.lottery.noData": "No lottery data", "listings.lottery.noDataDescription": "It looks like you haven't run a lottery for this listing yet.", diff --git a/sites/partners/src/pages/listings/[id]/lottery.tsx b/sites/partners/src/pages/listings/[id]/lottery.tsx index c8a8b4ae63..e02a80cc53 100644 --- a/sites/partners/src/pages/listings/[id]/lottery.tsx +++ b/sites/partners/src/pages/listings/[id]/lottery.tsx @@ -1,6 +1,7 @@ import React, { useState, useContext } from "react" import Head from "next/head" import axios from "axios" +import dayjs from "dayjs" import Ticket from "@heroicons/react/24/solid/TicketIcon" import Download from "@heroicons/react/24/solid/ArrowDownTrayIcon" import { t, Breadcrumbs, BreadcrumbLink } from "@bloom-housing/ui-components" @@ -32,6 +33,7 @@ const Lottery = (props: { listing: Listing }) => { const [runModal, setRunModal] = useState(false) const [reRunModal, setReRunModal] = useState(false) const [releaseModal, setReleaseModal] = useState(false) + const [exportModal, setExportModal] = useState(false) const [loading, setLoading] = useState(false) const { listingsService } = useContext(AuthContext) @@ -60,9 +62,13 @@ const Lottery = (props: { listing: Listing }) => { {t("listings.lottery.export")} -
{t("listings.lottery.exportFile")}
+
+ {listing.listingMultiselectQuestions.length + ? t("listings.lottery.exportFile") + : t("listings.lottery.exportFileNoPreferences")} +
- +
) @@ -330,6 +336,48 @@ const Lottery = (props: { listing: Listing }) => { + setExportModal(false)} + > + + {t("listings.lottery.export")} + + +

+ {listing.listingMultiselectQuestions.length + ? t("listings.lottery.exportContent") + : t("listings.lottery.exportContentNoPreferences")}{" "} + {t("listings.lottery.exportContentTimestamp", { + date: dayjs(listing.lotteryLastRunAt).format("MM/DD/YYYY"), + time: dayjs(listing.lotteryLastRunAt).format("h:mm a"), + })} +

+
+ + + + +