Skip to content

Commit

Permalink
Add remaining template elements
Browse files Browse the repository at this point in the history
Commit integration tests.
  • Loading branch information
froddd committed Dec 19, 2024
1 parent 94fa7f5 commit 123a7e1
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 42 deletions.
71 changes: 71 additions & 0 deletions integration_tests/pages/match/dayAvailabilityPage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { Cas1PremiseCapacityForDay, Cas1SpaceBookingCharacteristic } from '@approved-premises/api'
import Page from '../page'
import { dayAvailabilityCount, occupancyCriteriaMap } from '../../../server/utils/match/occupancy'
import { DateFormats } from '../../../server/utils/dateUtils'

type Availability = 'Available' | 'Overbooked' | 'Available for your criteria'

export default class DayAvailabilityPage extends Page {
availability: Availability

constructor(
private readonly dayCapacity: Cas1PremiseCapacityForDay,
private readonly criteria: Array<Cas1SpaceBookingCharacteristic> = [],
) {
let availability: Availability = dayAvailabilityCount(dayCapacity) > 0 ? 'Available' : 'Overbooked'

if (criteria.length) {
if (dayAvailabilityCount(dayCapacity, criteria) > 0 && availability === 'Overbooked') {
availability = 'Available for your criteria'
}
}

super(availability)

this.availability = availability
}

shouldShowDayAvailability() {
const uiDate = DateFormats.isoDateToUIDate(this.dayCapacity.date, { format: 'long' })
cy.get('h2').should('contain.text', uiDate)

if (this.availability === 'Available') {
cy.get('p').should('contain.text', 'The space you require is available.')
} else if (this.availability === 'Overbooked') {
cy.get('p').should('contain.text', 'This AP is full or overbooked. The space you require is not available.')
} else if (this.availability === 'Available for your criteria') {
cy.get('p').should(
'contain.text',
'This AP is full or overbooked, but the space you require is available as it is occupied by someone who does not need it.',
)
}

const summaryList = {
'AP capacity': this.dayCapacity.totalBedCount,
'Booked spaces': this.dayCapacity.bookingCount,
}

if (this.criteria.length) {
this.criteria.forEach(criteria => {
const criteriaLabel = occupancyCriteriaMap[criteria]
const criteriaAvailability = this.dayCapacity.characteristicAvailability.find(
characteristic => characteristic.characteristic === criteria,
)
summaryList[`${criteriaLabel} spaces available`] =
criteriaAvailability.availableBedsCount - criteriaAvailability.bookingsCount
})
} else {
summaryList['Available spaces'] = dayAvailabilityCount(this.dayCapacity)
}
this.shouldContainAvailabilitySummary(summaryList)
}

shouldContainAvailabilitySummary(items: Record<string, string | number>) {
Object.entries(items).forEach(([key, value]) => {
cy.get('.govuk-summary-list__key')
.contains(key)
.siblings('.govuk-summary-list__value')
.should('contain.text', value)
})
}
}
11 changes: 11 additions & 0 deletions integration_tests/pages/match/occupancyViewPage.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
ApType,
Cas1PremiseCapacity,
Cas1PremiseCapacityForDay,
Cas1PremisesSummary,
Cas1SpaceBookingCharacteristic,
PlacementRequest,
Expand Down Expand Up @@ -113,4 +114,14 @@ export default class OccupancyViewPage extends Page {
cy.get('.govuk-error-summary').should('contain', message)
cy.get(`.govuk-error-message`).should('contain', message)
}

getOccupancyForDate(date: Date, capacity: Cas1PremiseCapacity): Cas1PremiseCapacityForDay {
return capacity.capacity.find(day => day.date === DateFormats.dateObjToIsoDate(date))
}

