From 58c5f707a5ba964bcf4613805eaecb27c96cdbc9 Mon Sep 17 00:00:00 2001 From: Emily Jablonski Date: Wed, 5 Nov 2025 11:39:35 -0700 Subject: [PATCH 1/5] test: listing intro test --- .../sections/ListingIntro.test.tsx | 117 ++++++++++++++++++ .../sections/ListingIntro.tsx | 1 + 2 files changed, 118 insertions(+) create mode 100644 sites/partners/__tests__/components/listings/PaperListingForm/sections/ListingIntro.test.tsx diff --git a/sites/partners/__tests__/components/listings/PaperListingForm/sections/ListingIntro.test.tsx b/sites/partners/__tests__/components/listings/PaperListingForm/sections/ListingIntro.test.tsx new file mode 100644 index 00000000000..0cb9adc0019 --- /dev/null +++ b/sites/partners/__tests__/components/listings/PaperListingForm/sections/ListingIntro.test.tsx @@ -0,0 +1,117 @@ +import React from "react" +import { rest } from "msw" +import { setupServer } from "msw/node" +import userEvent from "@testing-library/user-event" +import { screen } from "@testing-library/react" +import { FormProvider, useForm } from "react-hook-form" +import { Jurisdiction } from "@bloom-housing/shared-helpers/src/types/backend-swagger" +import { mockNextRouter, render } from "../../../../testUtils" +import { formDefaults, FormListing } from "../../../../../src/lib/listings/formTypes" +import ListingIntro from "../../../../../src/components/listings/PaperListingForm/sections/ListingIntro" + +const FormComponent = ({ children, values }: { values?: FormListing; children }) => { + const formMethods = useForm({ + defaultValues: { ...formDefaults, ...values }, + shouldUnregister: false, + }) + return {children} +} + +const server = setupServer() + +// Enable API mocking before tests. +beforeAll(() => { + server.listen() + mockNextRouter() +}) + +// Reset any runtime request handlers we may add during the tests. +afterEach(() => server.resetHandlers()) + +// Disable API mocking after the tests are done. +afterAll(() => server.close()) + +describe("ListingIntro", () => { + const adminUserWithJurisdictions = { + jurisdictions: [ + { + id: "jurisdiction1", + name: "jurisdictionWithJurisdictionAdmin", + featureFlags: [], + }, + ], + } + + it("should render the ListingIntro section with one jurisdiction", async () => { + document.cookie = "access-token-available=True" + server.use( + rest.get("http://localhost/api/adapter/user", (_req, res, ctx) => { + return res(ctx.json(adminUserWithJurisdictions)) + }) + ) + + render( + + + + ) + + await screen.findByRole("heading", { level: 2, name: "Listing intro" }) + expect( + screen.getByText("Let's get started with some basic information about your listing.") + ).toBeInTheDocument() + expect(screen.getByRole("textbox", { name: "Listing name *" })).toBeInTheDocument() + expect(screen.queryByRole("combobox", { name: "Jurisdiction *" })).not.toBeInTheDocument() + expect(screen.getByRole("textbox", { name: "Housing developer" })).toBeInTheDocument() + }) + + it("should render the ListingIntro section with multiple jurisdictions and required developer", async () => { + document.cookie = "access-token-available=True" + server.use( + rest.get("http://localhost/api/adapter/user", (_req, res, ctx) => { + return res(ctx.json(adminUserWithJurisdictions)) + }) + ) + + render( + + + + ) + + await screen.findByRole("heading", { level: 2, name: "Listing intro" }) + expect( + screen.getByText("Let's get started with some basic information about your listing.") + ).toBeInTheDocument() + expect(screen.getByRole("textbox", { name: "Listing name *" })).toBeInTheDocument() + expect(screen.getByRole("combobox", { name: "Jurisdiction *" })).toBeInTheDocument() + expect(screen.getByRole("textbox", { name: "Housing developer *" })).toBeInTheDocument() + + await userEvent.selectOptions( + screen.getByRole("combobox", { name: "Jurisdiction *" }), + screen.getByRole("option", { name: "JurisdictionA" }) + ) + + expect(screen.getByRole("textbox", { name: "Housing developer *" })).toBeInTheDocument() + }) +}) diff --git a/sites/partners/src/components/listings/PaperListingForm/sections/ListingIntro.tsx b/sites/partners/src/components/listings/PaperListingForm/sections/ListingIntro.tsx index 91d00c844d9..ef76eca8664 100644 --- a/sites/partners/src/components/listings/PaperListingForm/sections/ListingIntro.tsx +++ b/sites/partners/src/components/listings/PaperListingForm/sections/ListingIntro.tsx @@ -92,6 +92,7 @@ const ListingIntro = (props: ListingIntroProps) => { } }, "aria-required": fieldIsRequired("jurisdictions", props.requiredFields), + "aria-hidden": !!defaultJurisdiction, }} /> From 5c151901e34e026033a3254cf4ce9c72060259ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Zi=C4=99cina?= Date: Wed, 5 Nov 2025 19:40:26 +0100 Subject: [PATCH 2/5] feat: add feature flag for housing developer owner (#5549) --- api/prisma/seed-staging.ts | 2 +- .../enums/feature-flags/feature-flags-enum.ts | 6 ++++++ .../services/listing-csv-export.service.ts | 7 ++++++- shared-helpers/src/types/backend-swagger.ts | 1 + .../locale_overrides/general.json | 1 + .../sections/DetailListingIntro.tsx | 17 ++++++++++++++- .../sections/ListingIntro.tsx | 21 +++++++++++++++---- 7 files changed, 48 insertions(+), 7 deletions(-) diff --git a/api/prisma/seed-staging.ts b/api/prisma/seed-staging.ts index 2f91177427a..c7e26b71211 100644 --- a/api/prisma/seed-staging.ts +++ b/api/prisma/seed-staging.ts @@ -155,7 +155,7 @@ export const stagingSeed = async ( }); const angelopolisJurisdiction = await prismaClient.jurisdictions.create({ data: jurisdictionFactory('Angelopolis', { - featureFlags: [], + featureFlags: [FeatureFlagEnum.enableHousingDeveloperOwner], requiredListingFields: [ 'listingsBuildingAddress', 'name', diff --git a/api/src/enums/feature-flags/feature-flags-enum.ts b/api/src/enums/feature-flags/feature-flags-enum.ts index de74314f2b9..d4ef3a08b3f 100644 --- a/api/src/enums/feature-flags/feature-flags-enum.ts +++ b/api/src/enums/feature-flags/feature-flags-enum.ts @@ -14,6 +14,7 @@ export enum FeatureFlagEnum { enableGeocodingPreferences = 'enableGeocodingPreferences', enableGeocodingRadiusMethod = 'enableGeocodingRadiusMethod', enableHomeType = 'enableHomeType', + enableHousingDeveloperOwner = 'enableHousingDeveloperOwner', enableIsVerified = 'enableIsVerified', enableLimitedHowDidYouHear = 'enableLimitedHowDidYouHear', enableListingFavoriting = 'enableListingFavoriting', @@ -104,6 +105,11 @@ export const featureFlagMap: { name: string; description: string }[] = [ name: FeatureFlagEnum.enableHomeType, description: 'When true, home type feature is turned on', }, + { + name: FeatureFlagEnum.enableHousingDeveloperOwner, + description: + "When true, the 'Housing developer' field label becomes 'Housing developer / owner'", + }, { name: FeatureFlagEnum.enableIsVerified, description: diff --git a/api/src/services/listing-csv-export.service.ts b/api/src/services/listing-csv-export.service.ts index b46ab4ebaa8..b7339e7a997 100644 --- a/api/src/services/listing-csv-export.service.ts +++ b/api/src/services/listing-csv-export.service.ts @@ -451,7 +451,12 @@ export class ListingCsvExporterService implements CsvExporterServiceInterface { }, { path: 'developer', - label: 'Developer', + label: doAnyJurisdictionHaveFeatureFlagSet( + user.jurisdictions, + FeatureFlagEnum.enableHousingDeveloperOwner, + ) + ? 'Housing developer / owner' + : 'Developer', }, { path: 'listingsBuildingAddress.street', diff --git a/shared-helpers/src/types/backend-swagger.ts b/shared-helpers/src/types/backend-swagger.ts index 32d66b17a1d..665e78c9111 100644 --- a/shared-helpers/src/types/backend-swagger.ts +++ b/shared-helpers/src/types/backend-swagger.ts @@ -7554,6 +7554,7 @@ export enum FeatureFlagEnum { "enableGeocodingPreferences" = "enableGeocodingPreferences", "enableGeocodingRadiusMethod" = "enableGeocodingRadiusMethod", "enableHomeType" = "enableHomeType", + "enableHousingDeveloperOwner" = "enableHousingDeveloperOwner", "enableIsVerified" = "enableIsVerified", "enableLimitedHowDidYouHear" = "enableLimitedHowDidYouHear", "enableListingFavoriting" = "enableListingFavoriting", diff --git a/sites/partners/page_content/locale_overrides/general.json b/sites/partners/page_content/locale_overrides/general.json index 6393f747c57..9ff341cf427 100644 --- a/sites/partners/page_content/locale_overrides/general.json +++ b/sites/partners/page_content/locale_overrides/general.json @@ -250,6 +250,7 @@ "listings.events.openHouseNotes": "Open house notes", "listings.fieldError": "Please resolve any errors before saving or publishing your listing.", "listings.firstComeFirstServe": "First come first serve", + "listings.housingDeveloperOwner": "Housing developer / owner", "listings.isDigitalApplication": "Is there a digital application?", "listings.isPaperApplication": "Is there a paper application?", "listings.isReferralOpportunity": "Is there a referral opportunity?", diff --git a/sites/partners/src/components/listings/PaperListingDetails/sections/DetailListingIntro.tsx b/sites/partners/src/components/listings/PaperListingDetails/sections/DetailListingIntro.tsx index b2f07968cd4..ec3b778af81 100644 --- a/sites/partners/src/components/listings/PaperListingDetails/sections/DetailListingIntro.tsx +++ b/sites/partners/src/components/listings/PaperListingDetails/sections/DetailListingIntro.tsx @@ -4,9 +4,17 @@ import { FieldValue, Grid } from "@bloom-housing/ui-seeds" import { ListingContext } from "../../ListingContext" import { getDetailFieldString } from "./helpers" import SectionWithGrid from "../../../shared/SectionWithGrid" +import { AuthContext } from "@bloom-housing/shared-helpers" +import { FeatureFlagEnum } from "@bloom-housing/shared-helpers/src/types/backend-swagger" const DetailListingIntro = () => { const listing = useContext(ListingContext) + const { doJurisdictionsHaveFeatureFlagOn } = useContext(AuthContext) + + const enableHousingDeveloperOwner = doJurisdictionsHaveFeatureFlagOn( + FeatureFlagEnum.enableHousingDeveloperOwner, + listing.jurisdictions.id + ) return ( @@ -24,7 +32,14 @@ const DetailListingIntro = () => { - + {getDetailFieldString(listing.developer)} diff --git a/sites/partners/src/components/listings/PaperListingForm/sections/ListingIntro.tsx b/sites/partners/src/components/listings/PaperListingForm/sections/ListingIntro.tsx index ef76eca8664..6379fa7df25 100644 --- a/sites/partners/src/components/listings/PaperListingForm/sections/ListingIntro.tsx +++ b/sites/partners/src/components/listings/PaperListingForm/sections/ListingIntro.tsx @@ -1,6 +1,9 @@ -import React from "react" +import React, { useContext } from "react" import { useFormContext } from "react-hook-form" -import { Jurisdiction } from "@bloom-housing/shared-helpers/src/types/backend-swagger" +import { + FeatureFlagEnum, + Jurisdiction, +} from "@bloom-housing/shared-helpers/src/types/backend-swagger" import { t, Field, SelectOption, Select } from "@bloom-housing/ui-components" import { Grid } from "@bloom-housing/ui-seeds" import { @@ -11,6 +14,7 @@ import { } from "../../../../lib/helpers" import SectionWithGrid from "../../../shared/SectionWithGrid" import styles from "../ListingForm.module.scss" +import { AuthContext } from "@bloom-housing/shared-helpers" interface ListingIntroProps { jurisdictions: Jurisdiction[] @@ -19,9 +23,16 @@ interface ListingIntroProps { const ListingIntro = (props: ListingIntroProps) => { const formMethods = useFormContext() + const { doJurisdictionsHaveFeatureFlagOn } = useContext(AuthContext) // eslint-disable-next-line @typescript-eslint/unbound-method - const { register, clearErrors, errors } = formMethods + const { register, clearErrors, errors, watch } = formMethods + const jurisdiction = watch("jurisdictions.id") + + const enableHousingDeveloperOwner = doJurisdictionsHaveFeatureFlagOn( + FeatureFlagEnum.enableHousingDeveloperOwner, + jurisdiction + ) const jurisdictionOptions: SelectOption[] = [ { label: "", value: "" }, @@ -102,7 +113,9 @@ const ListingIntro = (props: ListingIntroProps) => { register={register} {...defaultFieldProps( "developer", - t("listings.developer"), + enableHousingDeveloperOwner + ? t("listings.housingDeveloperOwner") + : t("listings.developer"), props.requiredFields, errors, clearErrors From 1f5953558f21b1c2197014eb077835a32231860f Mon Sep 17 00:00:00 2001 From: Emily Jablonski Date: Wed, 5 Nov 2025 12:11:18 -0700 Subject: [PATCH 3/5] test: listing intro test --- .../sections/ListingIntro.test.tsx | 45 ++++++++++++++++--- .../sections/ListingIntro.tsx | 6 +-- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/sites/partners/__tests__/components/listings/PaperListingForm/sections/ListingIntro.test.tsx b/sites/partners/__tests__/components/listings/PaperListingForm/sections/ListingIntro.test.tsx index 0cb9adc0019..77b0dc21590 100644 --- a/sites/partners/__tests__/components/listings/PaperListingForm/sections/ListingIntro.test.tsx +++ b/sites/partners/__tests__/components/listings/PaperListingForm/sections/ListingIntro.test.tsx @@ -4,7 +4,11 @@ import { setupServer } from "msw/node" import userEvent from "@testing-library/user-event" import { screen } from "@testing-library/react" import { FormProvider, useForm } from "react-hook-form" -import { Jurisdiction } from "@bloom-housing/shared-helpers/src/types/backend-swagger" +import { AuthContext } from "@bloom-housing/shared-helpers" +import { + FeatureFlagEnum, + Jurisdiction, +} from "@bloom-housing/shared-helpers/src/types/backend-swagger" import { mockNextRouter, render } from "../../../../testUtils" import { formDefaults, FormListing } from "../../../../../src/lib/listings/formTypes" import ListingIntro from "../../../../../src/components/listings/PaperListingForm/sections/ListingIntro" @@ -99,10 +103,6 @@ describe("ListingIntro", () => { ) - await screen.findByRole("heading", { level: 2, name: "Listing intro" }) - expect( - screen.getByText("Let's get started with some basic information about your listing.") - ).toBeInTheDocument() expect(screen.getByRole("textbox", { name: "Listing name *" })).toBeInTheDocument() expect(screen.getByRole("combobox", { name: "Jurisdiction *" })).toBeInTheDocument() expect(screen.getByRole("textbox", { name: "Housing developer *" })).toBeInTheDocument() @@ -114,4 +114,39 @@ describe("ListingIntro", () => { expect(screen.getByRole("textbox", { name: "Housing developer *" })).toBeInTheDocument() }) + + it("should render appropriate text when housing developer owner feature flag is on", () => { + document.cookie = "access-token-available=True" + server.use( + rest.get("http://localhost/api/adapter/user", (_req, res, ctx) => { + return res(ctx.json(adminUserWithJurisdictions)) + }) + ) + + const doJurisdictionsHaveFeatureFlagOn = () => { + return true + } + + render( + + + + + + ) + expect(screen.getByRole("textbox", { name: "Housing developer / owner" })).toBeInTheDocument() + }) }) diff --git a/sites/partners/src/components/listings/PaperListingForm/sections/ListingIntro.tsx b/sites/partners/src/components/listings/PaperListingForm/sections/ListingIntro.tsx index 6379fa7df25..5498ef2dc03 100644 --- a/sites/partners/src/components/listings/PaperListingForm/sections/ListingIntro.tsx +++ b/sites/partners/src/components/listings/PaperListingForm/sections/ListingIntro.tsx @@ -1,11 +1,12 @@ import React, { useContext } from "react" import { useFormContext } from "react-hook-form" +import { t, Field, SelectOption, Select } from "@bloom-housing/ui-components" +import { Grid } from "@bloom-housing/ui-seeds" import { FeatureFlagEnum, Jurisdiction, } from "@bloom-housing/shared-helpers/src/types/backend-swagger" -import { t, Field, SelectOption, Select } from "@bloom-housing/ui-components" -import { Grid } from "@bloom-housing/ui-seeds" +import { AuthContext } from "@bloom-housing/shared-helpers" import { fieldMessage, fieldHasError, @@ -14,7 +15,6 @@ import { } from "../../../../lib/helpers" import SectionWithGrid from "../../../shared/SectionWithGrid" import styles from "../ListingForm.module.scss" -import { AuthContext } from "@bloom-housing/shared-helpers" interface ListingIntroProps { jurisdictions: Jurisdiction[] From 7a8feb35a1abd4d8d008c405ce874d323e29e046 Mon Sep 17 00:00:00 2001 From: Emily Jablonski Date: Wed, 5 Nov 2025 12:23:58 -0700 Subject: [PATCH 4/5] fix: duplicate import --- .../listings/PaperListingForm/sections/ListingIntro.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/sites/partners/src/components/listings/PaperListingForm/sections/ListingIntro.tsx b/sites/partners/src/components/listings/PaperListingForm/sections/ListingIntro.tsx index 559be333773..5498ef2dc03 100644 --- a/sites/partners/src/components/listings/PaperListingForm/sections/ListingIntro.tsx +++ b/sites/partners/src/components/listings/PaperListingForm/sections/ListingIntro.tsx @@ -15,7 +15,6 @@ import { } from "../../../../lib/helpers" import SectionWithGrid from "../../../shared/SectionWithGrid" import styles from "../ListingForm.module.scss" -import { AuthContext } from "@bloom-housing/shared-helpers" interface ListingIntroProps { jurisdictions: Jurisdiction[] From 6ca1ccfa9a02af498b04bd57938f96d9adea571c Mon Sep 17 00:00:00 2001 From: Emily Jablonski Date: Mon, 10 Nov 2025 14:02:04 -0700 Subject: [PATCH 5/5] fix: pr review --- .../sections/ListingIntro.test.tsx | 51 ++++++++++--------- .../sections/ListingIntro.tsx | 9 ++-- 2 files changed, 32 insertions(+), 28 deletions(-) diff --git a/sites/partners/__tests__/components/listings/PaperListingForm/sections/ListingIntro.test.tsx b/sites/partners/__tests__/components/listings/PaperListingForm/sections/ListingIntro.test.tsx index 77b0dc21590..8c35ecd61e7 100644 --- a/sites/partners/__tests__/components/listings/PaperListingForm/sections/ListingIntro.test.tsx +++ b/sites/partners/__tests__/components/listings/PaperListingForm/sections/ListingIntro.test.tsx @@ -4,7 +4,6 @@ import { setupServer } from "msw/node" import userEvent from "@testing-library/user-event" import { screen } from "@testing-library/react" import { FormProvider, useForm } from "react-hook-form" -import { AuthContext } from "@bloom-housing/shared-helpers" import { FeatureFlagEnum, Jurisdiction, @@ -115,38 +114,40 @@ describe("ListingIntro", () => { expect(screen.getByRole("textbox", { name: "Housing developer *" })).toBeInTheDocument() }) - it("should render appropriate text when housing developer owner feature flag is on", () => { + it("should render appropriate text when housing developer owner feature flag is on", async () => { document.cookie = "access-token-available=True" server.use( rest.get("http://localhost/api/adapter/user", (_req, res, ctx) => { - return res(ctx.json(adminUserWithJurisdictions)) + return res( + ctx.json({ + jurisdictions: [ + { + id: "JurisdictionA", + name: "jurisdictionWithJurisdictionAdmin", + featureFlags: [{ name: FeatureFlagEnum.enableHousingDeveloperOwner, active: true }], + }, + ], + }) + ) }) ) - const doJurisdictionsHaveFeatureFlagOn = () => { - return true - } - render( - - - - - + + + ) + await screen.findByRole("textbox", { name: "Housing developer / owner" }) expect(screen.getByRole("textbox", { name: "Housing developer / owner" })).toBeInTheDocument() + expect(screen.queryByRole("textbox", { name: "Housing developer" })).not.toBeInTheDocument() }) }) diff --git a/sites/partners/src/components/listings/PaperListingForm/sections/ListingIntro.tsx b/sites/partners/src/components/listings/PaperListingForm/sections/ListingIntro.tsx index 5498ef2dc03..392069b73c5 100644 --- a/sites/partners/src/components/listings/PaperListingForm/sections/ListingIntro.tsx +++ b/sites/partners/src/components/listings/PaperListingForm/sections/ListingIntro.tsx @@ -21,6 +21,11 @@ interface ListingIntroProps { requiredFields: string[] } +const getDeveloperLabel = (jurisdiction: string, enableHousingDeveloperOwner: boolean) => { + if (!jurisdiction) return t("listings.developer") + return enableHousingDeveloperOwner ? t("listings.housingDeveloperOwner") : t("listings.developer") +} + const ListingIntro = (props: ListingIntroProps) => { const formMethods = useFormContext() const { doJurisdictionsHaveFeatureFlagOn } = useContext(AuthContext) @@ -113,9 +118,7 @@ const ListingIntro = (props: ListingIntroProps) => { register={register} {...defaultFieldProps( "developer", - enableHousingDeveloperOwner - ? t("listings.housingDeveloperOwner") - : t("listings.developer"), + getDeveloperLabel(jurisdiction, enableHousingDeveloperOwner), props.requiredFields, errors, clearErrors