Skip to content

Commit

Permalink
Feature/46532 the retro input assistant link does not display until o…
Browse files Browse the repository at this point in the history
…fficial check (#1875)

* [admin] bugfix: don't show the retro input assistent until the check window opens

* [admin] add tests

We want these tests to be unit tests rather than integration tests because the driver for showing it is the date and the checkwindow.  If we
were to make these  ntegration tests we can easily test the output, but if we have to mock the app then we would need to start and stop the app
for each test which would be slow.

This approach has the benefit that we can test the data presented to the view, without needing the view content itself.  The view here is entirely dumb
which seems best.

* [admin] fix test suite

Co-authored-by: Mohsen Qureshi <[email protected]>
  • Loading branch information
jon-shipley and activemq authored Apr 28, 2021
1 parent 3a55d51 commit a73eecb
Show file tree
Hide file tree
Showing 7 changed files with 199 additions and 32 deletions.
5 changes: 4 additions & 1 deletion admin/controllers/access-arrangements.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ const controller = {
const pinGenerationEligibilityData = schoolHomeFeatureEligibilityPresenter.getPresentationData(checkWindowData, req.user.timezone)
const availabilityData = await businessAvailabilityService.getAvailabilityData(req.user.schoolId, checkWindowData, req.user.timezone)
const pupilsFormatted = accessArrangementsOverviewPresenter.getPresentationData(pupils, availabilityData, hl)
const retroInputAssistantText = await accessArrangementsOverviewPresenter.getRetroInputAssistantText(availabilityData)

return res.render('access-arrangements/overview', {
highlight: hl,
messages: res.locals.messages,
Expand All @@ -54,7 +56,8 @@ const controller = {
pupilsFormatted,
aaViewMode,
title: res.locals.pageTitle,
availabilityData
availabilityData,
retroInputAssistantText
})
},
/**
Expand Down
53 changes: 34 additions & 19 deletions admin/helpers/access-arrangements-overview-presenter.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,41 @@
'use strict'
const ejsUtil = require('../lib/ejs-util')

const accessArrangementsOverviewPresenter = {}

/**
* Return list of pupils with appropriate styling properties
* @param {Array} pupils
* @param {Array} hl
* @param {Object} availabilityData
* @returns {Array} pupil data
*/
accessArrangementsOverviewPresenter.getPresentationData = (pupils, availabilityData, hl) => {
return pupils.map(pupil => {
if (hl && hl.includes(pupil.urlSlug)) {
let arrangementsLn = pupil.arrangements.length
if (pupil.showDoB && arrangementsLn === 1) {
arrangementsLn = 2
const accessArrangementsOverviewPresenter = {
/**
* Return list of pupils with appropriate styling properties
* @param {Array} pupils
* @param {Array} hl
* @param {Object} availabilityData
* @returns {Array} pupil data
*/
getPresentationData: function getPresentationData (pupils, availabilityData, hl) {
return pupils.map(pupil => {
if (hl && hl.includes(pupil.urlSlug)) {
let arrangementsLn = pupil.arrangements.length
if (pupil.showDoB && arrangementsLn === 1) {
arrangementsLn = 2
}
pupil.verticalBarStyle = arrangementsLn > 1 ? `height:${235 - 35 * (7 - arrangementsLn)}px` : 'height:0px'
}
pupil.verticalBarStyle = arrangementsLn > 1 ? `height:${235 - 35 * (7 - arrangementsLn)}px` : 'height:0px'
pupil.hasAAEditDisabled = !availabilityData.canEditArrangements || pupil.hasCompletedCheck || pupil.notTakingCheck
return pupil
})
},

/**
* The retro input assistant text gives the user a link to retrospectively apply an access arrangment to a check.
* As this only applies to live checks, we only want to show it when the check is active.
* @param availabilityData
* @return {Promise<string>}
*/
getRetroInputAssistantText: async function getRetroInputAssistantText (availabilityData) {
let html = ''
if (!availabilityData.hdfSubmitted && availabilityData.checkWindowStarted) {
html = await ejsUtil.render('access-arrangements/retro-input-assistant')
}
pupil.hasAAEditDisabled = !availabilityData.canEditArrangements || pupil.hasCompletedCheck || pupil.notTakingCheck
return pupil
})
return html
}
}

module.exports = accessArrangementsOverviewPresenter
19 changes: 19 additions & 0 deletions admin/lib/ejs-util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
'use strict'
const path = require('path')
const ejs = require('ejs')
const viewsDir = path.join(__dirname, '../views')

const ejsUtil = {
render: function ejsUtilRender (viewName, data = {}) {
return new Promise((resolve, reject) => {
const filename = path.join(viewsDir, viewName + '.ejs')
ejs.renderFile(filename, data, { cache: true, escape: false },
function (err, str) {
if (err) return reject(err)
return resolve(str)
})
})
}
}

module.exports = ejsUtil
113 changes: 108 additions & 5 deletions admin/spec/back-end/controllers/access-arrangements.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict'

/* global describe beforeEach it expect jasmine spyOn fail */
/* global describe beforeEach it expect jasmine spyOn fail test jest afterEach */

const httpMocks = require('node-mocks-http')
const R = require('ramda')
Expand All @@ -18,6 +18,9 @@ const ValidationError = require('../../../lib/validation-error')
const accessArrangementsDescriptionsPresenter = require('../../../helpers/access-arrangements-descriptions-presenter')
const aaViewModes = require('../../../lib/consts/access-arrangements-view-mode')
const { AccessArrangementsNotEditableError } = require('../../../error-types/access-arrangements-not-editable-error')
const moment = require('moment')
const uuid = require('uuid')
const headteacherDeclarationService = require('../../../services/headteacher-declaration.service')

describe('access arrangements controller:', () => {
let next
Expand All @@ -40,6 +43,10 @@ describe('access arrangements controller:', () => {
next = jasmine.createSpy('next')
})

afterEach(() => {
jest.restoreAllMocks()
})

describe('getOverview route', () => {
const reqParams = {
method: 'GET',
Expand All @@ -65,6 +72,7 @@ describe('access arrangements controller:', () => {
expect(schoolHomeFeatureEligibilityPresenter.getPresentationData).toHaveBeenCalled()
expect(accessArrangementsOverviewPresenter.getPresentationData).toHaveBeenCalled()
})

it('displays the access arrangements unavailable page when the feature is not accessible', async () => {
const res = getRes()
const req = getReq(reqParams)
Expand All @@ -81,6 +89,7 @@ describe('access arrangements controller:', () => {
title: 'Enable access arrangements for pupils who need them'
})
})

it('displays the overview in readonly mode when editing is no longer permitted', async () => {
const res = getRes()
const req = getReq(reqParams)
Expand All @@ -102,9 +111,11 @@ describe('access arrangements controller:', () => {
pupilsFormatted: undefined,
breadcrumbs: undefined,
highlight: undefined,
title: 'Enable access arrangements for pupils who need them'
title: 'Enable access arrangements for pupils who need them',
retroInputAssistantText: ''
})
})

it('throws an error if pupilAccessArrangementsService getPupils is rejected', async () => {
const res = getRes()
const req = getReq(reqParams)
Expand All @@ -122,7 +133,79 @@ describe('access arrangements controller:', () => {
expect(checkWindowV2Service.getActiveCheckWindow).not.toHaveBeenCalled()
expect(schoolHomeFeatureEligibilityPresenter.getPresentationData).not.toHaveBeenCalled()
})

test('does not display the retro input assistant link when the live check window is not active', async () => {
// Mock the date
setupFakeTime(moment('2021-04-21T09:00:00'))

const res = getRes()
const req = getReq()

// Mock the checkWindow, so that the Try it out phase it active, but not the live check
const checkWindow = {
id: 1,
createdAt: moment('2021-04-22T10:25:55Z'),
updatedAt: moment('2021-04-22T13:33:05Z'),
name: 'Test',
adminStartDate: moment('2021-04-19T00:00:00Z'),
checkStartDate: moment('2021-06-07T00:00:00Z'),
checkEndDate: moment('2021-06-25T23:59:59Z'),
isDeleted: false,
urlSlug: uuid.NIL,
adminEndDate: moment('2021-07-30T23:59:59Z'),
familiarisationCheckStartDate: moment('2021-04-19T00:00:00Z'),
familiarisationCheckEndDate: moment('2021-06-25T23:59:59Z')
}
jest.spyOn(accessArrangementsService, 'getCurrentViewMode').mockResolvedValue(aaViewModes.edit)
jest.spyOn(pupilAccessArrangementsService, 'getPupils').mockResolvedValue([])
jest.spyOn(checkWindowV2Service, 'getActiveCheckWindow').mockResolvedValue(checkWindow)
jest.spyOn(businessAvailabilityService, 'getAvailabilityData').mockResolvedValue({ accessArrangementsAvailable: true })
jest.spyOn(headteacherDeclarationService, 'isHdfSubmittedForCheck').mockResolvedValue(false)

await controller.getOverview(req, res, next)
const data = res._getRenderData()
expect(data.retroInputAssistantText).toBe('')
tearDownFakeTime()
})

test('displays the retro input assistant link when the live check window is active', async () => {
// Mock the date
setupFakeTime(moment('2021-06-07T06:00:00Z'))

const res = getRes()
const req = getReq()
req.user.schoolId = 1 // assign a school to the user
req.user.timezone = 'Europe/London'

// Mock the checkWindow, so that the Try it out phase it active, but not the live check
const checkWindow = {
id: 1,
createdAt: moment('2021-04-22T10:25:55Z'),
updatedAt: moment('2021-04-22T13:33:05Z'),
name: 'Test',
adminStartDate: moment('2021-04-19T00:00:00Z'),
checkStartDate: moment('2021-06-07T00:00:00Z'),
checkEndDate: moment('2021-06-25T23:59:59Z'),
isDeleted: false,
urlSlug: uuid.NIL,
adminEndDate: moment('2021-07-30T23:59:59Z'),
familiarisationCheckStartDate: moment('2021-04-19T00:00:00Z'),
familiarisationCheckEndDate: moment('2021-06-25T23:59:59Z')
}
jest.spyOn(accessArrangementsService, 'getCurrentViewMode').mockResolvedValue(aaViewModes.edit)
jest.spyOn(pupilAccessArrangementsService, 'getPupils').mockResolvedValue([])
jest.spyOn(checkWindowV2Service, 'getActiveCheckWindow').mockResolvedValue(checkWindow)
// headTeacherDeclarationService used by getAvailabilityData()
jest.spyOn(headteacherDeclarationService, 'isHdfSubmittedForCheck').mockResolvedValue(false)

await controller.getOverview(req, res, next)
const data = res._getRenderData()
console.log('data', data)
expect(data.retroInputAssistantText).toContain('/access-arrangements/retro-add-input-assistant')
tearDownFakeTime()
})
})

describe('getSelectAccessArrangements route', () => {
const reqParams = {
method: 'GET',
Expand Down Expand Up @@ -173,8 +256,7 @@ describe('access arrangements controller:', () => {
const reqParams = {
method: 'POST',
url: '/access-arrangements/submit',
body: {
},
body: {},
user: {
id: 1,
School: 1
Expand Down Expand Up @@ -350,7 +432,11 @@ describe('access arrangements controller:', () => {
spyOn(checkWindowV2Service, 'getActiveCheckWindow')
spyOn(businessAvailabilityService, 'determineAccessArrangementsEligibility')
spyOn(accessArrangementsService, 'getCurrentViewMode').and.returnValue(aaViewModes.edit)
spyOn(pupilAccessArrangementsService, 'deletePupilAccessArrangements').and.returnValue({ id: 1, foreName: 'foreName', lastName: 'lastName' })
spyOn(pupilAccessArrangementsService, 'deletePupilAccessArrangements').and.returnValue({
id: 1,
foreName: 'foreName',
lastName: 'lastName'
})
await controller.getDeleteAccessArrangements(req, res, next)
expect(checkWindowV2Service.getActiveCheckWindow).toHaveBeenCalled()
expect(businessAvailabilityService.determineAccessArrangementsEligibility).toHaveBeenCalled()
Expand All @@ -372,3 +458,20 @@ describe('access arrangements controller:', () => {
})
})
})

/**
* @param {moment.Moment} baseTime - set the fake time to this moment object
*
*/
function setupFakeTime (baseTime) {
if (!moment.isMoment(baseTime)) {
throw new Error('moment.Moment time expected')
}
jest.useFakeTimers('modern')
jest.setSystemTime(baseTime.toDate())
}

function tearDownFakeTime () {
const realTime = jest.getRealSystemTime()
jest.setSystemTime(realTime)
}
25 changes: 25 additions & 0 deletions admin/spec/back-end/lib/ejs-util.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use strict'
/* global test expect fail describe */

const sut = require('../../../lib/ejs-util')

describe('ejsUtil', () => {
test('it renders a relative template', async () => {
try {
const html = await sut.render('access-arrangements/retro-input-assistant', {})
expect(html).toContain('If you need to assign an input assistant after the pupil has taken the official check')
console.log('html', html)
} catch (error) {
fail(error)
}
})

test('it does not escape html entities', async () => {
try {
const html = await sut.render('access-arrangements/retro-input-assistant', {})
expect(html).toContain('<div id="retroAddInputAssistantInfo">')
} catch (error) {
fail(error)
}
})
})
10 changes: 3 additions & 7 deletions admin/views/access-arrangements/overview.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,9 @@
<% } else { %>
<p class="govuk-body govuk-!-margin-top-4 govuk-!-margin-bottom-9">No pupils with access arrangements.</p>
<% } %>
<% if (!availabilityData.hdfSubmitted) { %>
<div id="retroAddInputAssistantInfo">
<p>
If you need to assign an input assistant after the pupil has taken the official check go to <a href="/access-arrangements/retro-add-input-assistant">Record input assistant used for official check</a>.
</p>
</div>
<% } %>

<%- retroInputAssistantText %>

<div class="govuk-back-to-top">
<a class="govuk-link govuk-link--no-visited-state app-back-to-top__link" href="#top">
<svg role="presentation" focusable="false" class="app-back-to-top__icon" xmlns="http://www.w3.org/2000/svg" width="13" height="17" viewBox="0 0 13 17">
Expand Down
6 changes: 6 additions & 0 deletions admin/views/access-arrangements/retro-input-assistant.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<div id="retroAddInputAssistantInfo">
<p>
If you need to assign an input assistant after the pupil has taken the official check go to
<a href="/access-arrangements/retro-add-input-assistant">Record input assistant used for official check</a>.
</p>
</div>

0 comments on commit a73eecb

Please sign in to comment.