clickCalendarDay(date: string) {
const calendarDate = DateFormats.isoDateToUIDate(date, { format: 'longNoYear' })

cy.get('.calendar__day').contains(calendarDate).click()
}
}
85 changes: 47 additions & 38 deletions integration_tests/tests/match/match.cy.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { Cas1SpaceSearchParameters, PlacementCriteria } from '@approved-premises/api'
import {
Cas1PremiseCapacity,
Cas1PremisesSummary,
Cas1SpaceSearchParameters,
PlacementCriteria,
} from '@approved-premises/api'
import { addDays } from 'date-fns'
import SearchPage from '../../pages/match/searchPage'
import UnableToMatchPage from '../../pages/match/unableToMatchPage'

Expand All @@ -19,6 +25,7 @@ import ListPage from '../../pages/admin/placementApplications/listPage'
import { filterOutAPTypes, placementDates } from '../../../server/utils/match'
import BookASpacePage from '../../pages/match/bookASpacePage'
import OccupancyViewPage from '../../pages/match/occupancyViewPage'
import DayAvailabilityPage from '../../pages/match/dayAvailabilityPage'

context('Placement Requests', () => {
beforeEach(() => {
Expand Down Expand Up @@ -183,49 +190,45 @@ context('Placement Requests', () => {
placementRequest,
managerDetails,
)
return { occupancyViewPage, placementRequest, premiseCapacity, premises }
return { occupancyViewPage, placementRequest, premiseCapacity, premises, startDate }
}

it('allows me to view spaces and occupancy capacity and filter the result', () => {
const apType = 'normal'
const durationDays = 15
const startDate = '2024-07-23'
const endDate = '2024-08-07'
const totalCapacity = 10
const managerDetails = 'John Doe'

// Given I am signed in as a cru_member
signIn(['cru_member'], ['cas1_space_booking_create'])

// And there is a placement request waiting for me to match
const person = personFactory.build()
const premises = cas1PremisesSummaryFactory.build({ bedCount: totalCapacity })
const placementRequest = placementRequestDetailFactory.build({
person,
expectedArrival: startDate,
duration: durationDays,
const shouldShowDayDetailsAndReturn = (
occupancyViewPage: OccupancyViewPage,
date: Date,
premises: Cas1PremisesSummary,
premiseCapacity: Cas1PremiseCapacity,
) => {
const dayCapacity = occupancyViewPage.getOccupancyForDate(date, premiseCapacity)
const premiseCapacityForDay = cas1PremiseCapacityFactory.build({
premise: premiseCapacity.premise,
startDate: dayCapacity.date,
endDate: dayCapacity.date,
capacity: [dayCapacity],
})
const premiseCapacity = cas1PremiseCapacityFactory.build({
premise: { id: premises.id, bedCount: totalCapacity, managerDetails },
startDate,
endDate,
cy.task('stubPremiseCapacity', {
premisesId: premises.id,
startDate: dayCapacity.date,
endDate: dayCapacity.date,
premiseCapacity: premiseCapacityForDay,
})

cy.task('stubSinglePremises', premises)
cy.task('stubPlacementRequest', placementRequest)
cy.task('stubPremiseCapacity', { premisesId: premises.id, startDate, endDate, premiseCapacity })
// When I click on a day on the calendar
occupancyViewPage.clickCalendarDay(dayCapacity.date)

// When I visit the occupancy view page
const occupancyViewPage = OccupancyViewPage.visit(placementRequest, premises, apType)
// Then I should see the page showing details for the day
const dayAvailabilityPage = new DayAvailabilityPage(dayCapacity)

// Then I should see the details of the case I am matching
occupancyViewPage.shouldShowMatchingDetails(
totalCapacity,
startDate,
durationDays,
placementRequest,
managerDetails,
)
// And I should see availability details
dayAvailabilityPage.shouldShowDayAvailability()

// When I click back
dayAvailabilityPage.clickBack()
}

it('allows me to view spaces and occupancy capacity and filter the result', () => {
const { occupancyViewPage, premiseCapacity, premises, startDate } =
shouldVisitOccupancyViewPageAndShowMatchingDetails()

// And I should see the filter form with populated values
occupancyViewPage.shouldShowFilters(startDate, 'Up to 6 weeks', [])
Expand All @@ -236,6 +239,12 @@ context('Placement Requests', () => {
// And I should see an occupancy calendar
occupancyViewPage.shouldShowOccupancyCalendar(premiseCapacity)

// Then I should see the calendar again
occupancyViewPage.shouldShowOccupancyCalendar(premiseCapacity)

// And I should be able to see the day's availability details
shouldShowDayDetailsAndReturn(occupancyViewPage, addDays(startDate, 10), premises, premiseCapacity)

// When I filter with an invalid date
occupancyViewPage.filterAvailability('2025-02-35')

Expand All @@ -250,7 +259,7 @@ context('Placement Requests', () => {
const newDuration = 'Up to 1 week'
const newCriteria = ['Wheelchair accessible', 'Step-free']
const newPremiseCapacity = cas1PremiseCapacityFactory.build({
premise: { id: premises.id, bedCount: totalCapacity, managerDetails },
premise: { id: premises.id, bedCount: premises.bedCount },
startDate: newStartDate,
endDate: newEndDate,
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ describe('OccupancyViewController', () => {
request = createMock<Request>({
user: { token },
flash: flashSpy,
headers: {
referer: '/referrerPath',
},
})

placementRequestService.getPlacementRequest.mockResolvedValue(placementRequestDetail)
Expand Down Expand Up @@ -364,9 +367,11 @@ describe('OccupancyViewController', () => {

expect(premisesService.getCapacity).toHaveBeenCalledWith('SOME_TOKEN', premises.id, date)
expect(response.render).toHaveBeenCalledWith('match/placementRequests/occupancyView/viewDay', {
backlink: '/referrerPath',
pageHeading: dayAvailabilityStatusMap[expectedStatus],
placementRequest: placementRequestDetail,
premises,
date,
status: expectedStatus,
availabilitySummaryListItems: dayAvailabilitySummaryListItems(dayCapacity, criteria),
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,16 +229,19 @@ export default class {
const { id, premisesId, date } = req.params
const { criteria } = req.query

const backlink = req.headers.referer
const placementRequest = await this.placementRequestService.getPlacementRequest(token, id)
const premises = await this.premisesService.find(token, premisesId)
const premisesCapacity = await this.premisesService.getCapacity(token, premisesId, date)
const dayCapacity = premisesCapacity.capacity[0]
const status = dayAvailabilityStatus(dayCapacity, this.criteriaAsArray(criteria))

res.render('match/placementRequests/occupancyView/viewDay', {
backlink,
pageHeading: dayAvailabilityStatusMap[status],
placementRequest,
premises,
date,
status,
availabilitySummaryListItems: dayAvailabilitySummaryListItems(dayCapacity, this.criteriaAsArray(criteria)),
})
Expand Down
34 changes: 30 additions & 4 deletions server/views/match/placementRequests/occupancyView/viewDay.njk
Original file line number Diff line number Diff line change
@@ -1,13 +1,39 @@
{% from "govuk/components/back-link/macro.njk" import govukBackLink %}
{% from "govuk/components/summary-list/macro.njk" import govukSummaryList %}

{% extends "../../layout-with-details.njk" %}

{% set pageTitle = applicationName + " - " + pageHeading %}

{% block beforeContent %}
{{ govukBackLink({
text: "Back",
href: backlink
}) }}
{% endblock %}

{% block content %}
<h1 class="govuk-heading-l">{{ pageHeading }}</h1>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<h1 class="govuk-heading-l">{{ pageHeading }}</h1>

{{ govukSummaryList({
rows: availabilitySummaryListItems
}) }}
<h2 class="govuk-heading-m">{{ formatDate(date) }}</h2>

{% if status === 'available' %}
<p>The space you require is available.</p>
{% endif %}
{% if status === 'availableForCriteria' %}
<p>This AP is full or overbooked, but the space you require is available as it is occupied by someone
who does not need it.</p>
{% endif %}
{% if status === 'overbooked' %}
<p>This AP is full or overbooked. The space you require is not available.</p>
{% endif %}

{{ govukSummaryList({
rows: availabilitySummaryListItems
}) }}

</div>
</div>
{% endblock %}

0 comments on commit 123a7e1

Please sign in to comment.