diff --git a/integration_tests/e2e/refer/new/programmeHistory.cy.ts b/integration_tests/e2e/refer/new/programmeHistory.cy.ts index e90eebce4..040fe5f20 100644 --- a/integration_tests/e2e/refer/new/programmeHistory.cy.ts +++ b/integration_tests/e2e/refer/new/programmeHistory.cy.ts @@ -38,10 +38,18 @@ context('Programme history', () => { const courseNames = courses.map(course => course.name) const addedByUser1 = userFactory.build({ username: auth.mockedUser.username }) const addedByUser2 = userFactory.build() + + const courseOffering = courseOfferingFactory.build() + const referral = referralFactory.started().build({ + offeringId: courseOffering.id, + prisonNumber: person.prisonNumber, + referrerUsername: addedByUser1.username, + }) const courseParticipationWithKnownCourseName = courseParticipationFactory.build({ addedBy: addedByUser1.username, courseName: courses[0].name, prisonNumber: person.prisonNumber, + referralId: referral.id, }) const courseParticipationWithKnownCourseNamePresenter: CourseParticipationPresenter = { ...courseParticipationWithKnownCourseName, @@ -52,16 +60,6 @@ context('Programme history', () => { courseName: 'An course not in our system', prisonNumber: person.prisonNumber, }) - const courseParticipationWithUnknownCourseNamePresenter: CourseParticipationPresenter = { - ...courseParticipationWithUnknownCourseName, - addedByDisplayName: StringUtils.convertToTitleCase(addedByUser2.name), - } - const courseOffering = courseOfferingFactory.build() - const referral = referralFactory.started().build({ - offeringId: courseOffering.id, - prisonNumber: person.prisonNumber, - referrerUsername: addedByUser1.username, - }) const programmeHistoryPath = referPaths.new.programmeHistory.index({ referralId: referral.id }) const newParticipationPath = referPaths.new.programmeHistory.new({ referralId: referral.id }) const showProgrammeHistoryPath = referPaths.new.programmeHistory.show({ @@ -86,11 +84,11 @@ context('Programme history', () => { const prison = prisonFactory.build({ prisonId: courseOffering.organisationId }) const organisation = OrganisationUtils.organisationFromPrison(prison) - const courseParticipations = [courseParticipationWithKnownCourseName, courseParticipationWithUnknownCourseName] - const courseParticipationsPresenter: Array = [ - courseParticipationWithKnownCourseNamePresenter, - courseParticipationWithUnknownCourseNamePresenter, - ] + const existingParticipations = [courseParticipationWithKnownCourseName, courseParticipationWithUnknownCourseName] + const referralParticipations = courseParticipationFactory.buildList(2, { + isDraft: true, + referralId: referral.id, + }) beforeEach(() => { cy.task('stubCourse', courses[0]) @@ -98,14 +96,20 @@ context('Programme history', () => { describe('when there is an existing programme history', () => { beforeEach(() => { - cy.task('stubParticipationsByPerson', { courseParticipations, prisonNumber: prisoner.prisonerNumber }) + cy.task('stubParticipationsByPerson', { + courseParticipations: existingParticipations, + prisonNumber: prisoner.prisonerNumber, + }) + cy.task('stubParticipationsByReferral', { + courseParticipations: referralParticipations, + referralId: referral.id, + }) cy.visit(programmeHistoryPath) }) it('shows the page with an existing programme history', () => { const programmeHistoryPage = Page.verifyOnPage(NewReferralProgrammeHistoryPage, { - participations: courseParticipationsPresenter, person, referral, }) @@ -113,9 +117,14 @@ context('Programme history', () => { programmeHistoryPage.shouldContainBackLink(referPaths.new.show({ referralId: referral.id })) programmeHistoryPage.shouldContainHomeLink() programmeHistoryPage.shouldNotContainSuccessMessage() - programmeHistoryPage.shouldContainPreHistoryText() programmeHistoryPage.shouldContainPreHistoryParagraph() - programmeHistoryPage.shouldContainHistorySummaryCards(courseParticipationsPresenter, referral.id) + programmeHistoryPage.shouldContainHistoryTable(existingParticipations, referral.id, 'existing-participations') + programmeHistoryPage.shouldContainHistoryTable( + referralParticipations, + referral.id, + 'referral-participations', + true, + ) programmeHistoryPage.shouldContainButtonLink('Add a programme', newParticipationPath) programmeHistoryPage.shouldContainButton('Return to tasklist') }) @@ -130,7 +139,6 @@ context('Programme history', () => { it('updates the referral and redirects to the task list', () => { const programmeHistoryPage = Page.verifyOnPage(NewReferralProgrammeHistoryPage, { - participations: courseParticipationsPresenter, person, referral, }) @@ -149,20 +157,22 @@ context('Programme history', () => { describe('when there is no existing programme history', () => { const emptyCourseParticipations: Array = [] - const emptyCourseParticipationsPresenter: Array = [] beforeEach(() => { cy.task('stubParticipationsByPerson', { courseParticipations: emptyCourseParticipations, prisonNumber: prisoner.prisonerNumber, }) + cy.task('stubParticipationsByReferral', { + courseParticipations: emptyCourseParticipations, + referralId: referral.id, + }) cy.visit(programmeHistoryPath) }) it('shows the page without an existing programme history', () => { const programmeHistoryPage = Page.verifyOnPage(NewReferralProgrammeHistoryPage, { - participations: emptyCourseParticipationsPresenter, person, referral, }) @@ -186,7 +196,6 @@ context('Programme history', () => { it('updates the referral and redirects to the task list', () => { const programmeHistoryPage = Page.verifyOnPage(NewReferralProgrammeHistoryPage, { - participations: emptyCourseParticipationsPresenter, person, referral, }) @@ -419,16 +428,13 @@ context('Programme history', () => { status: formValues.outcome.status, yearCompleted: Number(formValues.outcome), }, + referralId: referral.id, setting: { location: formValues.setting.communityLocation, type: formValues.setting.type, }, source: formValues.source, }) - const updatedCourseParticipationPresenter: CourseParticipationPresenter = { - ...updatedCourseParticipation, - addedByDisplayName: StringUtils.convertToTitleCase(addedByUser1.name), - } const programmeHistoryDetailsPage = Page.verifyOnPage(NewReferralProgrammeHistoryDetailsPage, { course: courses[0], @@ -444,7 +450,6 @@ context('Programme history', () => { programmeHistoryDetailsPage.submitDetails() Page.verifyOnPage(NewReferralProgrammeHistoryPage, { - participations: [{ ...updatedCourseParticipationPresenter, name: courses[0].name }], person, referral, }) @@ -528,18 +533,20 @@ context('Programme history', () => { }) describe('success messages', () => { - const courseParticipations = [courseParticipationWithKnownCourseName] - const courseParticipationsPresenter = [courseParticipationWithKnownCourseNamePresenter] const addedSuccessMessage = 'You have successfully added a programme.' const updatedSuccessMessage = 'You have successfully updated a programme.' beforeEach(() => { cy.task('stubCourseNames', courseNames) + cy.task('stubCourse', courses[0]) cy.task('stubParticipationsByPerson', { - courseParticipations, - prisonNumber: prisoner.prisonerNumber, + courseParticipations: [], + prisonNumber: person.prisonNumber, + }) + cy.task('stubParticipationsByReferral', { + courseParticipations: [courseParticipationWithKnownCourseName], + referralId: referral.id, }) - cy.task('stubCourse', courses[0]) }) describe('when adding a new participation', () => { @@ -563,7 +570,6 @@ context('Programme history', () => { programmeHistoryDetailsPage.submitDetails() const programmeHistoryPage = Page.verifyOnPage(NewReferralProgrammeHistoryPage, { - participations: courseParticipationsPresenter, person, referral, }) @@ -576,7 +582,6 @@ context('Programme history', () => { cy.visit(programmeHistoryPath) const programmeHistoryPage = Page.verifyOnPage(NewReferralProgrammeHistoryPage, { - participations: courseParticipationsPresenter, person, referral, }) @@ -589,7 +594,6 @@ context('Programme history', () => { cy.visit(programmeHistoryPath) Page.verifyOnPage(NewReferralProgrammeHistoryPage, { - participations: courseParticipationsPresenter, person, referral, }) @@ -609,7 +613,6 @@ context('Programme history', () => { programmeHistoryDetailsPage.submitDetails() const programmeHistoryPage = Page.verifyOnPage(NewReferralProgrammeHistoryPage, { - participations: courseParticipationsPresenter, person, referral, }) @@ -643,7 +646,6 @@ context('Programme history', () => { cy.visit(programmeHistoryPath) const programmeHistoryPage = Page.verifyOnPage(NewReferralProgrammeHistoryPage, { - participations: courseParticipationsPresenter, person, referral, }) @@ -702,7 +704,6 @@ context('Programme history', () => { deleteProgrammeHistoryPage.confirm() const programmeHistoryPage = Page.verifyOnPage(NewReferralProgrammeHistoryPage, { - participations: [], person, referral, }) diff --git a/integration_tests/mockApis/courseParticipations.ts b/integration_tests/mockApis/courseParticipations.ts index 7246536f8..44301e67b 100644 --- a/integration_tests/mockApis/courseParticipations.ts +++ b/integration_tests/mockApis/courseParticipations.ts @@ -60,6 +60,22 @@ export default { }, }), + stubParticipationsByReferral: (args: { + courseParticipations: Array + referralId: CourseParticipation['referralId'] + }): SuperAgentRequest => + stubFor({ + request: { + method: 'GET', + url: apiPaths.participations.referral({ referralId: args.referralId }), + }, + response: { + headers: { 'Content-Type': 'application/json;charset=UTF-8' }, + jsonBody: args.courseParticipations, + status: 200, + }, + }), + stubUpdateParticipation: (courseParticipation: CourseParticipation): SuperAgentRequest => stubFor({ request: { diff --git a/integration_tests/pages/page.ts b/integration_tests/pages/page.ts index fa13f70bb..582eb9322 100644 --- a/integration_tests/pages/page.ts +++ b/integration_tests/pages/page.ts @@ -22,7 +22,7 @@ import type { MojTimelineItem, ReferralStatusHistoryPresenter, } from '@accredited-programmes/ui' -import type { Referral } from '@accredited-programmes-api' +import type { CourseParticipation, Referral } from '@accredited-programmes-api' import type { GovukFrontendRadiosItem, GovukFrontendSummaryListCardTitle, @@ -205,6 +205,35 @@ export default abstract class Page { }) } + shouldContainHistoryTable( + participations: Array, + referralId: Referral['id'], + testId: string, + editable = false, + ) { + const { rows } = CourseParticipationUtils.table(participations, referralId, testId, editable) + + cy.get(`[data-testid="${testId}"] .govuk-table__body`).within(() => { + cy.get('.govuk-table__row').each((tableRowElement, tableRowElementIndex) => { + const row = rows[tableRowElementIndex] + + cy.wrap(tableRowElement).within(() => { + row.forEach((cell, cellIndex) => { + cy.get('.govuk-table__cell') + .eq(cellIndex) + .then(cellElement => { + if ('text' in cell) { + cy.wrap(cellElement).should('contain.text', cell.text) + } else { + cy.wrap(cellElement).should('contain.html', cell.html) + } + }) + }) + }) + }) + }) + } + shouldContainHomeLink(): void { cy.get('[data-testid=home-link]').should('have.attr', 'href', '/') } diff --git a/integration_tests/pages/refer/new/deleteProgrammeHistory.ts b/integration_tests/pages/refer/new/deleteProgrammeHistory.ts index 234beebb2..3670b510b 100644 --- a/integration_tests/pages/refer/new/deleteProgrammeHistory.ts +++ b/integration_tests/pages/refer/new/deleteProgrammeHistory.ts @@ -23,6 +23,7 @@ export default class NewReferralDeleteProgrammeHistoryPage extends Page { confirm() { cy.task('stubParticipationsByPerson', { courseParticipations: [], prisonNumber: this.person.prisonNumber }) + cy.task('stubParticipationsByReferral', { courseParticipations: [], referralId: this.referral.id }) this.shouldContainButton('Confirm').click() } } diff --git a/integration_tests/pages/refer/new/programmeHistory.ts b/integration_tests/pages/refer/new/programmeHistory.ts index 4cbc664f3..0fa9c73d4 100644 --- a/integration_tests/pages/refer/new/programmeHistory.ts +++ b/integration_tests/pages/refer/new/programmeHistory.ts @@ -1,24 +1,20 @@ import Helpers from '../../../support/helpers' import Page from '../../page' import type { Person } from '@accredited-programmes/models' -import type { CourseParticipationPresenter } from '@accredited-programmes/ui' import type { Referral } from '@accredited-programmes-api' export default class NewReferralProgrammeHistoryPage extends Page { - participations: Array - person: Person referral: Referral - constructor(args: { participations: Array; person: Person; referral: Referral }) { + constructor(args: { person: Person; referral: Referral }) { super('Accredited Programme history', { hideTitleServiceName: true, pageTitleOverride: "Person's Accredited Programme history", }) - const { participations, person, referral } = args - this.participations = participations + const { person, referral } = args this.person = person this.referral = referral } @@ -55,20 +51,10 @@ export default class NewReferralProgrammeHistoryPage extends Page { shouldContainPreHistoryParagraph() { cy.get('[data-testid="pre-history-paragraph"]').should( 'have.text', - 'Add a programme if you know they completed or started a programme not listed here. Return to the tasklist once you’ve added all known programme history.', + `This is a list of programmes ${this.person.name} has started or completed. You can add missing programme history.`, ) } - shouldContainPreHistoryText() { - cy.get('[data-testid="history-text"]').then(historyTextElement => { - const { actual, expected } = Helpers.parseHtml( - historyTextElement, - `The history shows ${this.person.name} has previously started or completed an Accredited Programme.`, - ) - expect(actual).to.equal(expected) - }) - } - shouldContainSuccessMessage(message: string) { cy.get('[data-testid="success-banner"]').then(successBannerElement => { const { actual, expected } = Helpers.parseHtml(successBannerElement, message) diff --git a/integration_tests/pages/refer/new/programmeHistoryDetails.ts b/integration_tests/pages/refer/new/programmeHistoryDetails.ts index 7593ad9f7..1c0542036 100644 --- a/integration_tests/pages/refer/new/programmeHistoryDetails.ts +++ b/integration_tests/pages/refer/new/programmeHistoryDetails.ts @@ -161,9 +161,13 @@ export default class NewReferralProgrammeHistoryDetailsPage extends Page { cy.task('stubUpdateParticipation', this.courseParticipation) cy.task('stubCourse', this.course) cy.task('stubParticipationsByPerson', { - courseParticipations: [this.courseParticipation], + courseParticipations: [], prisonNumber: this.person.prisonNumber, }) + cy.task('stubParticipationsByReferral', { + courseParticipations: [this.courseParticipation], + referralId: this.courseParticipation.referralId, + }) this.shouldContainButton('Continue').click() } } diff --git a/server/controllers/refer/new/courseParticipationsController.test.ts b/server/controllers/refer/new/courseParticipationsController.test.ts index c9b9c2918..c272baac4 100644 --- a/server/controllers/refer/new/courseParticipationsController.test.ts +++ b/server/controllers/refer/new/courseParticipationsController.test.ts @@ -1,6 +1,7 @@ import type { DeepMocked } from '@golevelup/ts-jest' import { createMock } from '@golevelup/ts-jest' import type { NextFunction, Request, Response } from 'express' +import { when } from 'jest-when' import NewReferralsCourseParticipationsController from './courseParticipationsController' import { referPaths } from '../../../paths' @@ -16,6 +17,7 @@ import Helpers from '../../../testutils/helpers' import { CourseParticipationUtils, CourseUtils, FormUtils } from '../../../utils' import type { GovukFrontendSummaryListWithRowsWithKeysAndValues } from '@accredited-programmes/ui' import type { CourseParticipation } from '@accredited-programmes-api' +import type { GovukFrontendTable } from '@govuk-frontend' jest.mock('../../../utils/formUtils') jest.mock('../../../utils/courseParticipationUtils') @@ -33,6 +35,8 @@ describe('NewReferralsCourseParticipationsController', () => { let controller: NewReferralsCourseParticipationsController + const existingParticipations = courseParticipationFactory.buildList(2) + const referralParticipations = courseParticipationFactory.buildList(2, { isDraft: true }) const summaryListOptions = 'summary list options' as unknown as GovukFrontendSummaryListWithRowsWithKeysAndValues const person = personFactory.build() const referralId = 'a-referral-id' @@ -46,6 +50,8 @@ describe('NewReferralsCourseParticipationsController', () => { controller = new NewReferralsCourseParticipationsController(courseService, personService, referralService) courseService.presentCourseParticipation.mockResolvedValue(summaryListOptions) courseService.getAndPresentParticipationsByPerson.mockResolvedValue([summaryListOptions, summaryListOptions]) + courseService.getParticipationsByPerson.mockResolvedValue(existingParticipations) + courseService.getParticipationsByReferral.mockResolvedValue(referralParticipations) }) afterEach(() => { @@ -326,10 +332,27 @@ describe('NewReferralsCourseParticipationsController', () => { describe('index', () => { const course = courseFactory.build() + const existingParticipationsTable: GovukFrontendTable = { + head: [{ text: 'Existing participations table column header' }], + rows: [[{ text: 'Existing participations table value text' }]], + } + + const referralParticipationsTable: GovukFrontendTable = { + head: [{ text: 'Referral participations table column header' }], + rows: [[{ text: 'Referral participations table value text' }]], + } + beforeEach(() => { personService.getPerson.mockResolvedValue(person) courseService.getCourse.mockResolvedValue(course) ;(request.flash as jest.Mock).mockImplementation(() => []) + + when(CourseParticipationUtils.table) + .calledWith(existingParticipations, referralId, 'existing-participations') + .mockReturnValue(existingParticipationsTable) + when(CourseParticipationUtils.table) + .calledWith(referralParticipations, referralId, 'referral-participations', true) + .mockReturnValue(referralParticipationsTable) }) it("renders the index template for a person's programme history", async () => { @@ -340,47 +363,41 @@ describe('NewReferralsCourseParticipationsController', () => { expect(referralService.getReferral).toHaveBeenCalledWith(username, referralId) expect(personService.getPerson).toHaveBeenCalledWith(username, draftReferral.prisonNumber) - expect(courseService.getAndPresentParticipationsByPerson).toHaveBeenCalledWith( - username, - userToken, - person.prisonNumber, - referralId, - ) + expect(courseService.getParticipationsByPerson).toHaveBeenCalledWith(username, person.prisonNumber) + expect(courseService.getParticipationsByReferral).toHaveBeenCalledWith(username, referralId) expect(response.render).toHaveBeenCalledWith('referrals/new/courseParticipations/index', { action: `${referPaths.new.programmeHistory.updateReviewedStatus({ referralId })}?_method=PUT`, + existingParticipationsTable, hideTitleServiceName: true, - historyText: `The history shows ${person.name} has previously started or completed an Accredited Programme.`, pageHeading: 'Accredited Programme history', pageTitleOverride: "Person's Accredited Programme history", person, referralId, + referralParticipationsTable, successMessage: undefined, - summaryListsOptions: [summaryListOptions, summaryListOptions], }) }) describe('when there is no programme history for a person', () => { - it('renders the index template with the correct response locals for `historyText` and `summaryListsOptions`', async () => { + it('renders the index template with the correct response locals for `historyText, `existingParticipationsTable` and `referralParticipationsTable`', async () => { referralService.getReferral.mockResolvedValue(draftReferral) - courseService.getAndPresentParticipationsByPerson.mockResolvedValue([]) + courseService.getParticipationsByPerson.mockResolvedValue([]) + courseService.getParticipationsByReferral.mockResolvedValue([]) const requestHandler = controller.index() await requestHandler(request, response, next) expect(referralService.getReferral).toHaveBeenCalledWith(username, referralId) - expect(courseService.getAndPresentParticipationsByPerson).toHaveBeenCalledWith( - username, - userToken, - person.prisonNumber, - referralId, - ) + expect(courseService.getParticipationsByPerson).toHaveBeenCalledWith(username, person.prisonNumber) + expect(courseService.getParticipationsByReferral).toHaveBeenCalledWith(username, referralId) expect(response.render).toHaveBeenCalledWith( 'referrals/new/courseParticipations/index', expect.objectContaining({ + existingParticipationsTable: undefined, historyText: `There is no record of Accredited Programmes for ${person.name}.`, - summaryListsOptions: [], + referralParticipationsTable: undefined, }), ) }) diff --git a/server/controllers/refer/new/courseParticipationsController.ts b/server/controllers/refer/new/courseParticipationsController.ts index 00c6d7838..07ff7bd1e 100644 --- a/server/controllers/refer/new/courseParticipationsController.ts +++ b/server/controllers/refer/new/courseParticipationsController.ts @@ -161,29 +161,38 @@ export default class NewReferralsCourseParticipationsController { const person = await this.personService.getPerson(req.user.username, referral.prisonNumber) - const summaryListsOptions = await this.courseService.getAndPresentParticipationsByPerson( - req.user.username, - req.user.token, - person.prisonNumber, - referralId, - ) + const [existingParticipations, referralParticipations] = await Promise.all([ + this.courseService.getParticipationsByPerson(req.user.username, person.prisonNumber), + this.courseService.getParticipationsByReferral(req.user.username, referralId), + ]) const successMessage = req.flash('successMessage')[0] - const historyText = summaryListsOptions.length - ? `The history shows ${person.name} has previously started or completed an Accredited Programme.` - : `There is no record of Accredited Programmes for ${person.name}.` + const historyText = + !existingParticipations.length && !referralParticipations.length + ? `There is no record of Accredited Programmes for ${person.name}.` + : undefined return res.render('referrals/new/courseParticipations/index', { action: `${referPaths.new.programmeHistory.updateReviewedStatus({ referralId: referral.id })}?_method=PUT`, + existingParticipationsTable: CourseParticipationUtils.table( + existingParticipations, + referralId, + 'existing-participations', + ), hideTitleServiceName: true, historyText, pageHeading: 'Accredited Programme history', pageTitleOverride: "Person's Accredited Programme history", person, referralId, + referralParticipationsTable: CourseParticipationUtils.table( + referralParticipations, + referralId, + 'referral-participations', + true, + ), successMessage, - summaryListsOptions, }) } } diff --git a/server/views/referrals/new/courseParticipations/index.njk b/server/views/referrals/new/courseParticipations/index.njk index 2589ed4f5..562a4fc3f 100644 --- a/server/views/referrals/new/courseParticipations/index.njk +++ b/server/views/referrals/new/courseParticipations/index.njk @@ -2,6 +2,7 @@ {% from "govuk/components/button/macro.njk" import govukButton %} {% from "govuk/components/inset-text/macro.njk" import govukInsetText %} {% from "govuk/components/summary-list/macro.njk" import govukSummaryList %} +{% from "govuk/components/table/macro.njk" import govukTable %} {% from "moj/components/banner/macro.njk" import mojBanner %} {% extends "../../../partials/layout.njk" %} @@ -33,32 +34,42 @@ }) }} {% endif %} - {{ govukInsetText({ - classes: "govuk-!-margin-top-0", - text: historyText, - attributes: { - "data-testid": "history-text" - } - }) }} + {% if historyText %} + {{ govukInsetText({ + classes: "govuk-!-margin-top-0", + text: historyText, + attributes: { + "data-testid": "history-text" + } + }) }} + {% endif %} - {% if summaryListsOptions.length %} -

Add a programme if you know they completed or started a programme not listed here. Return to the tasklist once you’ve added all known programme history.

+ {% if existingParticipationsTable.rows | length %} +

This is a list of programmes {{ person.name }} has started or completed. You can add missing programme history.

{% else %}

The programme team may use information about a person's Accredited Programme history to assess whether they are suitable.

You can continue by adding a programme history or skip this section of the referral if the history is not known.

{% endif %} + + - {% for summaryListOptions in summaryListsOptions %} - {{ govukSummaryList(summaryListOptions) }} - {% endfor %} +
+ {% if existingParticipationsTable.rows | length %} + {{ govukTable(existingParticipationsTable) }} + {% endif %} -
- - + {% if referralParticipationsTable.rows | length %} +

Programmes you've added

+ {{ govukTable(referralParticipationsTable) }} + {% endif %} -
- {{ govukButton({ + + + + +
+ {{ govukButton({ text: "Add a programme", href: referPaths.new.programmeHistory.new({ referralId: referralId }), attributes: { @@ -66,12 +77,11 @@ } }) }} - {{ govukButton({ + {{ govukButton({ text: "Return to tasklist", classes: "govuk-button--secondary" }) }} -
- -
+
+ {% endblock content %}