From 080ee5575fea82a70df1a3633b7359a2e938a15f Mon Sep 17 00:00:00 2001 From: Alistair Laing Date: Wed, 27 Nov 2024 11:41:32 +0000 Subject: [PATCH] WIP - Adding radio buttons for occupancy attributes The main functionality seems to be working and should be a case of tidying up the types, adding tests and modify existing tests to pass --- .../manage/bedspaceSearch.ts | 16 ++++----- .../manage/bedspaceSearch.cy.ts | 19 ++++++----- server/@types/ui/index.d.ts | 7 ++++ .../manage/bedspaceSearchController.ts | 33 ++++++++++++++----- .../factories/bedspaceSearchFormParameters.ts | 15 +++++++++ server/testutils/factories/index.ts | 2 ++ .../partials/search-fields.njk | 33 +++++++++---------- 7 files changed, 81 insertions(+), 44 deletions(-) create mode 100644 server/testutils/factories/bedspaceSearchFormParameters.ts diff --git a/cypress_shared/pages/temporary-accommodation/manage/bedspaceSearch.ts b/cypress_shared/pages/temporary-accommodation/manage/bedspaceSearch.ts index 46bf044de..60794df5f 100644 --- a/cypress_shared/pages/temporary-accommodation/manage/bedspaceSearch.ts +++ b/cypress_shared/pages/temporary-accommodation/manage/bedspaceSearch.ts @@ -1,15 +1,11 @@ -import { - TemporaryAccommodationBedSearchParameters as BedSearchParameters, - BedSearchResults, - Room, - TemporaryAccommodationBedSearchResult, -} from '../../../../server/@types/shared' -import { PlaceContext } from '../../../../server/@types/ui' +import { BedSearchResults, Room, TemporaryAccommodationBedSearchResult } from '../../../../server/@types/shared' +import { BedspaceSearchFormParameters, PlaceContext } from '../../../../server/@types/ui' import paths from '../../../../server/paths/temporary-accommodation/manage' import BedspaceSearchResult from '../../../components/bedspaceSearchResult' import Page from '../../page' import { addPlaceContext } from '../../../../server/utils/placeUtils' +import { bedspaceSearchFormParametersFactory } from '../../../../server/testutils/factories' export default class BedspaceSearchPage extends Page { private readonly bedspaceSearchResults: Map @@ -44,7 +40,7 @@ export default class BedspaceSearchPage extends Page { cy.get('h2').should('contain', 'There are no available bedspaces') } - shouldShowPrefilledSearchParameters(searchParameters: BedSearchParameters) { + shouldShowPrefilledSearchParameters(searchParameters: BedspaceSearchFormParameters) { this.shouldShowDateInputsByLegend('Available from', searchParameters.startDate) this.shouldShowTextInputByLabel('Number of days required', `${searchParameters.durationDays}`) searchParameters.probationDeliveryUnits.forEach(pduId => { @@ -56,7 +52,7 @@ export default class BedspaceSearchPage extends Page { this.shouldShowDateInputsByLegend('Available from', placeContext.assessment.accommodationRequiredFromDate) } - completeForm(searchParameters: BedSearchParameters) { + completeForm(searchParameters: BedspaceSearchFormParameters) { this.completeDateInputsByLegend('Available from', searchParameters.startDate) this.completeTextInputByLabel('Number of days required', `${searchParameters.durationDays}`) @@ -65,7 +61,7 @@ export default class BedspaceSearchPage extends Page { }) this.getLegend('Property attributes') - this.getLegend('Occupancy (optional)') + this.getLegend('Occupancy') searchParameters.attributes .filter(attribute => attribute === 'wheelchairAccessible') .forEach(attribute => { diff --git a/integration_tests/tests/temporary-accommodation/manage/bedspaceSearch.cy.ts b/integration_tests/tests/temporary-accommodation/manage/bedspaceSearch.cy.ts index 24b936546..87db43a10 100644 --- a/integration_tests/tests/temporary-accommodation/manage/bedspaceSearch.cy.ts +++ b/integration_tests/tests/temporary-accommodation/manage/bedspaceSearch.cy.ts @@ -5,9 +5,9 @@ import BedspaceShowPage from '../../../../cypress_shared/pages/temporary-accommo import BookingShowPage from '../../../../cypress_shared/pages/temporary-accommodation/manage/bookingShow' import { setupTestUser } from '../../../../cypress_shared/utils/setupTestUser' import { - bedSearchParametersFactory, bedSearchResultFactory, bedSearchResultsFactory, + bedspaceSearchFormParametersFactory, bookingFactory, overlapFactory, personFactory, @@ -55,7 +55,7 @@ context('Bedspace Search', () => { const results = bedSearchResultsFactory.build() cy.task('stubBedSearch', results) - const searchParameters = bedSearchParametersFactory.build() + const searchParameters = bedspaceSearchFormParametersFactory.build() preSearchPage.completeForm(searchParameters) preSearchPage.clickSubmit() @@ -67,7 +67,8 @@ context('Bedspace Search', () => { expect(requestBody.startDate).equal(searchParameters.startDate) expect(requestBody.durationDays).equal(searchParameters.durationDays) expect(requestBody.probationDeliveryUnits).to.have.members(searchParameters.probationDeliveryUnits) - expect(requestBody.attributes).to.have.members(searchParameters.attributes) + cy.task('log', searchParameters.occupancyAttribute) + expect(requestBody.occupancyAttributes).equal(searchParameters.occupancyAttribute) }) // And I should see the search results @@ -95,7 +96,7 @@ context('Bedspace Search', () => { }) cy.task('stubBedSearch', results) - const searchParameters = bedSearchParametersFactory.build() + const searchParameters = bedspaceSearchFormParametersFactory.build() preSearchPage.completeForm(searchParameters) preSearchPage.clickSubmit() @@ -107,7 +108,7 @@ context('Bedspace Search', () => { expect(requestBody.startDate).equal(searchParameters.startDate) expect(requestBody.durationDays).equal(searchParameters.durationDays) expect(requestBody.probationDeliveryUnits).to.include.members(searchParameters.probationDeliveryUnits) - expect(requestBody.attributes).to.have.members(searchParameters.attributes) + expect(requestBody.occupancyAttributes).equal(searchParameters.occupancyAttribute) }) // And I should see empty search results @@ -137,7 +138,7 @@ context('Bedspace Search', () => { cy.task('stubSinglePremises', premises) cy.task('stubSingleRoom', { premisesId: premises.id, room }) - const searchParameters = bedSearchParametersFactory.build() + const searchParameters = bedspaceSearchFormParametersFactory.build() preSearchPage.completeForm(searchParameters) preSearchPage.clickSubmit() @@ -188,7 +189,7 @@ context('Bedspace Search', () => { cy.task('stubBooking', { premisesId: premises.id, booking }) // And when I fill out the form - const searchParameters = bedSearchParametersFactory.build() + const searchParameters = BedspaceSearchFormParametersFactory.build() preSearchPage.completeForm(searchParameters) preSearchPage.clickSubmit() @@ -236,7 +237,7 @@ context('Bedspace Search', () => { const results = bedSearchResultsFactory.build() cy.task('stubBedSearch', results) - const searchParameters = bedSearchParametersFactory.build() + const searchParameters = BedspaceSearchFormParametersFactory.build() preSearchPage.completeForm(searchParameters) preSearchPage.clickSubmit() @@ -269,7 +270,7 @@ context('Bedspace Search', () => { const results = bedSearchResultsFactory.build() cy.task('stubBedSearch', results) - const searchParameters = bedSearchParametersFactory.build() + const searchParameters = bedspaceSearchFormParametersFactory.build() preSearchPage.completeForm(searchParameters) preSearchPage.clickSubmit() diff --git a/server/@types/ui/index.d.ts b/server/@types/ui/index.d.ts index 0a1cf210e..55cdb1733 100644 --- a/server/@types/ui/index.d.ts +++ b/server/@types/ui/index.d.ts @@ -21,6 +21,7 @@ import { SortDirection, TemporaryAccommodationApplication, TemporaryAccommodationAssessmentStatus, + TemporaryAccommodationBedSearchParameters, User, } from '@approved-premises/api' import { CallConfig } from '../../data/restClient' @@ -340,3 +341,9 @@ export type PrimaryNavigationItem = { text: string active?: boolean } + +export type BedspaceSearchFormParameters = Omit & { + occupancyAttribute: OccupancyAttribute +} + +export type OccupancyAttribute = 'all' | 'sharedProperty' | 'singleOccupancy' diff --git a/server/controllers/temporary-accommodation/manage/bedspaceSearchController.ts b/server/controllers/temporary-accommodation/manage/bedspaceSearchController.ts index 93267db35..e562afcdb 100644 --- a/server/controllers/temporary-accommodation/manage/bedspaceSearchController.ts +++ b/server/controllers/temporary-accommodation/manage/bedspaceSearchController.ts @@ -1,6 +1,6 @@ import type { Request, RequestHandler, Response } from 'express' import { BedSearchResults } from '../../../@types/shared' -import { ObjectWithDateParts } from '../../../@types/ui' +import { BedspaceSearchFormParameters, ObjectWithDateParts } from '../../../@types/ui' import paths from '../../../paths/temporary-accommodation/manage' import { AssessmentsService } from '../../../services' @@ -13,8 +13,10 @@ import { catchValidationErrorOrPropogate, fetchErrorsAndUserInput, setUserInput export const DEFAULT_DURATION_DAYS = 84 -type BedspaceSearchQuery = ObjectWithDateParts<'startDate'> & { - probationDeliveryUnits: Array +type APISearchQuery = Omit & { + /** + * The number of days the Bed will need to be free from the start_date until + */ durationDays: string } @@ -35,7 +37,17 @@ export default class BedspaceSearchController { const { pdus: allPdus } = await this.searchService.getReferenceData(callConfig) const query = - (req.query as BedspaceSearchQuery).durationDays !== undefined ? (req.query as BedspaceSearchQuery) : undefined + (req.query as unknown as BedspaceSearchFormParameters).durationDays !== undefined + ? (req.query as unknown as BedspaceSearchFormParameters) + : undefined + + const apiQueryParams: APISearchQuery = (query !== undefined)? + { + startDate: query?.startDate, + durationDays: query?.durationDays, + probationDeliveryUnits: query?.probationDeliveryUnits, + ...Object.fromEntries(Object.entries(query).filter(([key]) => key !== 'occupancyAttribute')), + } : undefined const placeContextArrivalDate = placeContext?.assessment.accommodationRequiredFromDate const startDatePrefill = placeContextArrivalDate @@ -46,17 +58,21 @@ export default class BedspaceSearchController { let startDate: string try { - if (query) { + if (apiQueryParams) { startDate = DateFormats.dateAndTimeInputsToIsoString( - query as ObjectWithDateParts<'startDate'>, + apiQueryParams as ObjectWithDateParts<'startDate'>, 'startDate', ).startDate - const durationDays = parseNumber(query.durationDays) + if (query.occupancyAttribute !== 'all') { + apiQueryParams.attributes = [query.occupancyAttribute, ...(apiQueryParams?.attributes || [])] + } + + const durationDays = parseNumber(apiQueryParams.durationDays as APISearchQuery['durationDays']) results = ( await this.searchService.search(callConfig, { - ...query, + ...apiQueryParams, startDate, durationDays, }) @@ -76,6 +92,7 @@ export default class BedspaceSearchController { errors, errorSummary, durationDays: req.query.durationDays || DEFAULT_DURATION_DAYS, + occupancyAttribute: req.query.occupancyAttribute || 'all', ...startDatePrefill, ...query, ...userInput, diff --git a/server/testutils/factories/bedspaceSearchFormParameters.ts b/server/testutils/factories/bedspaceSearchFormParameters.ts new file mode 100644 index 000000000..180872b83 --- /dev/null +++ b/server/testutils/factories/bedspaceSearchFormParameters.ts @@ -0,0 +1,15 @@ +import { fakerEN_GB as faker } from '@faker-js/faker' +import { Factory } from 'fishery' + +import { BedspaceSearchFormParameters, OccupancyAttribute } from '../../@types/ui' +import { DateFormats } from '../../utils/dateUtils' +import referenceData from './referenceData' + +const occupancyAttributes: Array = ['all', 'singleOccupancy', 'sharedProperty'] + +export default Factory.define(() => ({ + startDate: DateFormats.dateObjToIsoDate(faker.date.soon()), + durationDays: faker.number.int({ min: 1, max: 10 }), + probationDeliveryUnits: [referenceData.pdu().build().id, referenceData.pdu().build().id], + occupancyAttribute: faker.helpers.arrayElement(occupancyAttributes), +})) diff --git a/server/testutils/factories/index.ts b/server/testutils/factories/index.ts index 843d1e078..04db97303 100644 --- a/server/testutils/factories/index.ts +++ b/server/testutils/factories/index.ts @@ -11,6 +11,7 @@ import bedFactory from './bed' import bedSearchParametersFactory from './bedSearchParameters' import bedSearchResultFactory from './bedSearchResult' import bedSearchResultsFactory from './bedSearchResults' +import bedspaceSearchFormParametersFactory from './bedspaceSearchFormParameters' import bookingFactory from './booking' import bookingSearchParametersFactory from './bookingSearchParameters' import bookingSearchResultFactory from './bookingSearchResult' @@ -83,6 +84,7 @@ export { bedSearchParametersFactory, bedSearchResultFactory, bedSearchResultsFactory, + bedspaceSearchFormParametersFactory, bookingFactory, bookingSearchParametersFactory, bookingSearchResultBedSummaryFactory, diff --git a/server/views/temporary-accommodation/bedspace-search/partials/search-fields.njk b/server/views/temporary-accommodation/bedspace-search/partials/search-fields.njk index b684ffb32..1ab02f797 100644 --- a/server/views/temporary-accommodation/bedspace-search/partials/search-fields.njk +++ b/server/views/temporary-accommodation/bedspace-search/partials/search-fields.njk @@ -1,5 +1,6 @@ {% from "govuk/components/fieldset/macro.njk" import govukFieldset %} {% from "../../../components/formFields/form-page-checkboxes/macro.njk" import formPageCheckboxes %} +{% from "../../../components/formFields/form-page-radios/macro.njk" import formPageRadios %} {% from "../../../components/formFields/form-page-date-input/macro.njk" import formPageDateInput %} {% from "../../../components/formFields/form-page-input/macro.njk" import formPageInput %} {% from "../../../components/formFields/form-page-select/macro.njk" import formPageSelect %} @@ -65,23 +66,21 @@ classes: "govuk-fieldset__legend--m" } }) %} - {{ formPageCheckboxes({ - classes: "govuk-checkboxes--small", - fieldName: "attributes", - fieldset: { - legend: { - text: "Occupancy (optional)", - classes: "govuk-fieldset__legend--s" - } - }, - hint: { - text: "Select all that apply" - }, - items: [ - { text: "Single", value: "singleOccupancy" }, - { text: "Shared", value: "sharedProperty" } - ] - }, fetchContext()) }} + {{ formPageRadios({ + classes: "govuk-radios--small", + fieldName: "occupancyAttribute", + fieldset: { + legend: { + text: "Occupancy", + classes: "govuk-fieldset__legend--s" + } + }, + items: [ + { text: "All", value: "all" }, + { text: "Single", value: "singleOccupancy" }, + { text: "Shared", value: "sharedProperty" } + ] + }, fetchContext()) }} {% endcall %}