Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

APS-1775: Update booking confirmation screen #2277

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
5 changes: 2 additions & 3 deletions e2e/pages/match/bookingPage.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { Page, expect } from '@playwright/test'
import { E2EDatesOfPlacement } from 'e2e/steps/assess'
import { BasePage } from '../basePage'
import { Premises } from '../../../server/@types/shared'
import { DateFormats } from '../../../server/utils/dateUtils'

export class BookingPage extends BasePage {
static async initialize(page: Page, premisesName: Premises['name']) {
await expect(page.locator('h1')).toContainText(`Book space in ${premisesName}`)
static async initialize(page: Page) {
await expect(page.locator('h1')).toContainText('Confirm booking')

return new BookingPage(page)
}
Expand Down
4 changes: 2 additions & 2 deletions e2e/steps/match.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export const matchAndBookApplication = async ({
await occupancyViewPage.clickContinue()

// Then I should see the booking screen for that AP
const bookingPage = await BookingPage.initialize(page, premisesName)
const bookingPage = await BookingPage.initialize(page)

// Should show the booking details (inc. new dates)
const newDatesOfPlacement: E2EDatesOfPlacement = {
Expand All @@ -109,7 +109,7 @@ export const matchAndBookApplication = async ({
await bookingPage.shouldShowDatesOfPlacement(newDatesOfPlacement)

// And I confirm the booking
const premisesId = page.url().match(/premisesId=(.[^&]*)/)[1] // premisesId=338e22f3-70be-4519-97ab-f08c6c2dfb0b
const premisesId = page.url().match(/space-bookings\/(.[^/]*)/)[1] // Path: /match/placement-requests/:id/space-bookings/:premisesId/new
await bookingPage.clickConfirm()

// Then I should see the Matched tab on the CRU dashboard
Expand Down
9 changes: 0 additions & 9 deletions integration_tests/mockApis/spaceBooking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import type {
Cas1SpaceBooking,
Cas1SpaceBookingResidency,
Cas1SpaceBookingSummary,
PlacementRequest,
TimelineEvent,
} from '@approved-premises/api'

Expand All @@ -26,14 +25,6 @@ export default {
},
}),

verifySpaceBookingCreate: async (placementRequest: PlacementRequest) =>
(
await getMatchingRequests({
method: 'POST',
url: paths.placementRequests.spaceBookings.create({ id: placementRequest.id }),
})
).body.requests,

stubSpaceBookingShow: (placement: Cas1SpaceBooking) =>
stubFor({
request: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ export default class ListPage extends Page {
return new ListPage()
}

shouldShowSpaceBookingConfirmation(premisesName: string, personName: string) {
this.shouldShowBanner(`Space booked for ${personName} in ${premisesName}`)
shouldShowSpaceBookingConfirmation() {
this.shouldShowBanner(
'You have now booked a place in this AP for this person. An email will be sent to the AP, to inform them of the booking.',
)
}

shouldShowPlacementRequests(placementRequests: Array<PlacementRequest>, status?: PlacementRequestStatus): void {
Expand Down
64 changes: 34 additions & 30 deletions integration_tests/pages/match/bookASpacePage.ts
Original file line number Diff line number Diff line change
@@ -1,54 +1,58 @@
import {
ApType,
Cas1PremisesSummary,
Cas1SpaceBookingCharacteristic,
Cas1SpaceCharacteristic,
PlacementDates,
PlacementRequestDetail,
Premises,
} from '@approved-premises/api'
import { differenceInDays } from 'date-fns'
import Page from '../page'
import paths from '../../../server/paths/match'
import { createQueryString, sentenceCase } from '../../../server/utils/utils'
import { DateFormats } from '../../../server/utils/dateUtils'
import { placementDates, placementLength as placementLengthInDaysAndWeeks } from '../../../server/utils/match'
import { placementCriteriaLabels } from '../../../server/utils/placementCriteriaUtils'
import { apTypeLabels } from '../../../server/utils/apTypeLabels'
import { createQueryString } from '../../../server/utils/utils'
import { DateFormats, daysToWeeksAndDays } from '../../../server/utils/dateUtils'
import { requirementsHtmlString } from '../../../server/utils/match'
import { allReleaseTypes } from '../../../server/utils/applications/releaseTypeUtils'

export default class BookASpacePage extends Page {
constructor(premisesName: string) {
super(`Book space in ${premisesName}`)
constructor() {
super(`Confirm booking`)
}

static visit(
placementRequest: PlacementRequestDetail,
startDate: string,
durationDays: PlacementDates['duration'],
premisesName: Premises['name'],
premisesId: Premises['id'],
apType: ApType,
arrivalDate: string,
departureDate: string,
criteria: Array<Cas1SpaceBookingCharacteristic>,
) {
const queryString = createQueryString({ startDate, durationDays, premisesName, premisesId, apType, criteria })
const path = `${paths.v2Match.placementRequests.spaceBookings.new({ id: placementRequest.id })}?${queryString}`
const queryString = createQueryString({ arrivalDate, departureDate, criteria })
const path = `${paths.v2Match.placementRequests.spaceBookings.new({
id: placementRequest.id,
premisesId,
})}?${queryString}`

cy.visit(path)
return new BookASpacePage(premisesName)

return new BookASpacePage()
}

shouldShowBookingDetails(
placementRequest: PlacementRequestDetail,
startDate: string,
duration: PlacementDates['duration'],
apType: ApType,
criteria?: Array<Cas1SpaceCharacteristic>,
premises: Cas1PremisesSummary,
arrivalDate: string,
departureDate: string,
criteria: Array<Cas1SpaceBookingCharacteristic>,
): void {
const { endDate, placementLength } = placementDates(startDate, duration.toString())
cy.get('dd').contains(apTypeLabels[apType])
cy.get('dd').contains(DateFormats.isoDateToUIDate(startDate))
cy.get('dd').contains(DateFormats.isoDateToUIDate(endDate))
cy.get('dd').contains(placementLengthInDaysAndWeeks(placementLength))
cy.get('dd').contains(sentenceCase(placementRequest.gender))
;(criteria || []).forEach(requirement => {
cy.get('li').contains(placementCriteriaLabels[requirement])
})
this.shouldContainSummaryListItems([
{ key: { text: 'Approved Premises' }, value: { text: premises.name } },
// { key: { text: 'Address' }, value: { text: premises.fullAddress } },
{ key: { text: 'Space type' }, value: { html: requirementsHtmlString(criteria) } },
{ key: { text: 'Arrival date' }, value: { text: DateFormats.isoDateToUIDate(arrivalDate) } },
{ key: { text: 'Departure date' }, value: { text: DateFormats.isoDateToUIDate(departureDate) } },
{
key: { text: 'Length of stay' },
value: { text: DateFormats.formatDuration(daysToWeeksAndDays(differenceInDays(departureDate, arrivalDate))) },
},
{ key: { text: 'Release type' }, value: { text: allReleaseTypes[placementRequest.releaseType] } },
])
}
}
4 changes: 0 additions & 4 deletions integration_tests/pages/match/searchPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,6 @@ export default class SearchPage extends Page {
}

shouldHaveSearchParametersInLinks(newSearchParameters: SpaceSearchParametersUi): void {
cy.get('.govuk-summary-card__actions .govuk-link')
.invoke('attr', 'href')
.should('contain', `apType=${newSearchParameters.requirements.apType}`)

newSearchParameters.requirements.spaceCharacteristics.forEach(spaceCharacteristic => {
const isSpaceBookingCharacteristic = Object.keys(occupancyCriteriaMap).includes(spaceCharacteristic)
cy.get('.govuk-summary-card__actions .govuk-link')
Expand Down
99 changes: 37 additions & 62 deletions integration_tests/tests/match/match.cy.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import {
Cas1PremiseCapacity,
Cas1PremisesSummary,
Cas1SpaceBookingCharacteristic,
Cas1SpaceSearchParameters,
PlacementCriteria,
FullPerson,
} from '@approved-premises/api'
import { addDays } from 'date-fns'
import SearchPage from '../../pages/match/searchPage'
Expand All @@ -22,11 +23,12 @@ import Page from '../../pages/page'
import { signIn } from '../signIn'

import ListPage from '../../pages/admin/placementApplications/listPage'
import { filterOutAPTypes, filterToSpaceBookingCharacteristics, placementDates } from '../../../server/utils/match'
import { filterOutAPTypes } from '../../../server/utils/match'
import BookASpacePage from '../../pages/match/bookASpacePage'
import OccupancyViewPage from '../../pages/match/occupancyViewPage'
import applicationFactory from '../../../server/testutils/factories/application'
import DayAvailabilityPage from '../../pages/match/dayAvailabilityPage'
import apiPaths from '../../../server/paths/api'

context('Placement Requests', () => {
beforeEach(() => {
Expand Down Expand Up @@ -150,20 +152,6 @@ context('Placement Requests', () => {
occupancyViewPage.shouldShowErrorSummaryAndErrorMessage('The departure date must be after the arrival date')
})

it('allows me to submit valid dates in the book your placement form on occupancy view page and redirects to book a space', () => {
const { occupancyViewPage, placementRequest, premises } =
shouldVisitOccupancyViewPageAndShowMatchingDetails(defaultLicenceExpiryDate)

// When I submit valid dates
const arrivalDate = '2024-11-25'
occupancyViewPage.shouldFillBookYourPlacementFormDates(arrivalDate, '2024-11-26')
occupancyViewPage.clickContinue()

// Then I should land on the Book a space page (with the new dates overriding the original ones)
const bookASpacePage = Page.verifyOnPage(BookASpacePage, premises.name)
bookASpacePage.shouldShowBookingDetails(placementRequest, arrivalDate, 1, 'normal')
})

const shouldVisitOccupancyViewPageAndShowMatchingDetails = (licenceExpiryDate: string | undefined) => {
const apType = 'normal'
const durationDays = 15
Expand Down Expand Up @@ -296,49 +284,38 @@ context('Placement Requests', () => {
// Given I am signed in as a cru_member
signIn(['cru_member'], ['cas1_space_booking_create'])

const premisesName = 'Hope House'
const premisesId = 'abc123'
const apType = 'normal'
const durationDays = 15
const startDate = '2024-07-23'
const { endDate } = placementDates(startDate, durationDays.toString())
const premises = cas1PremisesSummaryFactory.build()
cy.task('stubSinglePremises', premises)

const arrivalDate = '2024-07-23'
const departureDate = '2024-08-08'
const criteria: Array<Cas1SpaceBookingCharacteristic> = ['isWheelchairDesignated', 'hasEnSuite']

// And there is a placement request waiting for me to match
const person = personFactory.build()
const essentialCharacteristics: Array<PlacementCriteria> = ['hasEnSuite']
const desirableCharacteristics: Array<PlacementCriteria> = ['isCatered']
const placementRequest = placementRequestDetailFactory.build({
person,
status: 'notMatched',
duration: durationDays,
essentialCriteria: [],
desirableCriteria: desirableCharacteristics,
})

// When I visit the 'Book a space' page
cy.task('stubPlacementRequest', placementRequest)
const page = BookASpacePage.visit(
placementRequest,
startDate,
durationDays,
premisesName,
premisesId,
apType,
filterToSpaceBookingCharacteristics(essentialCharacteristics),
)
const page = BookASpacePage.visit(placementRequest, premises.id, arrivalDate, departureDate, criteria)

// Then I should see the details of the space I am booking
page.shouldShowBookingDetails(
placementRequest,
startDate,
durationDays,
apType,
filterToSpaceBookingCharacteristics(essentialCharacteristics),
)
// Then I should see the details of the case I am matching
page.shouldShowPersonHeader(placementRequest.person as FullPerson)

// And I should see the details of the space I am booking
page.shouldShowBookingDetails(placementRequest, premises, arrivalDate, departureDate, criteria)

// And when I complete the form
const requirements = spaceBookingRequirementsFactory.build()
const spaceBooking = cas1SpaceBookingFactory.build({ requirements })
const requirements = spaceBookingRequirementsFactory.build({
essentialCharacteristics: criteria,
})
const spaceBooking = cas1SpaceBookingFactory.upcoming().build({
expectedArrivalDate: arrivalDate,
expectedDepartureDate: departureDate,
requirements,
})
cy.task('stubSpaceBookingCreate', { placementRequestId: placementRequest.id, spaceBooking })
cy.task('stubPlacementRequestsDashboard', { placementRequests: [placementRequest], status: 'matched' })
page.clickSubmit()
Expand All @@ -347,23 +324,21 @@ context('Placement Requests', () => {
const cruDashboard = Page.verifyOnPage(ListPage)

// And I should see a success message
cruDashboard.shouldShowSpaceBookingConfirmation(premisesName, person.name)
cruDashboard.shouldShowSpaceBookingConfirmation()

// And the booking details should have been sent to the API
cy.task('verifySpaceBookingCreate', placementRequest).then(requests => {
expect(requests).to.have.length(1)
const body = JSON.parse(requests[0].body)

expect(body).to.deep.equal({
arrivalDate: startDate,
departureDate: endDate,
premisesId,
requirements: {
...spaceBooking.requirements,
essentialCharacteristics,
},
})
})
cy.task('verifyApiPost', apiPaths.placementRequests.spaceBookings.create({ id: placementRequest.id })).then(
body => {
expect(body).to.deep.equal({
arrivalDate,
departureDate,
premisesId: premises.id,
requirements: {
essentialCharacteristics: criteria,
},
})
},
)
})

it('allows me to mark a placement request as unable to match', () => {
Expand Down
2 changes: 1 addition & 1 deletion server/controllers/match/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const controllers = (services: Services) => {
)
const spaceSearchController = new SpaceSearchController(spaceService, placementRequestService)
const placementRequestBookingsController = new BookingsController(placementRequestService)
const spaceBookingsController = new SpaceBookingsController(placementRequestService, spaceService)
const spaceBookingsController = new SpaceBookingsController(placementRequestService, premisesService, spaceService)
const occupancyViewController = new OccupancyViewController(placementRequestService, premisesService)

return {
Expand Down
Loading
Loading