Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion api/prisma/seed-staging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,11 @@ export const stagingSeed = async (
});
const angelopolisJurisdiction = await prismaClient.jurisdictions.create({
data: jurisdictionFactory('Angelopolis', {
featureFlags: [FeatureFlagEnum.enableNeighborhoodAmenities, FeatureFlagEnum.enableHousingDeveloperOwner],
featureFlags: [
FeatureFlagEnum.enableHousingDeveloperOwner,
FeatureFlagEnum.enableNeighborhoodAmenities,
FeatureFlagEnum.enableNeighborhoodAmenitiesDropdown,
],
visibleNeighborhoodAmenities: [
NeighborhoodAmenitiesEnum.groceryStores,
NeighborhoodAmenitiesEnum.pharmacies,
Expand Down
6 changes: 6 additions & 0 deletions api/src/enums/feature-flags/feature-flags-enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export enum FeatureFlagEnum {
enableListingUpdatedAt = 'enableListingUpdatedAt',
enableMarketingStatus = 'enableMarketingStatus',
enableNeighborhoodAmenities = 'enableNeighborhoodAmenities',
enableNeighborhoodAmenitiesDropdown = 'enableNeighborhoodAmenitiesDropdown',
enableNonRegulatedListings = 'enableNonRegulatedListings',
enablePartnerDemographics = 'enablePartnerDemographics',
enablePartnerSettings = 'enablePartnerSettings',
Expand Down Expand Up @@ -154,6 +155,11 @@ export const featureFlagMap: { name: string; description: string }[] = [
description:
"When true, the 'neighborhood amenities' section is displayed in listing creation/edit and the public listing view",
},
{
name: FeatureFlagEnum.enableNeighborhoodAmenitiesDropdown,
description:
'When true, neighborhood amenities inputs render as dropdowns with distance options instead of textareas',
},
{
name: FeatureFlagEnum.enableNonRegulatedListings,
description:
Expand Down
1 change: 1 addition & 0 deletions shared-helpers/src/types/backend-swagger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7582,6 +7582,7 @@ export enum FeatureFlagEnum {
"enableListingUpdatedAt" = "enableListingUpdatedAt",
"enableMarketingStatus" = "enableMarketingStatus",
"enableNeighborhoodAmenities" = "enableNeighborhoodAmenities",
"enableNeighborhoodAmenitiesDropdown" = "enableNeighborhoodAmenitiesDropdown",
"enableNonRegulatedListings" = "enableNonRegulatedListings",
"enablePartnerDemographics" = "enablePartnerDemographics",
"enablePartnerSettings" = "enablePartnerSettings",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,303 @@
import React from "react"
import { screen, waitFor, within } from "@testing-library/react"
import { FormProvider, useForm } from "react-hook-form"
import { AuthContext } from "@bloom-housing/shared-helpers"
import {
FeatureFlagEnum,
Jurisdiction,
JurisdictionsService,
NeighborhoodAmenitiesEnum,
} from "@bloom-housing/shared-helpers/src/types/backend-swagger"
import { t } from "@bloom-housing/ui-components"
import { mockNextRouter, render } from "../../../../testUtils"
import { formDefaults, FormListing } from "../../../../../src/lib/listings/formTypes"
import NeighborhoodAmenities from "../../../../../src/components/listings/PaperListingForm/sections/NeighborhoodAmenities"

const FormComponent = ({ children, values }: { values?: FormListing; children }) => {
const formMethods = useForm<FormListing>({
defaultValues: { ...formDefaults, ...values },
shouldUnregister: false,
})
return <FormProvider {...formMethods}>{children}</FormProvider>
}

beforeAll(() => {
mockNextRouter()
})

describe("NeighborhoodAmenities", () => {
const mockJurisdictionWithAllAmenities = {
id: "jurisdiction1",
name: "Test Jurisdiction",
visibleNeighborhoodAmenities: [
NeighborhoodAmenitiesEnum.groceryStores,
NeighborhoodAmenitiesEnum.publicTransportation,
NeighborhoodAmenitiesEnum.schools,
NeighborhoodAmenitiesEnum.parksAndCommunityCenters,
NeighborhoodAmenitiesEnum.pharmacies,
NeighborhoodAmenitiesEnum.healthCareResources,
],
}

const mockJurisdictionWithLimitedAmenities = {
id: "jurisdiction2",
name: "Limited Jurisdiction",
visibleNeighborhoodAmenities: [
NeighborhoodAmenitiesEnum.groceryStores,
NeighborhoodAmenitiesEnum.publicTransportation,
],
}

it("should not render when feature flag is disabled", () => {
const doJurisdictionsHaveFeatureFlagOn = () => false

const { container } = render(
<AuthContext.Provider
value={{
doJurisdictionsHaveFeatureFlagOn,
}}
>
<FormComponent
values={{ ...formDefaults, jurisdictions: { id: "jurisdiction1" } as Jurisdiction }}
>
<NeighborhoodAmenities />
</FormComponent>
</AuthContext.Provider>
)

expect(container.firstChild).toBeNull()
})

it("should not render when no jurisdiction is selected", () => {
const doJurisdictionsHaveFeatureFlagOn = () => true

const { container } = render(
<AuthContext.Provider
value={{
doJurisdictionsHaveFeatureFlagOn,
}}
>
<FormComponent>
<NeighborhoodAmenities />
</FormComponent>
</AuthContext.Provider>
)

expect(container.firstChild).toBeNull()
})

it("should render all neighborhood amenities as textareas when dropdown is disabled", async () => {
const mockRetrieve = jest.fn().mockResolvedValue(mockJurisdictionWithAllAmenities)

const doJurisdictionsHaveFeatureFlagOn = (flag: FeatureFlagEnum) => {
if (flag === FeatureFlagEnum.enableNeighborhoodAmenities) return true
if (flag === FeatureFlagEnum.enableNeighborhoodAmenitiesDropdown) return false
return false
}

render(
<AuthContext.Provider
value={{
doJurisdictionsHaveFeatureFlagOn,
jurisdictionsService: {
retrieve: mockRetrieve,
} as unknown as JurisdictionsService,
}}
>
<FormComponent
values={{ ...formDefaults, jurisdictions: { id: "jurisdiction1" } as Jurisdiction }}
>
<NeighborhoodAmenities />
</FormComponent>
</AuthContext.Provider>
)

await screen.findByRole("heading", { name: "Neighborhood amenities" })
expect(
screen.getByText(
"Provide details about any local amenities including grocery stores, health services and parks within 2 miles of your listing."
)
).toBeInTheDocument()

// Test all visible amenities are rendered as textareas
for (const amenity of mockJurisdictionWithAllAmenities.visibleNeighborhoodAmenities) {
const amenityLabel = t(`listings.amenities.${amenity}`)
const textarea = await screen.findByRole("textbox", { name: amenityLabel })
expect(textarea).toBeInTheDocument()
expect(textarea.tagName).toBe("TEXTAREA")
}
})

it("should render neighborhood amenities with dropdowns when dropdown is enabled", async () => {
const mockRetrieve = jest.fn().mockResolvedValue(mockJurisdictionWithAllAmenities)

const doJurisdictionsHaveFeatureFlagOn = (flag: FeatureFlagEnum) => {
if (flag === FeatureFlagEnum.enableNeighborhoodAmenities) return true
if (flag === FeatureFlagEnum.enableNeighborhoodAmenitiesDropdown) return true
return false
}

render(
<AuthContext.Provider
value={{
doJurisdictionsHaveFeatureFlagOn,
jurisdictionsService: {
retrieve: mockRetrieve,
} as unknown as JurisdictionsService,
}}
>
<FormComponent
values={{
...formDefaults,
jurisdictions: { id: "jurisdiction1" } as Jurisdiction,
}}
>
<NeighborhoodAmenities />
</FormComponent>
</AuthContext.Provider>
)

await waitFor(() => {
expect(screen.getByRole("heading", { name: "Neighborhood amenities" })).toBeInTheDocument()
})
expect(
screen.getByText(
"Provide details about any local amenities including grocery stores, health services and parks within 2 miles of your listing."
)
).toBeInTheDocument()

// Test all visible amenities are rendered as SELECT dropdowns
for (const amenity of mockJurisdictionWithAllAmenities.visibleNeighborhoodAmenities) {
const amenityLabel = t(`listings.amenities.${amenity}`)
const select = screen.getByRole("combobox", { name: amenityLabel })
expect(select).toBeInTheDocument()
expect(select.tagName).toBe("SELECT")
}
})

it("should only render visible amenities from jurisdiction configuration", async () => {
const mockRetrieve = jest.fn().mockResolvedValue(mockJurisdictionWithLimitedAmenities)

const doJurisdictionsHaveFeatureFlagOn = (flag: FeatureFlagEnum) => {
if (flag === FeatureFlagEnum.enableNeighborhoodAmenities) return true
if (flag === FeatureFlagEnum.enableNeighborhoodAmenitiesDropdown) return false
return false
}

render(
<AuthContext.Provider
value={{
doJurisdictionsHaveFeatureFlagOn,
jurisdictionsService: {
retrieve: mockRetrieve,
} as unknown as JurisdictionsService,
}}
>
<FormComponent
values={{
...formDefaults,
jurisdictions: { id: "jurisdiction2" } as Jurisdiction,
}}
>
<NeighborhoodAmenities />
</FormComponent>
</AuthContext.Provider>
)

await screen.findByRole("heading", { name: "Neighborhood amenities" })

expect(screen.getByRole("textbox", { name: "Grocery stores" })).toBeInTheDocument()
expect(screen.getByRole("textbox", { name: "Public transportation" })).toBeInTheDocument()

expect(screen.queryByRole("textbox", { name: "Schools" })).not.toBeInTheDocument()
expect(
screen.queryByRole("textbox", { name: "Parks and community centers" })
).not.toBeInTheDocument()
expect(screen.queryByRole("textbox", { name: "Pharmacies" })).not.toBeInTheDocument()
expect(screen.queryByRole("textbox", { name: "Health care resources" })).not.toBeInTheDocument()
})

it("should include distance options in dropdown when enabled", async () => {
const mockRetrieve = jest.fn().mockResolvedValue(mockJurisdictionWithLimitedAmenities)

const doJurisdictionsHaveFeatureFlagOn = (flag: FeatureFlagEnum) => {
if (flag === FeatureFlagEnum.enableNeighborhoodAmenities) return true
if (flag === FeatureFlagEnum.enableNeighborhoodAmenitiesDropdown) return true
return false
}

render(
<AuthContext.Provider
value={{
doJurisdictionsHaveFeatureFlagOn,
jurisdictionsService: {
retrieve: mockRetrieve,
} as unknown as JurisdictionsService,
}}
>
<FormComponent
values={{ ...formDefaults, jurisdictions: { id: "jurisdiction1" } as Jurisdiction }}
>
<NeighborhoodAmenities />
</FormComponent>
</AuthContext.Provider>
)

const select = await screen.findByRole("combobox", { name: "Grocery stores" })
expect(select).toBeInTheDocument()

const options = within(select).getAllByRole("option")

const optionTexts = options.map((option) => option.textContent)
const expectedDistanceOptions = [
"On site",
"One block",
"Two blocks",
"Three blocks",
"Four blocks",
"Five blocks",
"Within one mile",
"Within two miles",
"Within three miles",
"Within four miles",
]
expect(optionTexts).toContain("Select one")
expect(optionTexts.length).toBe(expectedDistanceOptions.length + 1)

for (const distanceOption of expectedDistanceOptions) {
expect(optionTexts).toContain(distanceOption)
}
})

it("should render partial amenities in 1 row when there are less or equal to 2 amenities", async () => {
const mockRetrieve = jest.fn().mockResolvedValue(mockJurisdictionWithLimitedAmenities)

const doJurisdictionsHaveFeatureFlagOn = (flag: FeatureFlagEnum) => {
if (flag === FeatureFlagEnum.enableNeighborhoodAmenities) return true
return false
}

const { container } = render(
<AuthContext.Provider
value={{
doJurisdictionsHaveFeatureFlagOn,
jurisdictionsService: {
retrieve: mockRetrieve,
} as unknown as JurisdictionsService,
}}
>
<FormComponent
values={{ ...formDefaults, jurisdictions: { id: "jurisdiction2" } as Jurisdiction }}
>
<NeighborhoodAmenities />
</FormComponent>
</AuthContext.Provider>
)

await screen.findByRole("heading", { name: "Neighborhood amenities" })

// Check that Grid.Row elements are created (2 amenities = 1 row)
const rows = container.querySelectorAll('[class*="grid-row"]')
expect(rows.length).toBe(1)
})
})
10 changes: 10 additions & 0 deletions sites/partners/page_content/locale_overrides/general.json
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,16 @@
"nav.flags": "Flags",
"nav.siteTitlePartners": "Partners Portal",
"nav.users": "Users",
"neighborhoodAmenities.distance.onSite": "On site",
"neighborhoodAmenities.distance.oneBlock": "One block",
"neighborhoodAmenities.distance.twoBlocks": "Two blocks",
"neighborhoodAmenities.distance.threeBlocks": "Three blocks",
"neighborhoodAmenities.distance.fourBlocks": "Four blocks",
"neighborhoodAmenities.distance.fiveBlocks": "Five blocks",
"neighborhoodAmenities.distance.withinOneMile": "Within one mile",
"neighborhoodAmenities.distance.withinTwoMiles": "Within two miles",
"neighborhoodAmenities.distance.withinThreeMiles": "Within three miles",
"neighborhoodAmenities.distance.withinFourMiles": "Within four miles",
"settings.createCopy": "Make a copy",
"settings.createCopyDescription": "Create a copy of your preference.",
"settings.preference": "Preference",
Expand Down
Loading
Loading