From 3dcb83c85e5fa39f7381306bf2e342f0ec7ed136 Mon Sep 17 00:00:00 2001 From: Roble Date: Tue, 30 Apr 2024 12:01:51 +0100 Subject: [PATCH] Saving input from return requirements in session (#939) * Saving input from return requirements in session During the initial development of the return requirements pages, user input was not being saved in session. Later tickets and pages have this functionality in place. The session data is needed for the Check Your Answers page at the end of the journey. This PR is focused on saving user input into the session where it is missing. --- .../abstraction-period.presenter.js | 22 +-- .../return-requirements/points.presenter.js | 19 +- .../return-requirements/reason.presenter.js | 3 +- .../return-requirements/setup.presenter.js | 3 +- .../site-description.presenter.js | 19 +- .../return-requirements/points.service.js | 4 +- .../submit-abstraction-period.service.js | 14 +- .../submit-points.service.js | 34 +++- .../submit-purpose.service.js | 10 +- .../submit-setup.service.js | 4 +- .../submit-site-description.service.js | 37 +++- .../return-requirements/setup.validator.js | 4 +- .../abstraction-period.njk | 12 +- app/views/return-requirements/points.njk | 2 +- app/views/return-requirements/reason.njk | 22 +-- app/views/return-requirements/setup.njk | 8 +- .../return-requirements/site-description.njk | 16 ++ .../abstraction-period.presenter.test.js | 57 +++--- .../points.presenter.test.js | 166 +++++++++--------- .../reason.presenter.test.js | 3 +- .../setup.presenter.test.js | 3 +- .../site-description.presenter.test.js | 55 +++--- .../abstraction-period.service.test.js | 9 +- .../points.service.test.js | 3 +- .../reason.service.test.js | 3 +- .../return-requirements/setup.service.test.js | 3 +- .../site-description.service.test.js | 2 +- .../submit-abstraction-period.service.test.js | 49 +++--- .../submit-points.service.test.js | 26 ++- .../submit-reason.service.test.js | 6 +- .../submit-setup.service.test.js | 13 +- .../submit-site-description.service.test.js | 32 ++-- .../setup.validator.test.js | 2 +- 33 files changed, 354 insertions(+), 311 deletions(-) diff --git a/app/presenters/return-requirements/abstraction-period.presenter.js b/app/presenters/return-requirements/abstraction-period.presenter.js index 7a14541a9c..606bd31047 100644 --- a/app/presenters/return-requirements/abstraction-period.presenter.js +++ b/app/presenters/return-requirements/abstraction-period.presenter.js @@ -9,36 +9,20 @@ * Formats data for the `/return-requirements/{sessionId}/abstraction-period` page * * @param {module:SessionModel} session - The returns requirements session instance - * @param {Object} [payload] - The payload from the request * * @returns {Object} - The data formatted for the view template */ -function go (session, payload = {}) { +function go (session) { const data = { + abstractionPeriod: session.data.abstractionPeriod ? session.data.abstractionPeriod : null, id: session.id, licenceId: session.data.licence.id, - licenceRef: session.data.licence.licenceRef, - abstractionPeriod: _licenceAbstractionPeriod(payload) + licenceRef: session.data.licence.licenceRef } return data } -function _licenceAbstractionPeriod (payload) { - // NOTE: The following checks whether a user has inputted any values for the abstraction period for the returns - // requirements. If the values have not been set, then it is because the presenter has been called from - // 'AbstractionPeriodService' and it's the first load. Else it has been called by the 'SubmitAbstractionPeriod' and - // the user has not inputted any values for the abstraction period. Either way, we use it to tell us wether there is - // anything in the payload worth transforming. - - return { - startDay: payload['start-abstraction-period-day'] ? payload['start-abstraction-period-day'] : null, - startMonth: payload['start-abstraction-period-month'] ? payload['start-abstraction-period-month'] : null, - endDay: payload['end-abstraction-period-day'] ? payload['end-abstraction-period-day'] : null, - endMonth: payload['end-abstraction-period-month'] ? payload['end-abstraction-period-month'] : null - } -} - module.exports = { go } diff --git a/app/presenters/return-requirements/points.presenter.js b/app/presenters/return-requirements/points.presenter.js index e7229d3490..f02975d3bd 100644 --- a/app/presenters/return-requirements/points.presenter.js +++ b/app/presenters/return-requirements/points.presenter.js @@ -10,33 +10,22 @@ * * @param {module:SessionModel} session - The returns requirements session instance * @param {Object} [pointsData] - The points for the licence - * @param {Object} [payload] - The payload from the request * * @returns {Object} - The data formatted for the view template */ -function go (session, pointsData, payload = {}) { +function go (session, pointsData) { const data = { id: session.id, licenceId: session.data.licence.id, licenceRef: session.data.licence.licenceRef, - licencePoints: _licencePoints(pointsData, payload) + licencePoints: _licencePoints(pointsData), + selectedPoints: session.data.points ? session.data.points.join(',') : '' } return data } -function _licencePoints (pointsData, payload) { - // NOTE: 'points' is the payload value that tells us whether the user selected any purposes - // for the return requirement. - // If it is not set then it is because the presenter has been called from 'PointsService' and it's the first - // load. Else it has been called by the 'SubmitPointsService' and the user has not checked a point from the list. - // Either way, we use it to tell us whether there is anything in the payload worth transforming. - const points = payload.points - - if (points) { - return points - } - +function _licencePoints (pointsData) { const abstractionPoints = [] if (!pointsData) { diff --git a/app/presenters/return-requirements/reason.presenter.js b/app/presenters/return-requirements/reason.presenter.js index be9ecd0378..7f9d69d4e0 100644 --- a/app/presenters/return-requirements/reason.presenter.js +++ b/app/presenters/return-requirements/reason.presenter.js @@ -8,7 +8,8 @@ function go (session) { const data = { id: session.id, - licenceRef: session.data.licence.licenceRef + licenceRef: session.data.licence.licenceRef, + reason: session.data.reason ? session.data.reason : null } return data diff --git a/app/presenters/return-requirements/setup.presenter.js b/app/presenters/return-requirements/setup.presenter.js index 84770f6bf3..bc6e88f6f2 100644 --- a/app/presenters/return-requirements/setup.presenter.js +++ b/app/presenters/return-requirements/setup.presenter.js @@ -8,7 +8,8 @@ function go (session) { const data = { id: session.id, - licenceRef: session.data.licence.licenceRef + licenceRef: session.data.licence.licenceRef, + setup: session.data.setup ? session.data.setup : null } return data diff --git a/app/presenters/return-requirements/site-description.presenter.js b/app/presenters/return-requirements/site-description.presenter.js index 85b61d6ed2..e75d942924 100644 --- a/app/presenters/return-requirements/site-description.presenter.js +++ b/app/presenters/return-requirements/site-description.presenter.js @@ -13,32 +13,17 @@ * * @returns {Object} - The data formatted for the view template */ -function go (session, payload = {}) { +function go (session) { const data = { id: session.id, licenceId: session.data.licence.id, licenceRef: session.data.licence.licenceRef, - licenceSiteDescription: _licenceSiteDescription(payload) + siteDescription: session.data.siteDescription ? session.data.siteDescription : null } return data } -function _licenceSiteDescription (payload) { - // NOTE: 'siteDescription' is the payload value that tells us whether the user inputted a siteDescription - // for the return requirement site. - // If it is not set then it is because the presenter has been called from 'SiteDescriptionService' and it's the first - // load. Else it has been called by the 'SubmitSiteDescriptionService' and the user has not inputted a site description. - // Either way, we use it to tell us wether there is anything in the payload worth transforming. - const siteDescription = payload.siteDescription - - if (!siteDescription) { - return null - } - - return siteDescription -} - module.exports = { go } diff --git a/app/services/return-requirements/points.service.js b/app/services/return-requirements/points.service.js index 9cfe33c94e..a597b01c2d 100644 --- a/app/services/return-requirements/points.service.js +++ b/app/services/return-requirements/points.service.js @@ -6,7 +6,7 @@ */ const FetchPointsService = require('../../services/return-requirements/fetch-points.service.js') -const SelectPointsPresenter = require('../../presenters/return-requirements/points.presenter.js') +const PointsPresenter = require('../../presenters/return-requirements/points.presenter.js') const SessionModel = require('../../models/session.model.js') /** @@ -23,7 +23,7 @@ async function go (sessionId) { const session = await SessionModel.query().findById(sessionId) const pointsData = await FetchPointsService.go(session.data.licence.id) - const formattedData = SelectPointsPresenter.go(session, pointsData) + const formattedData = PointsPresenter.go(session, pointsData) return { activeNavBar: 'search', diff --git a/app/services/return-requirements/submit-abstraction-period.service.js b/app/services/return-requirements/submit-abstraction-period.service.js index 69f09aff31..52be57f69b 100644 --- a/app/services/return-requirements/submit-abstraction-period.service.js +++ b/app/services/return-requirements/submit-abstraction-period.service.js @@ -34,13 +34,13 @@ async function go (sessionId, payload) { return {} } - const formattedData = AbstractionPeriodPresenter.go(session, payload) + const submittedSessionData = _submittedSessionData(session, payload) return { activeNavBar: 'search', error: validationResult, pageTitle: 'Enter the abstraction period for the requirements for returns', - ...formattedData + ...submittedSessionData } } @@ -52,6 +52,16 @@ async function _save (session, payload) { return session.$query().patch({ data: currentData }) } +/** + * Combines the existing session data with the submitted payload formatted by the presenter. If nothing is submitted by + * the user, payload will be an empty object. + */ +function _submittedSessionData (session, payload) { + session.data.abstractionPeriod = Object.keys(payload).length > 0 ? payload : null + + return AbstractionPeriodPresenter.go(session) +} + function _validate (payload) { const validation = AbstractionPeriodValidator.go(payload) diff --git a/app/services/return-requirements/submit-points.service.js b/app/services/return-requirements/submit-points.service.js index 76fd2dbccd..95dcd24b62 100644 --- a/app/services/return-requirements/submit-points.service.js +++ b/app/services/return-requirements/submit-points.service.js @@ -7,7 +7,7 @@ const FetchPointsService = require('../../services/return-requirements/fetch-points.service.js') const PointsValidator = require('../../validators/return-requirements/points.validator.js') -const SelectPointsPresenter = require('../../presenters/return-requirements/points.presenter.js') +const PointsPresenter = require('../../presenters/return-requirements/points.presenter.js') const SessionModel = require('../../models/session.model.js') /** @@ -27,9 +27,18 @@ const SessionModel = require('../../models/session.model.js') async function go (sessionId, payload) { const session = await SessionModel.query().findById(sessionId) - const pointsData = await FetchPointsService.go(session.data.licence.id) + _handleOneOptionSelected(payload) + const validationResult = _validate(payload) - const formattedData = SelectPointsPresenter.go(session, pointsData, payload) + + if (!validationResult) { + await _save(session, payload) + + return {} + } + + const pointsData = await FetchPointsService.go(session.data.licence.id) + const formattedData = PointsPresenter.go(session, pointsData) return { activeNavBar: 'search', @@ -39,6 +48,25 @@ async function go (sessionId, payload) { } } +/** + * When a single point is checked by the user, it returns as a string. When multiple points are checked, the + * 'points' is returned as an array. This function works to make those single selected string 'points' into an array + * for uniformity. + */ +function _handleOneOptionSelected (payload) { + if (!Array.isArray(payload.points)) { + payload.points = [payload.points] + } +} + +async function _save (session, payload) { + const currentData = session.data + + currentData.points = payload.points + + return session.$query().patch({ data: currentData }) +} + function _validate (payload) { const validation = PointsValidator.go(payload) diff --git a/app/services/return-requirements/submit-purpose.service.js b/app/services/return-requirements/submit-purpose.service.js index c0b69b997a..d1a896fda2 100644 --- a/app/services/return-requirements/submit-purpose.service.js +++ b/app/services/return-requirements/submit-purpose.service.js @@ -7,7 +7,7 @@ const FetchPurposesService = require('../../services/return-requirements/fetch-purposes.service.js') const PurposeValidation = require('../../validators/return-requirements/purpose.validator.js') -const SelectPurposePresenter = require('../../presenters/return-requirements/purpose.presenter.js') +const PurposePresenter = require('../../presenters/return-requirements/purpose.presenter.js') const SessionModel = require('../../models/session.model.js') /** @@ -15,9 +15,9 @@ const SessionModel = require('../../models/session.model.js') * * It first retrieves the session instance for the returns requirements journey in progress. * - * The user input is then validated and the result is then combined with the output of the presenter to generate the page data needed by the view. - * If there was a validation error the controller will re-render the page so needs this information. If all is well the - * controller will redirect to the next page in the journey. + * The user input is then validated and the result is then combined with the output of the presenter to generate the + * page data needed by the view. If there was a validation error the controller will re-render the page so needs this + * information. If all is well the controller will redirect to the next page in the journey. * * @param {string} sessionId - The id of the current session * @param {Object} payload - The submitted form data @@ -38,7 +38,7 @@ async function go (sessionId, payload) { } const purposesData = await FetchPurposesService.go(session.data.licence.id) - const formattedData = SelectPurposePresenter.go(session, purposesData) + const formattedData = PurposePresenter.go(session, purposesData) return { activeNavBar: 'search', diff --git a/app/services/return-requirements/submit-setup.service.js b/app/services/return-requirements/submit-setup.service.js index 59fc2d7d97..5b06630445 100644 --- a/app/services/return-requirements/submit-setup.service.js +++ b/app/services/return-requirements/submit-setup.service.js @@ -49,11 +49,11 @@ async function go (sessionId, payload) { function _redirect (setup) { let endpoint - if (setup === 'use_abstraction_data') { + if (setup === 'use-abstraction-data') { endpoint = 'check-your-answers' } - if (setup === 'set_up_manually') { + if (setup === 'set-up-manually') { endpoint = 'purpose' } diff --git a/app/services/return-requirements/submit-site-description.service.js b/app/services/return-requirements/submit-site-description.service.js index 2db6dbc2dc..b7ff9a8d78 100644 --- a/app/services/return-requirements/submit-site-description.service.js +++ b/app/services/return-requirements/submit-site-description.service.js @@ -14,11 +14,11 @@ const SiteDescriptionValidator = require('../../validators/return-requirements/s * * It first retrieves the session instance for the returns requirements journey in progress. * - * The user input is then validated and the result is then combined with the output of the presenter to generate the page data needed by the view. - * If there was a validation error the controller will re-render the page so needs this information. If all is well the - * controller will redirect to the next page in the journey. + * The user input is then validated and the site description in the payload is saved in the session. If there is a + * validation error the controller will re-render the page. If all is well the controller will redirect to the next page + * in the journey. * - * @param {string} sessionId - The id of the current session + * @param {string} sessionId - The UUID of the current session * @param {Object} payload - The submitted form data * * @returns {Promise} The page data for the start date page @@ -28,17 +28,40 @@ async function go (sessionId, payload) { const validationResult = _validate(payload) - const formattedData = SiteDescriptionPresenter.go(session, payload) + if (!validationResult) { + await _save(session, payload) + + return {} + } + + const submittedSessionData = _submittedSessionData(session, payload) return { activeNavBar: 'search', error: validationResult, - journey: session.data.journey, pageTitle: 'Enter a site description for the requirements for returns', - ...formattedData + ...submittedSessionData } } +async function _save (session, payload) { + const currentData = session.data + + currentData.siteDescription = payload.siteDescription + + return session.$query().patch({ data: currentData }) +} + +/** + * Combines the existing session data with the submitted payload formatted by the presenter. If nothing is submitted by + * the user, payload will be an empty object. + */ +function _submittedSessionData (session, payload) { + session.data.siteDescription = payload.siteDescription ? payload.siteDescription : null + + return SiteDescriptionPresenter.go(session) +} + function _validate (payload) { const validation = SiteDescriptionValidator.go(payload) diff --git a/app/validators/return-requirements/setup.validator.js b/app/validators/return-requirements/setup.validator.js index 284c5d1116..b15645eda8 100644 --- a/app/validators/return-requirements/setup.validator.js +++ b/app/validators/return-requirements/setup.validator.js @@ -8,8 +8,8 @@ const Joi = require('joi') const VALID_VALUES = [ - 'use_abstraction_data', - 'set_up_manually' + 'use-abstraction-data', + 'set-up-manually' ] /** diff --git a/app/views/return-requirements/abstraction-period.njk b/app/views/return-requirements/abstraction-period.njk index f8068b96b4..bd4deeb831 100644 --- a/app/views/return-requirements/abstraction-period.njk +++ b/app/views/return-requirements/abstraction-period.njk @@ -73,11 +73,13 @@ items: [ { name: "day", - classes: "govuk-input--width-2" + startResultErrorClass + classes: "govuk-input--width-2" + startResultErrorClass, + value: abstractionPeriod['start-abstraction-period-day'] }, { name: "month", - classes: "govuk-input--width-2" + startResultErrorClass + classes: "govuk-input--width-2" + startResultErrorClass, + value: abstractionPeriod['start-abstraction-period-month'] } ] }) }} @@ -96,11 +98,13 @@ items: [ { name: "day", - classes: "govuk-input--width-2" + endResultErrorClass + classes: "govuk-input--width-2" + endResultErrorClass, + value: abstractionPeriod['end-abstraction-period-day'] }, { name: "month", - classes: "govuk-input--width-2" + endResultErrorClass + classes: "govuk-input--width-2" + endResultErrorClass, + value: abstractionPeriod['end-abstraction-period-month'] } ] }) }} diff --git a/app/views/return-requirements/points.njk b/app/views/return-requirements/points.njk index ea91294c2d..4a3e8698f7 100644 --- a/app/views/return-requirements/points.njk +++ b/app/views/return-requirements/points.njk @@ -47,7 +47,7 @@ {% set checkBoxItem = { value: point, text: point, - checked: false + checked: point in selectedPoints } %} {% set checkBoxItems = (checkBoxItems.push(checkBoxItem), checkBoxItems) %} diff --git a/app/views/return-requirements/reason.njk b/app/views/return-requirements/reason.njk index d41461d876..44fcc10064 100644 --- a/app/views/return-requirements/reason.njk +++ b/app/views/return-requirements/reason.njk @@ -47,57 +47,57 @@ { text: 'Change to special agreement', value: 'change-to-special-agreement', - checked: false + checked: 'change-to-special-agreement' === reason }, { text: 'Licence holder name or address change', value: 'name-or-address-change', - checked: false + checked: 'name-or-address-change' === reason }, { text: 'Licence transferred and now chargeable', value: 'transfer-and-now-chargeable', - checked: false + checked: 'transfer-and-now-chargeable' === reason }, { text: 'Limited extension of licence validity (LEV)', value: 'extension-of-licence-validity', - checked: false + checked: 'extension-of-licence-validity' === reason }, { text: 'Major change', value: 'major-change', - checked: false + checked: 'major-change' === reason }, { text: 'Minor change', value: 'minor-change', - checked: false + checked: 'minor-change' === reason }, { text: 'New licence in part succession or licence apportionment', value: 'new-licence-in-part-succession-or-licence-apportionment', - checked: false + checked: 'new-licence-in-part-succession-or-licence-apportionment' === reason }, { text: 'New licence', value: 'new-licence', - checked: false + checked: 'new-licence' === reason }, { text: 'New special agreement', value: 'new-special-agreement', - checked: false + checked: 'new-special-agreement' === reason }, { text: 'Succession or transfer of licence', value: 'succession-or-transfer-of-licence', - checked: false + checked: 'succession-or-transfer-of-licence' === reason }, { text: 'Succession to remainder licence or licence apportionment', value: 'succession-to-remainder-licence-or-licence-apportionment', - checked: false + checked: 'succession-to-remainder-licence-or-licence-apportionment' === reason } ] }) }} diff --git a/app/views/return-requirements/setup.njk b/app/views/return-requirements/setup.njk index 1612cfe1e2..6112b0680f 100644 --- a/app/views/return-requirements/setup.njk +++ b/app/views/return-requirements/setup.njk @@ -43,17 +43,17 @@ }, items: [ { - value: "use_abstraction_data", + value: "use-abstraction-data", text: "Start by using abstraction data", - checked: false + checked: "use-abstraction-data" === setup }, { divider: "or" }, { - value: "set_up_manually", + value: "set-up-manually", text: "Set up manually", - checked: false + checked: "set-up-manually" === setup } ] }) }} diff --git a/app/views/return-requirements/site-description.njk b/app/views/return-requirements/site-description.njk index f1f4de25eb..7b9da75d83 100644 --- a/app/views/return-requirements/site-description.njk +++ b/app/views/return-requirements/site-description.njk @@ -1,6 +1,7 @@ {% extends 'layout.njk' %} {% from "govuk/components/back-link/macro.njk" import govukBackLink %} {% from "govuk/components/button/macro.njk" import govukButton %} +{% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %} {% from "govuk/components/input/macro.njk" import govukInput %} {% set rootLink = "/system/return-requirements/" + id %} @@ -16,6 +17,20 @@ {% endblock %} {% block content %} +{# Error summary #} + {% if error %} + {{ govukErrorSummary({ + titleText: "There is a problem", + errorList: [ + { + text: error.text, + href: "#siteDescription" + } + ] + }) }} + {%endif%} + + {# Main heading #}
Licence {{ licenceRef }} @@ -28,6 +43,7 @@ hint: { text: "This is the description that will appear on the return" }, + value: siteDescription, id: "site-description", name: "siteDescription", errorMessage: error diff --git a/test/presenters/return-requirements/abstraction-period.presenter.test.js b/test/presenters/return-requirements/abstraction-period.presenter.test.js index abc9cffafe..77f11696d2 100644 --- a/test/presenters/return-requirements/abstraction-period.presenter.test.js +++ b/test/presenters/return-requirements/abstraction-period.presenter.test.js @@ -29,47 +29,42 @@ describe('Abstraction Period presenter', () => { } }) - describe('when provided with a populated session', () => { - describe('and no payload', () => { - it('correctly presents the data', () => { - const result = AbstractionPeriodPresenter.go(session) + describe('when provided with a session where abstraction period is populated', () => { + beforeEach(() => { + session.data.abstractionPeriod = { + 'start-abstraction-period-day': '07', + 'start-abstraction-period-month': '12', + 'end-abstraction-period-day': '22', + 'end-abstraction-period-month': '07' + } + }) - expect(result).to.equal({ - id: '61e07498-f309-4829-96a9-72084a54996d', - licenceId: '8b7f78ba-f3ad-4cb6-a058-78abc4d1383d', - licenceRef: '01/ABC', - abstractionPeriod: { - startDay: null, - startMonth: null, - endDay: null, - endMonth: null - } - }) + it('correctly presents the data', () => { + const result = AbstractionPeriodPresenter.go(session) + + expect(result).to.equal({ + abstractionPeriod: { + 'start-abstraction-period-day': '07', + 'start-abstraction-period-month': '12', + 'end-abstraction-period-day': '22', + 'end-abstraction-period-month': '07' + }, + id: '61e07498-f309-4829-96a9-72084a54996d', + licenceId: '8b7f78ba-f3ad-4cb6-a058-78abc4d1383d', + licenceRef: '01/ABC' }) }) }) - describe('and with a payload', () => { - const payload = { - 'start-abstraction-period-day': '01', - 'start-abstraction-period-month': '12', - 'end-abstraction-period-day': '02', - 'end-abstraction-period-month': '7' - } - + describe('when provided with a session where abstraction period is not populated', () => { it('correctly presents the data', () => { - const result = AbstractionPeriodPresenter.go(session, payload) + const result = AbstractionPeriodPresenter.go(session) expect(result).to.equal({ + abstractionPeriod: null, id: '61e07498-f309-4829-96a9-72084a54996d', licenceId: '8b7f78ba-f3ad-4cb6-a058-78abc4d1383d', - licenceRef: '01/ABC', - abstractionPeriod: { - startDay: '01', - startMonth: '12', - endDay: '02', - endMonth: '7' - } + licenceRef: '01/ABC' }) }) }) diff --git a/test/presenters/return-requirements/points.presenter.test.js b/test/presenters/return-requirements/points.presenter.test.js index 4798a0e749..d44b85a191 100644 --- a/test/presenters/return-requirements/points.presenter.test.js +++ b/test/presenters/return-requirements/points.presenter.test.js @@ -10,75 +10,33 @@ const { expect } = Code // Thing under test const PointsPresenter = require('../../../app/presenters/return-requirements/points.presenter.js') -describe('Select Points presenter', () => { - let session +describe('Points presenter', () => { let pointsData beforeEach(() => { - session = { - id: '61e07498-f309-4829-96a9-72084a54996d', - data: { - licence: { - id: '8b7f78ba-f3ad-4cb6-a058-78abc4d1383d', - currentVersionStartDate: '2023-01-01T00:00:00.000Z', - endDate: null, - licenceRef: '01/ABC', - licenceHolder: 'Turbo Kid', - startDate: '2022-04-01T00:00:00.000Z' - } - } - } - - pointsData = [{ - NGR1_EAST: '69212', - NGR2_EAST: 'null', - NGR3_EAST: 'null', - NGR4_EAST: 'null', - LOCAL_NAME: 'RIVER MEDWAY AT YALDING INTAKE', - NGR1_NORTH: '50394', - NGR1_SHEET: 'TQ', - NGR2_NORTH: 'null', - NGR2_SHEET: 'null', - NGR3_NORTH: 'null', - NGR3_SHEET: 'null', - NGR4_NORTH: 'null', - NGR4_SHEET: 'null' - }, - { - NGR1_EAST: '69212', - NGR2_EAST: 'null', - NGR3_EAST: 'null', - NGR4_EAST: 'null', - LOCAL_NAME: 'RIVER MEDWAY AT YALDING INTAKE', - NGR1_NORTH: '50394', - NGR1_SHEET: 'TQ', - NGR2_NORTH: 'null', - NGR2_SHEET: 'null', - NGR3_NORTH: 'null', - NGR3_SHEET: 'null', - NGR4_NORTH: 'null', - NGR4_SHEET: 'null' - }, - { - NGR1_EAST: '68083', - NGR2_EAST: 'null', - NGR3_EAST: 'null', - NGR4_EAST: 'null', - LOCAL_NAME: 'BEWL WATER RESERVOIR', - NGR1_NORTH: '33604', - NGR1_SHEET: 'TQ', - NGR2_NORTH: 'null', - NGR2_SHEET: 'null', - NGR3_NORTH: 'null', - NGR3_SHEET: 'null', - NGR4_NORTH: 'null', - NGR4_SHEET: 'null' - } - ] + pointsData = _testPointsData() }) describe('when provided with a populated session', () => { - describe('and no payload', () => { + let session + + describe('and no points in session data', () => { + beforeEach(() => { + session = { + id: '61e07498-f309-4829-96a9-72084a54996d', + data: { + licence: { + id: '8b7f78ba-f3ad-4cb6-a058-78abc4d1383d', + currentVersionStartDate: '2023-01-01T00:00:00.000Z', + endDate: null, + licenceRef: '01/ABC', + licenceHolder: 'Turbo Kid', + startDate: '2022-04-01T00:00:00.000Z' + } + } + } + }) + it('correctly presents the data', () => { const result = PointsPresenter.go(session, pointsData) @@ -89,30 +47,78 @@ describe('Select Points presenter', () => { licencePoints: [ 'At National Grid Reference TQ 69212 50394 (RIVER MEDWAY AT YALDING INTAKE)', 'At National Grid Reference TQ 68083 33604 (BEWL WATER RESERVOIR)' - ] + ], + selectedPoints: '' }) }) }) - }) - describe('and with a payload', () => { - const payload = { - points: [ - 'At National Grid Reference TQ 68083 33604 (BEWL WATER RESERVOIR)' - ] - } + describe('and with points in session data', () => { + beforeEach(() => { + session = { + id: '61e07498-f309-4829-96a9-72084a54996d', + data: { + licence: { + id: '8b7f78ba-f3ad-4cb6-a058-78abc4d1383d', + currentVersionStartDate: '2023-01-01T00:00:00.000Z', + endDate: null, + licenceRef: '01/ABC', + licenceHolder: 'Turbo Kid', + startDate: '2022-04-01T00:00:00.000Z' + }, + points: ['At National Grid Reference TQ 68083 33604 (BEWL WATER RESERVOIR)'] + } + } + }) - it('correctly presents the data', () => { - const result = PointsPresenter.go(session, pointsData, payload) + it('correctly presents the data', () => { + const result = PointsPresenter.go(session, pointsData) - expect(result).to.equal({ - id: '61e07498-f309-4829-96a9-72084a54996d', - licenceId: '8b7f78ba-f3ad-4cb6-a058-78abc4d1383d', - licenceRef: '01/ABC', - licencePoints: [ - 'At National Grid Reference TQ 68083 33604 (BEWL WATER RESERVOIR)' - ] + expect(result).to.equal({ + id: '61e07498-f309-4829-96a9-72084a54996d', + licenceId: '8b7f78ba-f3ad-4cb6-a058-78abc4d1383d', + licenceRef: '01/ABC', + licencePoints: [ + 'At National Grid Reference TQ 69212 50394 (RIVER MEDWAY AT YALDING INTAKE)', + 'At National Grid Reference TQ 68083 33604 (BEWL WATER RESERVOIR)' + ], + selectedPoints: 'At National Grid Reference TQ 68083 33604 (BEWL WATER RESERVOIR)' + }) }) }) }) }) + +function _testPointsData () { + return [{ + NGR1_EAST: '69212', + NGR2_EAST: 'null', + NGR3_EAST: 'null', + NGR4_EAST: 'null', + LOCAL_NAME: 'RIVER MEDWAY AT YALDING INTAKE', + NGR1_NORTH: '50394', + NGR1_SHEET: 'TQ', + NGR2_NORTH: 'null', + NGR2_SHEET: 'null', + NGR3_NORTH: 'null', + NGR3_SHEET: 'null', + NGR4_NORTH: 'null', + NGR4_SHEET: 'null' + }, + { + NGR1_EAST: '68083', + NGR2_EAST: 'null', + NGR3_EAST: 'null', + NGR4_EAST: 'null', + LOCAL_NAME: 'BEWL WATER RESERVOIR', + NGR1_NORTH: '33604', + NGR1_SHEET: 'TQ', + NGR2_NORTH: 'null', + NGR2_SHEET: 'null', + NGR3_NORTH: 'null', + NGR3_SHEET: 'null', + NGR4_NORTH: 'null', + NGR4_SHEET: 'null' + } + ] +} diff --git a/test/presenters/return-requirements/reason.presenter.test.js b/test/presenters/return-requirements/reason.presenter.test.js index 21176ecfd2..f6d2a548cb 100644 --- a/test/presenters/return-requirements/reason.presenter.test.js +++ b/test/presenters/return-requirements/reason.presenter.test.js @@ -32,7 +32,8 @@ describe('Select Reason presenter', () => { expect(result).to.equal({ id: 'f1288f6c-8503-4dc1-b114-75c408a14bd0', - licenceRef: '01/123' + licenceRef: '01/123', + reason: null }) }) }) diff --git a/test/presenters/return-requirements/setup.presenter.test.js b/test/presenters/return-requirements/setup.presenter.test.js index 1291de8a73..6b9ed6cfa4 100644 --- a/test/presenters/return-requirements/setup.presenter.test.js +++ b/test/presenters/return-requirements/setup.presenter.test.js @@ -32,7 +32,8 @@ describe('Setup presenter', () => { expect(result).to.equal({ id: 'f1288f6c-8503-4dc1-b114-75c408a14bd0', - licenceRef: '01/123' + licenceRef: '01/123', + setup: null }) }) }) diff --git a/test/presenters/return-requirements/site-description.presenter.test.js b/test/presenters/return-requirements/site-description.presenter.test.js index 1ca974d070..8f12510564 100644 --- a/test/presenters/return-requirements/site-description.presenter.test.js +++ b/test/presenters/return-requirements/site-description.presenter.test.js @@ -11,26 +11,26 @@ const { expect } = Code const SiteDescriptionPresenter = require('../../../app/presenters/return-requirements/site-description.presenter.js') describe('Site Description presenter', () => { - let session + describe('when provided with a populated session', () => { + let session - beforeEach(() => { - session = { - id: '61e07498-f309-4829-96a9-72084a54996d', - data: { - licence: { - id: '8b7f78ba-f3ad-4cb6-a058-78abc4d1383d', - currentVersionStartDate: '2023-01-01T00:00:00.000Z', - endDate: null, - licenceRef: '01/ABC', - licenceHolder: 'Turbo Kid', - startDate: '2022-04-01T00:00:00.000Z' + describe('and no site description in session data and no payload', () => { + beforeEach(() => { + session = { + id: '61e07498-f309-4829-96a9-72084a54996d', + data: { + licence: { + id: '8b7f78ba-f3ad-4cb6-a058-78abc4d1383d', + currentVersionStartDate: '2023-01-01T00:00:00.000Z', + endDate: null, + licenceRef: '01/ABC', + licenceHolder: 'Turbo Kid', + startDate: '2022-04-01T00:00:00.000Z' + } + } } - } - } - }) + }) - describe('when provided with a populated session', () => { - describe('and no payload', () => { it('correctly presents the data', () => { const result = SiteDescriptionPresenter.go(session) @@ -38,18 +38,33 @@ describe('Site Description presenter', () => { id: '61e07498-f309-4829-96a9-72084a54996d', licenceId: '8b7f78ba-f3ad-4cb6-a058-78abc4d1383d', licenceRef: '01/ABC', - licenceSiteDescription: null + siteDescription: null }) }) }) - describe('and a populated payload', () => { + describe('and with site description in session data and populated payload', () => { let payload beforeEach(() => { payload = { siteDescription: 'This is a valid return requirement description' } + + session = { + id: '61e07498-f309-4829-96a9-72084a54996d', + data: { + licence: { + id: '8b7f78ba-f3ad-4cb6-a058-78abc4d1383d', + currentVersionStartDate: '2023-01-01T00:00:00.000Z', + endDate: null, + licenceRef: '01/ABC', + licenceHolder: 'Turbo Kid', + startDate: '2022-04-01T00:00:00.000Z' + }, + siteDescription: 'This is a valid return requirement description' + } + } }) it('correctly presents the data', () => { @@ -59,7 +74,7 @@ describe('Site Description presenter', () => { id: '61e07498-f309-4829-96a9-72084a54996d', licenceId: '8b7f78ba-f3ad-4cb6-a058-78abc4d1383d', licenceRef: '01/ABC', - licenceSiteDescription: 'This is a valid return requirement description' + siteDescription: 'This is a valid return requirement description' }) }) }) diff --git a/test/services/return-requirements/abstraction-period.service.test.js b/test/services/return-requirements/abstraction-period.service.test.js index e75d0bec72..9cf9a1cd52 100644 --- a/test/services/return-requirements/abstraction-period.service.test.js +++ b/test/services/return-requirements/abstraction-period.service.test.js @@ -45,17 +45,12 @@ describe('Abstraction Period service', () => { const result = await AbstractionPeriodService.go(session.id) expect(result).to.equal({ + abstractionPeriod: null, activeNavBar: 'search', pageTitle: 'Enter the abstraction period for the requirements for returns', id: '465c6792-dd84-4163-a808-cbb834a779be', licenceId: '8b7f78ba-f3ad-4cb6-a058-78abc4d1383d', - licenceRef: '01/ABC', - abstractionPeriod: { - startDay: null, - startMonth: null, - endDay: null, - endMonth: null - } + licenceRef: '01/ABC' }, { skip: ['id'] }) }) }) diff --git a/test/services/return-requirements/points.service.test.js b/test/services/return-requirements/points.service.test.js index 647dca6679..59e805179f 100644 --- a/test/services/return-requirements/points.service.test.js +++ b/test/services/return-requirements/points.service.test.js @@ -93,7 +93,8 @@ describe('Select Points service', () => { 'At National Grid Reference TQ 68083 33604 (BEWL WATER RESERVOIR)' ], licenceRef: '01/ABC', - pageTitle: 'Select the points for the requirements for returns' + pageTitle: 'Select the points for the requirements for returns', + selectedPoints: '' }, { skip: ['id'] }) }) }) diff --git a/test/services/return-requirements/reason.service.test.js b/test/services/return-requirements/reason.service.test.js index 1fc9bbf377..3afbcd2fb0 100644 --- a/test/services/return-requirements/reason.service.test.js +++ b/test/services/return-requirements/reason.service.test.js @@ -48,7 +48,8 @@ describe('Select Reason service', () => { activeNavBar: 'search', checkYourAnswersVisited: false, pageTitle: 'Select the reason for the return requirement', - licenceRef: '01/ABC' + licenceRef: '01/ABC', + reason: null }, { skip: ['id'] }) }) }) diff --git a/test/services/return-requirements/setup.service.test.js b/test/services/return-requirements/setup.service.test.js index 79f19c04f2..04070d5078 100644 --- a/test/services/return-requirements/setup.service.test.js +++ b/test/services/return-requirements/setup.service.test.js @@ -46,7 +46,8 @@ describe('Select Reason service', () => { expect(result).to.equal({ activeNavBar: 'search', pageTitle: 'How do you want to set up the return requirement?', - licenceRef: '01/ABC' + licenceRef: '01/ABC', + setup: null }, { skip: ['id'] }) }) }) diff --git a/test/services/return-requirements/site-description.service.test.js b/test/services/return-requirements/site-description.service.test.js index 742c169336..b7c0324b1a 100644 --- a/test/services/return-requirements/site-description.service.test.js +++ b/test/services/return-requirements/site-description.service.test.js @@ -49,7 +49,7 @@ describe('Site Description service', () => { pageTitle: 'Enter a site description for the requirements for returns', licenceId: '8b7f78ba-f3ad-4cb6-a058-78abc4d1383d', licenceRef: '01/ABC', - licenceSiteDescription: null + siteDescription: null }, { skip: ['id'] }) }) }) diff --git a/test/services/return-requirements/submit-abstraction-period.service.test.js b/test/services/return-requirements/submit-abstraction-period.service.test.js index fe7128038c..0cfa1234b4 100644 --- a/test/services/return-requirements/submit-abstraction-period.service.test.js +++ b/test/services/return-requirements/submit-abstraction-period.service.test.js @@ -83,18 +83,13 @@ describe('Submit Abstraction Period service', () => { const result = await SubmitAbstractionPeriodService.go(session.id, payload) expect(result).to.equal({ + abstractionPeriod: null, activeNavBar: 'search', error: null, pageTitle: 'Enter the abstraction period for the requirements for returns', id: 'aeb46f58-3431-42af-8724-361a7779becf', licenceId: '8b7f78ba-f3ad-4cb6-a058-78abc4d1383d', - licenceRef: '01/ABC', - abstractionPeriod: { - startDay: null, - startMonth: null, - endDay: null, - endMonth: null - } + licenceRef: '01/ABC' }, { skip: ['id', 'error'] }) }) @@ -137,10 +132,10 @@ describe('Submit Abstraction Period service', () => { licenceId: '8b7f78ba-f3ad-4cb6-a058-78abc4d1383d', licenceRef: '01/ABC', abstractionPeriod: { - startDay: null, - startMonth: null, - endDay: '02', - endMonth: '7' + 'start-abstraction-period-day': null, + 'start-abstraction-period-month': null, + 'end-abstraction-period-day': '02', + 'end-abstraction-period-month': '7' } }, { skip: ['id', 'error'] }) }) @@ -184,10 +179,10 @@ describe('Submit Abstraction Period service', () => { licenceId: '8b7f78ba-f3ad-4cb6-a058-78abc4d1383d', licenceRef: '01/ABC', abstractionPeriod: { - startDay: '08', - startMonth: '12', - endDay: null, - endMonth: null + 'start-abstraction-period-day': '08', + 'start-abstraction-period-month': '12', + 'end-abstraction-period-day': null, + 'end-abstraction-period-month': null } }, { skip: ['id', 'error'] }) }) @@ -231,10 +226,10 @@ describe('Submit Abstraction Period service', () => { licenceId: '8b7f78ba-f3ad-4cb6-a058-78abc4d1383d', licenceRef: '01/ABC', abstractionPeriod: { - startDay: 'abc', - startMonth: '123', - endDay: 'abc', - endMonth: '123' + 'start-abstraction-period-day': 'abc', + 'start-abstraction-period-month': '123', + 'end-abstraction-period-day': 'abc', + 'end-abstraction-period-month': '123' } }, { skip: ['id', 'error'] }) }) @@ -278,10 +273,10 @@ describe('Submit Abstraction Period service', () => { licenceId: '8b7f78ba-f3ad-4cb6-a058-78abc4d1383d', licenceRef: '01/ABC', abstractionPeriod: { - startDay: 'abc', - startMonth: '123', - endDay: '02', - endMonth: '07' + 'start-abstraction-period-day': 'abc', + 'start-abstraction-period-month': '123', + 'end-abstraction-period-day': '02', + 'end-abstraction-period-month': '07' } }, { skip: ['id', 'error'] }) }) @@ -325,10 +320,10 @@ describe('Submit Abstraction Period service', () => { licenceId: '8b7f78ba-f3ad-4cb6-a058-78abc4d1383d', licenceRef: '01/ABC', abstractionPeriod: { - startDay: '08', - startMonth: '12', - endDay: 'abc', - endMonth: '123' + 'start-abstraction-period-day': '08', + 'start-abstraction-period-month': '12', + 'end-abstraction-period-day': 'abc', + 'end-abstraction-period-month': '123' } }, { skip: ['id', 'error'] }) }) diff --git a/test/services/return-requirements/submit-points.service.test.js b/test/services/return-requirements/submit-points.service.test.js index 4a7159e13d..fbf6bb24f6 100644 --- a/test/services/return-requirements/submit-points.service.test.js +++ b/test/services/return-requirements/submit-points.service.test.js @@ -59,25 +59,20 @@ describe('Submit Points service', () => { Sinon.stub(PointsValidator, 'go').resolves(null) }) - it('fetches the current setup session record', async () => { - const result = await SubmitPointsService.go(session.id, payload) + it('saves the submitted value', async () => { + await SubmitPointsService.go(session.id, payload) - expect(result.id).to.equal(session.id) + const refreshedSession = await session.$query() + + expect(refreshedSession.data.points).to.equal([ + 'At National Grid Reference TQ 69212 50394 (RIVER MEDWAY AT YALDING INTAKE)' + ]) }) - it('returns page data for the view', async () => { + it('returns an empty object (no page data needed for a redirect)', async () => { const result = await SubmitPointsService.go(session.id, payload) - expect(result).to.equal({ - activeNavBar: 'search', - error: null, - pageTitle: 'Select the points for the requirements for returns', - licenceId: '8b7f78ba-f3ad-4cb6-a058-78abc4d1383d', - licenceRef: '01/ABC', - licencePoints: [ - 'At National Grid Reference TQ 69212 50394 (RIVER MEDWAY AT YALDING INTAKE)' - ] - }, { skip: ['id'] }) + expect(result).to.equal({}) }) }) }) @@ -107,7 +102,8 @@ describe('Submit Points service', () => { licencePoints: [ 'At National Grid Reference TQ 69212 50394 (RIVER MEDWAY AT YALDING INTAKE)', 'At National Grid Reference TQ 68083 33604 (BEWL WATER RESERVOIR)' - ] + ], + selectedPoints: '' }, { skip: ['id', 'error'] }) }) diff --git a/test/services/return-requirements/submit-reason.service.test.js b/test/services/return-requirements/submit-reason.service.test.js index c5711da4f0..4b720ebf67 100644 --- a/test/services/return-requirements/submit-reason.service.test.js +++ b/test/services/return-requirements/submit-reason.service.test.js @@ -61,7 +61,8 @@ describe('Submit Reason service', () => { activeNavBar: 'search', checkYourAnswersVisited: false, pageTitle: 'Select the reason for the return requirement', - licenceRef: '01/ABC' + licenceRef: '01/ABC', + reason: null }, { skip: ['id', 'error'] }) }) }) @@ -85,7 +86,8 @@ describe('Submit Reason service', () => { activeNavBar: 'search', checkYourAnswersVisited: false, pageTitle: 'Select the reason for the return requirement', - licenceRef: '01/ABC' + licenceRef: '01/ABC', + reason: null }, { skip: ['id', 'error'] }) }) diff --git a/test/services/return-requirements/submit-setup.service.test.js b/test/services/return-requirements/submit-setup.service.test.js index ddf3cb27ac..eb8ed39739 100644 --- a/test/services/return-requirements/submit-setup.service.test.js +++ b/test/services/return-requirements/submit-setup.service.test.js @@ -40,7 +40,7 @@ describe('Submit Setup service', () => { describe('with a valid payload', () => { beforeEach(() => { payload = { - setup: 'use_abstraction_data' + setup: 'use-abstraction-data' } }) @@ -69,7 +69,8 @@ describe('Submit Setup service', () => { expect(result).to.equal({ activeNavBar: 'search', licenceRef: '01/ABC', - pageTitle: 'How do you want to set up the return requirement?' + pageTitle: 'How do you want to set up the return requirement?', + setup: null }, { skip: ['id', 'error'] }) }) @@ -85,10 +86,10 @@ describe('Submit Setup service', () => { }) describe('with different setups', () => { - describe('and setup is use_abstraction_data', () => { + describe('and setup is use-abstraction-data', () => { beforeEach(() => { payload = { - setup: 'use_abstraction_data' + setup: 'use-abstraction-data' } }) @@ -98,10 +99,10 @@ describe('Submit Setup service', () => { }) }) - describe('and setup is set_up_manually', () => { + describe('and setup is set-up-manually', () => { beforeEach(() => { payload = { - setup: 'set_up_manually' + setup: 'set-up-manually' } }) diff --git a/test/services/return-requirements/submit-site-description.service.test.js b/test/services/return-requirements/submit-site-description.service.test.js index 6f1ff5f5ad..047b791d1f 100644 --- a/test/services/return-requirements/submit-site-description.service.test.js +++ b/test/services/return-requirements/submit-site-description.service.test.js @@ -44,24 +44,18 @@ describe('Submit Site Description service', () => { } }) - it('fetches the current setup session record', async () => { - const result = await SubmitSiteDescriptionService.go(session.id, payload) + it('saves the submitted value', async () => { + await SubmitSiteDescriptionService.go(session.id, payload) + + const refreshedSession = await session.$query() - expect(result.id).to.equal(session.id) + expect(refreshedSession.data.siteDescription).to.equal('This is a valid return requirement description') }) - it('returns page data for the view', async () => { + it('returns an empty object (no page data needed for a redirect)', async () => { const result = await SubmitSiteDescriptionService.go(session.id, payload) - expect(result).to.equal({ - activeNavBar: 'search', - error: null, - journey: 'returns-required', - pageTitle: 'Enter a site description for the requirements for returns', - licenceId: '8b7f78ba-f3ad-4cb6-a058-78abc4d1383d', - licenceRef: '01/ABC', - licenceSiteDescription: 'This is a valid return requirement description' - }, { skip: ['id'] }) + expect(result).to.equal({}) }) }) @@ -82,11 +76,10 @@ describe('Submit Site Description service', () => { expect(result).to.equal({ activeNavBar: 'search', - journey: 'returns-required', pageTitle: 'Enter a site description for the requirements for returns', licenceId: '8b7f78ba-f3ad-4cb6-a058-78abc4d1383d', licenceRef: '01/ABC', - licenceSiteDescription: null + siteDescription: null }, { skip: ['id', 'error'] }) }) @@ -117,11 +110,10 @@ describe('Submit Site Description service', () => { expect(result).to.equal({ activeNavBar: 'search', - journey: 'returns-required', pageTitle: 'Enter a site description for the requirements for returns', licenceId: '8b7f78ba-f3ad-4cb6-a058-78abc4d1383d', licenceRef: '01/ABC', - licenceSiteDescription: 'Too short' + siteDescription: 'Too short' }, { skip: ['id', 'error'] }) }) @@ -135,7 +127,8 @@ describe('Submit Site Description service', () => { }) describe('because the user has entered a description more than 100 characters', () => { - const invalidSiteDescription = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis.' + const invalidSiteDescription = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit' + + ', sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis.' beforeEach(() => { payload = { @@ -154,11 +147,10 @@ describe('Submit Site Description service', () => { expect(result).to.equal({ activeNavBar: 'search', - journey: 'returns-required', pageTitle: 'Enter a site description for the requirements for returns', licenceId: '8b7f78ba-f3ad-4cb6-a058-78abc4d1383d', licenceRef: '01/ABC', - licenceSiteDescription: invalidSiteDescription + siteDescription: invalidSiteDescription }, { skip: ['id', 'error'] }) }) diff --git a/test/validators/no-returns-required/setup.validator.test.js b/test/validators/no-returns-required/setup.validator.test.js index 10bbf3c023..faa6d9fcae 100644 --- a/test/validators/no-returns-required/setup.validator.test.js +++ b/test/validators/no-returns-required/setup.validator.test.js @@ -13,7 +13,7 @@ const SetupValidator = require('../../../app/validators/return-requirements/setu describe('Setup validator', () => { describe('when valid data is provided', () => { it('confirms the data is valid', () => { - const result = SetupValidator.go({ setup: 'use_abstraction_data' }) + const result = SetupValidator.go({ setup: 'use-abstraction-data' }) expect(result.value).to.exist() expect(result.error).not.to.exist()