@defra/forms-engine-plugin 3.0.6 → 3.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.public/javascripts/application.min.js +1 -1
- package/.public/javascripts/application.min.js.map +1 -1
- package/.public/javascripts/shared.min.js +1 -1
- package/.public/javascripts/shared.min.js.map +1 -1
- package/.server/client/javascripts/file-upload.d.ts +1 -0
- package/.server/client/javascripts/file-upload.js +8 -2
- package/.server/client/javascripts/file-upload.js.map +1 -1
- package/.server/server/plugins/engine/models/FormModel.d.ts +1 -1
- package/.server/server/plugins/engine/models/FormModel.js +11 -10
- package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
- package/.server/server/plugins/engine/routes/file-upload.js +3 -1
- package/.server/server/plugins/engine/routes/file-upload.js.map +1 -1
- package/package.json +1 -1
- package/src/client/javascripts/file-upload.js +16 -2
- package/src/server/index.test.ts +3 -1
- package/src/server/plugins/engine/models/FormModel.ts +13 -9
- package/src/server/plugins/engine/pageControllers/QuestionPageController.test.ts +235 -18
- package/src/server/plugins/engine/routes/file-upload.ts +3 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Build the upload status URL given the current pathname and the upload ID.
|
|
3
|
+
* This only works when called on a file upload page that has a maximum depth of 1 URL segments following the slug.
|
|
3
4
|
* @param {string} pathname – e.g. window.location.pathname
|
|
4
5
|
* @param {string} uploadId
|
|
5
6
|
* @returns {string} e.g. "/form/upload-status/abc123"
|
|
@@ -190,13 +190,19 @@ function reloadPage() {
|
|
|
190
190
|
|
|
191
191
|
/**
|
|
192
192
|
* Build the upload status URL given the current pathname and the upload ID.
|
|
193
|
+
* This only works when called on a file upload page that has a maximum depth of 1 URL segments following the slug.
|
|
193
194
|
* @param {string} pathname – e.g. window.location.pathname
|
|
194
195
|
* @param {string} uploadId
|
|
195
196
|
* @returns {string} e.g. "/form/upload-status/abc123"
|
|
196
197
|
*/
|
|
197
198
|
export function buildUploadStatusUrl(pathname, uploadId) {
|
|
198
|
-
|
|
199
|
-
const
|
|
199
|
+
// Remove preview markers and duplicate slashes
|
|
200
|
+
const normalisedPath = pathname.replace(/\/preview\/(draft|live)/g, '').replace(/\/{2,}/g, '/').replace(/\/$/, '');
|
|
201
|
+
const segments = normalisedPath.split('/').filter(Boolean);
|
|
202
|
+
|
|
203
|
+
// The slug is always the second to last segment
|
|
204
|
+
// The prefix is everything before the slug
|
|
205
|
+
const prefix = segments.length > 2 ? `/${segments.slice(0, segments.length - 2).join('/')}` : '';
|
|
200
206
|
return `${prefix}/upload-status/${uploadId}`;
|
|
201
207
|
}
|
|
202
208
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-upload.js","names":["MAX_POLLING_DURATION","ARIA_DESCRIBEDBY","ERROR_SUMMARY_TITLE_ID","createOrUpdateStatusAnnouncer","form","fileCountP","statusAnnouncer","querySelector","document","createElement","id","className","setAttribute","addStatusAnnouncerToDOM","asHTMLElement","appendChild","body","nextSibling","parentNode","insertBefore","parentElement","findOrCreateSummaryList","summaryList","buttonGroup","createFileRow","selectedFile","statusText","row","name","innerHTML","renderSummary","container","getElementById","uploadForm","closest","HTMLFormElement","fileInput","existingRow","remove","firstChild","textContent","showError","message","errorSummary","topErrorSummary","titleElement","removeAttribute","formGroup","classList","add","inputId","errorMessage","element","reloadPage","window","history","replaceState","location","href","pathname","buildUploadStatusUrl","uploadId","pathSegments","split","filter","segment","prefix","length","pollUploadStatus","attempts","interval","setInterval","clearInterval","uploadStatusUrl","fetch","headers","Accept","then","response","ok","Error","json","data","uploadStatus","catch","handleStandardFormSubmission","formElement","uploadButton","continueButton","focus","setTimeout","disabled","handleAjaxFormSubmission","event","action","preventDefault","formData","FormData","isLocalDev","dataset","proxyUrl","uploadUrl","fetchOptions","method","redirect","mode","initUpload","Array","from","querySelectorAll","find","button","trim","isSubmitting","addEventListener","files","initFileUpload","readyState"],"sources":["../../../src/client/javascripts/file-upload.js"],"sourcesContent":["export const MAX_POLLING_DURATION = 300 // 5 minutes\nconst ARIA_DESCRIBEDBY = 'aria-describedby'\nconst ERROR_SUMMARY_TITLE_ID = 'error-summary-title'\n\n/**\n * Creates or updates status announcer for screen readers\n * @param {HTMLElement | null} form - The form element\n * @param {HTMLElement | null} fileCountP - The file count paragraph element\n * @returns {HTMLElement} The status announcer element\n */\nfunction createOrUpdateStatusAnnouncer(form, fileCountP) {\n let statusAnnouncer = form?.querySelector('#statusInformation')\n\n if (!statusAnnouncer) {\n statusAnnouncer = document.createElement('div')\n statusAnnouncer.id = 'statusInformation'\n statusAnnouncer.className = 'govuk-visually-hidden'\n statusAnnouncer.setAttribute('aria-live', 'polite')\n\n // multiple fallbacks to ensure the status announcer is always added to the DOM\n // this helps with cross-browser compatibility and unexpected DOM structures encountered during QA\n try {\n addStatusAnnouncerToDOM(\n asHTMLElement(form),\n asHTMLElement(fileCountP),\n asHTMLElement(statusAnnouncer)\n )\n } catch {\n try {\n form?.appendChild(statusAnnouncer)\n } catch {\n document.body.appendChild(statusAnnouncer)\n }\n }\n }\n\n return /** @type {HTMLElement} */ (statusAnnouncer)\n}\n\n/**\n * Helper function to add the status announcer to the DOM\n * @param {HTMLElement} form - The form element\n * @param {HTMLElement | null} fileCountP - The file count paragraph element\n * @param {HTMLElement} statusAnnouncer - The status announcer element to add\n */\nfunction addStatusAnnouncerToDOM(form, fileCountP, statusAnnouncer) {\n if (fileCountP?.nextSibling && fileCountP.parentNode === form) {\n form.insertBefore(statusAnnouncer, fileCountP.nextSibling)\n return\n }\n\n const parentElement = fileCountP?.parentNode ?? form\n parentElement.appendChild(statusAnnouncer)\n}\n\n/**\n * Finds an existing summary list or creates a new one\n * @param {HTMLFormElement} form - The form element\n * @param {HTMLElement} fileCountP - The file count paragraph element\n * @returns {HTMLElement} The summary list element\n */\nfunction findOrCreateSummaryList(form, fileCountP) {\n let summaryList = form.querySelector('dl.govuk-summary-list')\n\n if (!summaryList) {\n summaryList = document.createElement('dl')\n summaryList.className = 'govuk-summary-list govuk-summary-list--long-key'\n\n const buttonGroup = form.querySelector('.govuk-button-group')\n\n if (buttonGroup) {\n form.insertBefore(summaryList, buttonGroup)\n } else {\n form.insertBefore(summaryList, fileCountP.nextSibling)\n }\n }\n\n return /** @type {HTMLElement} */ (summaryList)\n}\n\n/**\n * Creates a file row element for the summary list\n * @param {File | null} selectedFile - The selected file\n * @param {string} statusText - The status to display\n * @returns {HTMLElement} The created row element\n */\nfunction createFileRow(selectedFile, statusText) {\n const row = document.createElement('div')\n row.className = 'govuk-summary-list__row'\n row.setAttribute('data-filename', selectedFile?.name ?? '')\n row.innerHTML = `\n <dt class=\"govuk-summary-list__key\">\n ${selectedFile?.name ?? ''}\n </dt>\n <dd class=\"govuk-summary-list__value\">\n <strong class=\"govuk-tag govuk-tag--yellow\">${statusText}</strong>\n </dd>\n <dd class=\"govuk-summary-list__actions\">\n </dd>\n `\n return row\n}\n\n/**\n * Renders or updates the file summary box for the selected file\n * @param {File | null} selectedFile - The selected file\n * @param {string} statusText - The status to display\n * @param {HTMLElement} form - The form element\n */\nfunction renderSummary(selectedFile, statusText, form) {\n const container = document.getElementById('uploadedFilesContainer')\n const uploadForm = container ? container.closest('form') : null\n\n if (!uploadForm || !(uploadForm instanceof HTMLFormElement)) {\n return\n }\n\n const fileCountP = uploadForm.querySelector('p.govuk-body')\n\n if (!fileCountP) {\n return\n }\n\n const statusAnnouncer = createOrUpdateStatusAnnouncer(\n /** @type {HTMLElement} */ (uploadForm),\n /** @type {HTMLElement | null} */ (fileCountP)\n )\n\n const fileInput = form.querySelector('input[type=\"file\"]')\n\n if (fileInput) {\n fileInput.setAttribute(ARIA_DESCRIBEDBY, 'statusInformation')\n }\n\n const summaryList = findOrCreateSummaryList(\n /** @type {HTMLFormElement} */ (uploadForm),\n /** @type {HTMLElement} */ (fileCountP)\n )\n\n const existingRow = document.querySelector(\n `[data-filename=\"${selectedFile?.name}\"]`\n )\n\n if (existingRow) {\n existingRow.remove()\n }\n\n const row = createFileRow(selectedFile, statusText)\n summaryList.insertBefore(row, summaryList.firstChild)\n statusAnnouncer.textContent = `${selectedFile?.name ?? ''} ${statusText}`\n}\n\n/**\n * Shows an error message using the GOV.UK error summary component\n * and adds inline error styling to the file input\n * @param {string} message - The error message to display\n * @param {HTMLElement | null} errorSummary - The error summary container\n * @param {HTMLInputElement} fileInput - The file input element\n * @returns {void}\n */\nfunction showError(message, errorSummary, fileInput) {\n const topErrorSummary = document.querySelector('.govuk-error-summary')\n\n if (topErrorSummary) {\n const titleElement = document.getElementById(ERROR_SUMMARY_TITLE_ID)\n if (titleElement) {\n fileInput.setAttribute(ARIA_DESCRIBEDBY, ERROR_SUMMARY_TITLE_ID)\n } else {\n fileInput.removeAttribute(ARIA_DESCRIBEDBY)\n }\n return\n }\n\n if (errorSummary) {\n errorSummary.innerHTML = `\n <div class=\"govuk-error-summary\" data-module=\"govuk-error-summary\">\n <div role=\"alert\">\n <h2 class=\"govuk-error-summary__title\" id=\"${ERROR_SUMMARY_TITLE_ID}\">\n There is a problem\n </h2>\n <div class=\"govuk-error-summary__body\">\n <ul class=\"govuk-list govuk-error-summary__list\">\n <li>\n <a href=\"#file-upload\">${message}</a>\n </li>\n </ul>\n </div>\n </div>\n </div>\n `\n\n fileInput.setAttribute(ARIA_DESCRIBEDBY, ERROR_SUMMARY_TITLE_ID)\n }\n\n const formGroup = fileInput.closest('.govuk-form-group')\n if (formGroup) {\n formGroup.classList.add('govuk-form-group--error')\n fileInput.classList.add('govuk-file-upload--error')\n\n const inputId = fileInput.id\n let errorMessage = document.getElementById(`${inputId}-error`)\n\n if (!errorMessage) {\n errorMessage = document.createElement('p')\n errorMessage.id = `${inputId}-error`\n errorMessage.className = 'govuk-error-message'\n errorMessage.innerHTML = `<span class=\"govuk-visually-hidden\">Error:</span> ${message}`\n formGroup.insertBefore(errorMessage, fileInput)\n }\n\n fileInput.setAttribute(\n ARIA_DESCRIBEDBY,\n `error-summary-title ${inputId}-error`\n )\n }\n}\n\n/**\n * Helper to safely convert an Element to HTMLElement\n * @param {Element | null} element - The element to convert\n */\nfunction asHTMLElement(element) {\n return /** @type {HTMLElement} */ (element)\n}\n\nfunction reloadPage() {\n window.history.replaceState(null, '', window.location.href)\n window.location.href = window.location.pathname\n}\n\n/**\n * Build the upload status URL given the current pathname and the upload ID.\n * @param {string} pathname – e.g. window.location.pathname\n * @param {string} uploadId\n * @returns {string} e.g. \"/form/upload-status/abc123\"\n */\nexport function buildUploadStatusUrl(pathname, uploadId) {\n const pathSegments = pathname.split('/').filter((segment) => segment)\n const prefix = pathSegments.length > 0 ? `/${pathSegments[0]}` : ''\n return `${prefix}/upload-status/${uploadId}`\n}\n\n/**\n * Polls the upload status endpoint until the file is ready or timeout occurs\n * @param {string} uploadId - The upload ID to check\n */\nfunction pollUploadStatus(uploadId) {\n let attempts = 0\n const interval = setInterval(() => {\n attempts++\n\n if (attempts >= MAX_POLLING_DURATION) {\n clearInterval(interval)\n reloadPage()\n return\n }\n\n const uploadStatusUrl = buildUploadStatusUrl(\n window.location.pathname,\n uploadId\n )\n\n fetch(uploadStatusUrl, {\n headers: {\n Accept: 'application/json'\n }\n })\n .then((response) => {\n if (!response.ok) {\n throw new Error('Network response was not ok')\n }\n return response.json()\n })\n .then((data) => {\n if (data.uploadStatus === 'ready') {\n clearInterval(interval)\n reloadPage()\n }\n })\n .catch(() => {\n clearInterval(interval)\n reloadPage()\n })\n }, 1000)\n}\n\n/**\n * Handle standard form submission for file upload\n * @param {HTMLFormElement} formElement - The form element\n * @param {HTMLInputElement} fileInput - The file input element\n * @param {HTMLButtonElement} uploadButton - The upload button\n * @param {HTMLButtonElement} continueButton - The continue button\n * @param {File | null} selectedFile - The selected file\n */\nfunction handleStandardFormSubmission(\n formElement,\n fileInput,\n uploadButton,\n continueButton,\n selectedFile\n) {\n renderSummary(selectedFile, 'Uploading…', formElement)\n\n fileInput.focus()\n\n setTimeout(() => {\n fileInput.disabled = true\n uploadButton.disabled = true\n continueButton.disabled = true\n }, 100)\n}\n\n/**\n * Handle AJAX form submission with upload ID\n * @param {Event} event - The click event\n * @param {HTMLFormElement} formElement - The form element\n * @param {HTMLInputElement} fileInput - The file input element\n * @param {HTMLButtonElement} uploadButton - The upload button\n * @param {HTMLElement | null} errorSummary - The error summary container\n * @param {string | undefined} uploadId - The upload ID\n * @returns {boolean} Whether the event was handled\n */\nfunction handleAjaxFormSubmission(\n event,\n formElement,\n fileInput,\n uploadButton,\n errorSummary,\n uploadId\n) {\n if (!formElement.action || !uploadId) {\n return false\n }\n\n event.preventDefault()\n\n const formData = new FormData(formElement)\n const isLocalDev = !!formElement.dataset.proxyUrl\n const uploadUrl = formElement.dataset.proxyUrl ?? formElement.action\n\n const fetchOptions = /** @type {RequestInit} */ ({\n method: 'POST',\n body: formData,\n redirect: isLocalDev ? 'follow' : 'manual' // follow mode if local development with the proxy\n })\n\n // no-cors mode if needed local development with the proxy\n if (isLocalDev) {\n fetchOptions.mode = 'no-cors'\n }\n\n fetch(uploadUrl, fetchOptions)\n .then(() => {\n pollUploadStatus(uploadId)\n })\n .catch(() => {\n fileInput.disabled = false\n uploadButton.disabled = false\n\n showError(\n 'There was a problem uploading the file',\n errorSummary,\n fileInput\n )\n\n return null\n })\n\n return true\n}\n\nfunction initUpload() {\n const form = document.querySelector('form:has(input[type=\"file\"])')\n /** @type {HTMLInputElement | null} */\n const fileInput = form ? form.querySelector('input[type=\"file\"]') : null\n /** @type {HTMLButtonElement | null} */\n const uploadButton = form ? form.querySelector('.upload-file-button') : null\n const continueButton =\n /** @type {HTMLButtonElement} */ (\n Array.from(document.querySelectorAll('button.govuk-button')).find(\n (button) => button.textContent?.trim() === 'Continue'\n )\n ) ?? null\n\n const errorSummary = document.querySelector('.govuk-error-summary-container')\n\n if (!form || !fileInput || !uploadButton) {\n return\n }\n\n const formElement = /** @type {HTMLFormElement} */ (form)\n /** @type {File | null} */\n let selectedFile = null\n let isSubmitting = false\n const uploadId = formElement.dataset.uploadId\n\n fileInput.addEventListener('change', () => {\n if (errorSummary) {\n errorSummary.innerHTML = ''\n }\n\n if (fileInput.files && fileInput.files.length > 0) {\n selectedFile = fileInput.files[0]\n }\n })\n\n uploadButton.addEventListener('click', (event) => {\n if (!selectedFile) {\n event.preventDefault()\n showError(\n 'Select a file',\n /** @type {HTMLElement | null} */ (errorSummary),\n fileInput\n )\n return\n }\n\n if (isSubmitting) {\n event.preventDefault()\n return\n }\n\n isSubmitting = true\n\n handleStandardFormSubmission(\n formElement,\n fileInput,\n uploadButton,\n continueButton,\n selectedFile\n )\n\n handleAjaxFormSubmission(\n event,\n formElement,\n fileInput,\n uploadButton,\n /** @type {HTMLElement | null} */ (errorSummary),\n uploadId\n )\n })\n}\n\nexport function initFileUpload() {\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', initUpload)\n } else {\n initUpload()\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"file-upload.js","names":["MAX_POLLING_DURATION","ARIA_DESCRIBEDBY","ERROR_SUMMARY_TITLE_ID","createOrUpdateStatusAnnouncer","form","fileCountP","statusAnnouncer","querySelector","document","createElement","id","className","setAttribute","addStatusAnnouncerToDOM","asHTMLElement","appendChild","body","nextSibling","parentNode","insertBefore","parentElement","findOrCreateSummaryList","summaryList","buttonGroup","createFileRow","selectedFile","statusText","row","name","innerHTML","renderSummary","container","getElementById","uploadForm","closest","HTMLFormElement","fileInput","existingRow","remove","firstChild","textContent","showError","message","errorSummary","topErrorSummary","titleElement","removeAttribute","formGroup","classList","add","inputId","errorMessage","element","reloadPage","window","history","replaceState","location","href","pathname","buildUploadStatusUrl","uploadId","normalisedPath","replace","segments","split","filter","Boolean","prefix","length","slice","join","pollUploadStatus","attempts","interval","setInterval","clearInterval","uploadStatusUrl","fetch","headers","Accept","then","response","ok","Error","json","data","uploadStatus","catch","handleStandardFormSubmission","formElement","uploadButton","continueButton","focus","setTimeout","disabled","handleAjaxFormSubmission","event","action","preventDefault","formData","FormData","isLocalDev","dataset","proxyUrl","uploadUrl","fetchOptions","method","redirect","mode","initUpload","Array","from","querySelectorAll","find","button","trim","isSubmitting","addEventListener","files","initFileUpload","readyState"],"sources":["../../../src/client/javascripts/file-upload.js"],"sourcesContent":["export const MAX_POLLING_DURATION = 300 // 5 minutes\nconst ARIA_DESCRIBEDBY = 'aria-describedby'\nconst ERROR_SUMMARY_TITLE_ID = 'error-summary-title'\n\n/**\n * Creates or updates status announcer for screen readers\n * @param {HTMLElement | null} form - The form element\n * @param {HTMLElement | null} fileCountP - The file count paragraph element\n * @returns {HTMLElement} The status announcer element\n */\nfunction createOrUpdateStatusAnnouncer(form, fileCountP) {\n let statusAnnouncer = form?.querySelector('#statusInformation')\n\n if (!statusAnnouncer) {\n statusAnnouncer = document.createElement('div')\n statusAnnouncer.id = 'statusInformation'\n statusAnnouncer.className = 'govuk-visually-hidden'\n statusAnnouncer.setAttribute('aria-live', 'polite')\n\n // multiple fallbacks to ensure the status announcer is always added to the DOM\n // this helps with cross-browser compatibility and unexpected DOM structures encountered during QA\n try {\n addStatusAnnouncerToDOM(\n asHTMLElement(form),\n asHTMLElement(fileCountP),\n asHTMLElement(statusAnnouncer)\n )\n } catch {\n try {\n form?.appendChild(statusAnnouncer)\n } catch {\n document.body.appendChild(statusAnnouncer)\n }\n }\n }\n\n return /** @type {HTMLElement} */ (statusAnnouncer)\n}\n\n/**\n * Helper function to add the status announcer to the DOM\n * @param {HTMLElement} form - The form element\n * @param {HTMLElement | null} fileCountP - The file count paragraph element\n * @param {HTMLElement} statusAnnouncer - The status announcer element to add\n */\nfunction addStatusAnnouncerToDOM(form, fileCountP, statusAnnouncer) {\n if (fileCountP?.nextSibling && fileCountP.parentNode === form) {\n form.insertBefore(statusAnnouncer, fileCountP.nextSibling)\n return\n }\n\n const parentElement = fileCountP?.parentNode ?? form\n parentElement.appendChild(statusAnnouncer)\n}\n\n/**\n * Finds an existing summary list or creates a new one\n * @param {HTMLFormElement} form - The form element\n * @param {HTMLElement} fileCountP - The file count paragraph element\n * @returns {HTMLElement} The summary list element\n */\nfunction findOrCreateSummaryList(form, fileCountP) {\n let summaryList = form.querySelector('dl.govuk-summary-list')\n\n if (!summaryList) {\n summaryList = document.createElement('dl')\n summaryList.className = 'govuk-summary-list govuk-summary-list--long-key'\n\n const buttonGroup = form.querySelector('.govuk-button-group')\n\n if (buttonGroup) {\n form.insertBefore(summaryList, buttonGroup)\n } else {\n form.insertBefore(summaryList, fileCountP.nextSibling)\n }\n }\n\n return /** @type {HTMLElement} */ (summaryList)\n}\n\n/**\n * Creates a file row element for the summary list\n * @param {File | null} selectedFile - The selected file\n * @param {string} statusText - The status to display\n * @returns {HTMLElement} The created row element\n */\nfunction createFileRow(selectedFile, statusText) {\n const row = document.createElement('div')\n row.className = 'govuk-summary-list__row'\n row.setAttribute('data-filename', selectedFile?.name ?? '')\n row.innerHTML = `\n <dt class=\"govuk-summary-list__key\">\n ${selectedFile?.name ?? ''}\n </dt>\n <dd class=\"govuk-summary-list__value\">\n <strong class=\"govuk-tag govuk-tag--yellow\">${statusText}</strong>\n </dd>\n <dd class=\"govuk-summary-list__actions\">\n </dd>\n `\n return row\n}\n\n/**\n * Renders or updates the file summary box for the selected file\n * @param {File | null} selectedFile - The selected file\n * @param {string} statusText - The status to display\n * @param {HTMLElement} form - The form element\n */\nfunction renderSummary(selectedFile, statusText, form) {\n const container = document.getElementById('uploadedFilesContainer')\n const uploadForm = container ? container.closest('form') : null\n\n if (!uploadForm || !(uploadForm instanceof HTMLFormElement)) {\n return\n }\n\n const fileCountP = uploadForm.querySelector('p.govuk-body')\n\n if (!fileCountP) {\n return\n }\n\n const statusAnnouncer = createOrUpdateStatusAnnouncer(\n /** @type {HTMLElement} */ (uploadForm),\n /** @type {HTMLElement | null} */ (fileCountP)\n )\n\n const fileInput = form.querySelector('input[type=\"file\"]')\n\n if (fileInput) {\n fileInput.setAttribute(ARIA_DESCRIBEDBY, 'statusInformation')\n }\n\n const summaryList = findOrCreateSummaryList(\n /** @type {HTMLFormElement} */ (uploadForm),\n /** @type {HTMLElement} */ (fileCountP)\n )\n\n const existingRow = document.querySelector(\n `[data-filename=\"${selectedFile?.name}\"]`\n )\n\n if (existingRow) {\n existingRow.remove()\n }\n\n const row = createFileRow(selectedFile, statusText)\n summaryList.insertBefore(row, summaryList.firstChild)\n statusAnnouncer.textContent = `${selectedFile?.name ?? ''} ${statusText}`\n}\n\n/**\n * Shows an error message using the GOV.UK error summary component\n * and adds inline error styling to the file input\n * @param {string} message - The error message to display\n * @param {HTMLElement | null} errorSummary - The error summary container\n * @param {HTMLInputElement} fileInput - The file input element\n * @returns {void}\n */\nfunction showError(message, errorSummary, fileInput) {\n const topErrorSummary = document.querySelector('.govuk-error-summary')\n\n if (topErrorSummary) {\n const titleElement = document.getElementById(ERROR_SUMMARY_TITLE_ID)\n if (titleElement) {\n fileInput.setAttribute(ARIA_DESCRIBEDBY, ERROR_SUMMARY_TITLE_ID)\n } else {\n fileInput.removeAttribute(ARIA_DESCRIBEDBY)\n }\n return\n }\n\n if (errorSummary) {\n errorSummary.innerHTML = `\n <div class=\"govuk-error-summary\" data-module=\"govuk-error-summary\">\n <div role=\"alert\">\n <h2 class=\"govuk-error-summary__title\" id=\"${ERROR_SUMMARY_TITLE_ID}\">\n There is a problem\n </h2>\n <div class=\"govuk-error-summary__body\">\n <ul class=\"govuk-list govuk-error-summary__list\">\n <li>\n <a href=\"#file-upload\">${message}</a>\n </li>\n </ul>\n </div>\n </div>\n </div>\n `\n\n fileInput.setAttribute(ARIA_DESCRIBEDBY, ERROR_SUMMARY_TITLE_ID)\n }\n\n const formGroup = fileInput.closest('.govuk-form-group')\n if (formGroup) {\n formGroup.classList.add('govuk-form-group--error')\n fileInput.classList.add('govuk-file-upload--error')\n\n const inputId = fileInput.id\n let errorMessage = document.getElementById(`${inputId}-error`)\n\n if (!errorMessage) {\n errorMessage = document.createElement('p')\n errorMessage.id = `${inputId}-error`\n errorMessage.className = 'govuk-error-message'\n errorMessage.innerHTML = `<span class=\"govuk-visually-hidden\">Error:</span> ${message}`\n formGroup.insertBefore(errorMessage, fileInput)\n }\n\n fileInput.setAttribute(\n ARIA_DESCRIBEDBY,\n `error-summary-title ${inputId}-error`\n )\n }\n}\n\n/**\n * Helper to safely convert an Element to HTMLElement\n * @param {Element | null} element - The element to convert\n */\nfunction asHTMLElement(element) {\n return /** @type {HTMLElement} */ (element)\n}\n\nfunction reloadPage() {\n window.history.replaceState(null, '', window.location.href)\n window.location.href = window.location.pathname\n}\n\n/**\n * Build the upload status URL given the current pathname and the upload ID.\n * This only works when called on a file upload page that has a maximum depth of 1 URL segments following the slug.\n * @param {string} pathname – e.g. window.location.pathname\n * @param {string} uploadId\n * @returns {string} e.g. \"/form/upload-status/abc123\"\n */\nexport function buildUploadStatusUrl(pathname, uploadId) {\n // Remove preview markers and duplicate slashes\n const normalisedPath = pathname\n .replace(/\\/preview\\/(draft|live)/g, '')\n .replace(/\\/{2,}/g, '/')\n .replace(/\\/$/, '')\n\n const segments = normalisedPath.split('/').filter(Boolean)\n\n // The slug is always the second to last segment\n // The prefix is everything before the slug\n const prefix =\n segments.length > 2\n ? `/${segments.slice(0, segments.length - 2).join('/')}`\n : ''\n\n return `${prefix}/upload-status/${uploadId}`\n}\n\n/**\n * Polls the upload status endpoint until the file is ready or timeout occurs\n * @param {string} uploadId - The upload ID to check\n */\nfunction pollUploadStatus(uploadId) {\n let attempts = 0\n const interval = setInterval(() => {\n attempts++\n\n if (attempts >= MAX_POLLING_DURATION) {\n clearInterval(interval)\n reloadPage()\n return\n }\n\n const uploadStatusUrl = buildUploadStatusUrl(\n window.location.pathname,\n uploadId\n )\n\n fetch(uploadStatusUrl, {\n headers: {\n Accept: 'application/json'\n }\n })\n .then((response) => {\n if (!response.ok) {\n throw new Error('Network response was not ok')\n }\n return response.json()\n })\n .then((data) => {\n if (data.uploadStatus === 'ready') {\n clearInterval(interval)\n reloadPage()\n }\n })\n .catch(() => {\n clearInterval(interval)\n reloadPage()\n })\n }, 1000)\n}\n\n/**\n * Handle standard form submission for file upload\n * @param {HTMLFormElement} formElement - The form element\n * @param {HTMLInputElement} fileInput - The file input element\n * @param {HTMLButtonElement} uploadButton - The upload button\n * @param {HTMLButtonElement} continueButton - The continue button\n * @param {File | null} selectedFile - The selected file\n */\nfunction handleStandardFormSubmission(\n formElement,\n fileInput,\n uploadButton,\n continueButton,\n selectedFile\n) {\n renderSummary(selectedFile, 'Uploading…', formElement)\n\n fileInput.focus()\n\n setTimeout(() => {\n fileInput.disabled = true\n uploadButton.disabled = true\n continueButton.disabled = true\n }, 100)\n}\n\n/**\n * Handle AJAX form submission with upload ID\n * @param {Event} event - The click event\n * @param {HTMLFormElement} formElement - The form element\n * @param {HTMLInputElement} fileInput - The file input element\n * @param {HTMLButtonElement} uploadButton - The upload button\n * @param {HTMLElement | null} errorSummary - The error summary container\n * @param {string | undefined} uploadId - The upload ID\n * @returns {boolean} Whether the event was handled\n */\nfunction handleAjaxFormSubmission(\n event,\n formElement,\n fileInput,\n uploadButton,\n errorSummary,\n uploadId\n) {\n if (!formElement.action || !uploadId) {\n return false\n }\n\n event.preventDefault()\n\n const formData = new FormData(formElement)\n const isLocalDev = !!formElement.dataset.proxyUrl\n const uploadUrl = formElement.dataset.proxyUrl ?? formElement.action\n\n const fetchOptions = /** @type {RequestInit} */ ({\n method: 'POST',\n body: formData,\n redirect: isLocalDev ? 'follow' : 'manual' // follow mode if local development with the proxy\n })\n\n // no-cors mode if needed local development with the proxy\n if (isLocalDev) {\n fetchOptions.mode = 'no-cors'\n }\n\n fetch(uploadUrl, fetchOptions)\n .then(() => {\n pollUploadStatus(uploadId)\n })\n .catch(() => {\n fileInput.disabled = false\n uploadButton.disabled = false\n\n showError(\n 'There was a problem uploading the file',\n errorSummary,\n fileInput\n )\n\n return null\n })\n\n return true\n}\n\nfunction initUpload() {\n const form = document.querySelector('form:has(input[type=\"file\"])')\n /** @type {HTMLInputElement | null} */\n const fileInput = form ? form.querySelector('input[type=\"file\"]') : null\n /** @type {HTMLButtonElement | null} */\n const uploadButton = form ? form.querySelector('.upload-file-button') : null\n const continueButton =\n /** @type {HTMLButtonElement} */ (\n Array.from(document.querySelectorAll('button.govuk-button')).find(\n (button) => button.textContent?.trim() === 'Continue'\n )\n ) ?? null\n\n const errorSummary = document.querySelector('.govuk-error-summary-container')\n\n if (!form || !fileInput || !uploadButton) {\n return\n }\n\n const formElement = /** @type {HTMLFormElement} */ (form)\n /** @type {File | null} */\n let selectedFile = null\n let isSubmitting = false\n const uploadId = formElement.dataset.uploadId\n\n fileInput.addEventListener('change', () => {\n if (errorSummary) {\n errorSummary.innerHTML = ''\n }\n\n if (fileInput.files && fileInput.files.length > 0) {\n selectedFile = fileInput.files[0]\n }\n })\n\n uploadButton.addEventListener('click', (event) => {\n if (!selectedFile) {\n event.preventDefault()\n showError(\n 'Select a file',\n /** @type {HTMLElement | null} */ (errorSummary),\n fileInput\n )\n return\n }\n\n if (isSubmitting) {\n event.preventDefault()\n return\n }\n\n isSubmitting = true\n\n handleStandardFormSubmission(\n formElement,\n fileInput,\n uploadButton,\n continueButton,\n selectedFile\n )\n\n handleAjaxFormSubmission(\n event,\n formElement,\n fileInput,\n uploadButton,\n /** @type {HTMLElement | null} */ (errorSummary),\n uploadId\n )\n })\n}\n\nexport function initFileUpload() {\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', initUpload)\n } else {\n initUpload()\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SchemaVersion, type ComponentDef, type ConditionWrapper, type ConditionWrapperV2, type ConditionsModelData, type Engine, type FormDefinition, type List, type Page } from '@defra/forms-model';
|
|
2
2
|
import { Parser, type Value } from 'expr-eval';
|
|
3
3
|
import joi from 'joi';
|
|
4
4
|
import { type Component } from '~/src/server/plugins/engine/components/helpers/components.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ComponentType, ConditionsModel, ControllerPath, ControllerType,
|
|
1
|
+
import { ComponentType, ConditionsModel, ControllerPath, ControllerType, SchemaVersion, convertConditionWrapperFromV2, formDefinitionSchema, formDefinitionV2Schema, generateConditionAlias, hasComponents, hasRepeater, isConditionWrapperV2, yesNoListId, yesNoListName } from '@defra/forms-model';
|
|
2
2
|
import { add, format } from 'date-fns';
|
|
3
3
|
import { Parser } from 'expr-eval';
|
|
4
4
|
import joi from 'joi';
|
|
@@ -240,11 +240,7 @@ export class FormModel {
|
|
|
240
240
|
while (nextPage) {
|
|
241
241
|
// Add page to context
|
|
242
242
|
context.relevantPages.push(nextPage);
|
|
243
|
-
|
|
244
|
-
// Engine.V2 is excluded here as this will have already been done in initialiseContext()
|
|
245
|
-
if (this.engine !== Engine.V2) {
|
|
246
|
-
this.assignEvaluationState(context, nextPage);
|
|
247
|
-
}
|
|
243
|
+
this.assignEvaluationState(context, nextPage);
|
|
248
244
|
this.assignRelevantState(context, nextPage);
|
|
249
245
|
|
|
250
246
|
// Stop at current page
|
|
@@ -264,12 +260,17 @@ export class FormModel {
|
|
|
264
260
|
return context;
|
|
265
261
|
}
|
|
266
262
|
initialiseContext(context) {
|
|
267
|
-
//
|
|
263
|
+
// Initialise `evaluationState` for all keys using empty state.
|
|
268
264
|
// This is because the current condition evaluation library (eval-expr)
|
|
269
265
|
// will throw if an expression uses a key that is undefined.
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
266
|
+
const emptyState = Object.freeze({});
|
|
267
|
+
for (const page of this.pages) {
|
|
268
|
+
const {
|
|
269
|
+
collection,
|
|
270
|
+
pageDef
|
|
271
|
+
} = page;
|
|
272
|
+
if (!hasRepeater(pageDef)) {
|
|
273
|
+
Object.assign(context.evaluationState, collection.getContextValueFromState(emptyState));
|
|
273
274
|
}
|
|
274
275
|
}
|
|
275
276
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FormModel.js","names":["ComponentType","ConditionsModel","ControllerPath","ControllerType","Engine","SchemaVersion","convertConditionWrapperFromV2","formDefinitionSchema","formDefinitionV2Schema","generateConditionAlias","hasComponents","hasRepeater","isConditionWrapperV2","yesNoListId","yesNoListName","add","format","Parser","joi","createLogger","hasListFormField","todayAsDateOnly","findPage","getError","getPage","setPageTitles","createPage","validationOptions","opts","defaultServices","FormAction","merge","logger","FormModel","engine","schemaVersion","def","lists","sections","name","values","basePath","versionNumber","conditions","pages","services","controllers","pageDefMap","listDefMap","listDefIdMap","componentDefMap","componentDefIdMap","pageMap","componentMap","constructor","options","schema","V1","warn","result","validate","abortEarly","error","structuredClone","value","push","id","title","type","items","text","Map","map","page","path","list","filter","flatMap","components","component","forEach","conditionDef","condition","makeCondition","pageDef","some","controller","Status","collection","makeFilteredSchema","relevantPages","object","required","concat","stateSchema","parser","operators","logical","Object","assign","functions","dateForComparison","timePeriod","timeUnit","displayName","expr","toConditionExpression","fn","evaluationState","ctx","toConditionContext","evaluate","context","conditionId","propertyName","V2","defineProperty","get","from","parse","toExpression","getList","nameOrId","find","getFormContext","request","state","errors","query","currentPath","startPath","getStartPath","isForceAccess","relevantState","payload","getFormDataFromState","paths","data","referenceNumber","getReferenceNumber","submittedVersionNumber","validateFormPayload","nextPage","initialiseContext","assignEvaluationState","assignRelevantState","pageStateIsInvalid","getNextPath","validateFormState","assignPaths","getContextValueFromState","key","keys","listFields","fields","field","undefined","YesNoField","hasOptionalItems","item","length","fieldStateIsInvalid","validValues","fieldState","getFormValueFromState","isInvalid","isArray","Array","every","includes","href","getComponentById","componentId","getListById","listId","getConditionById","action","getFormParams","Validate","SaveAndExit","update","CheckboxesField","formState","getStateFromValidForm","previousPages","relevantPage","model","stripUnknown","errorsState","details","$$__referenceNumber","Error"],"sources":["../../../../../src/server/plugins/engine/models/FormModel.ts"],"sourcesContent":["import {\n ComponentType,\n ConditionsModel,\n ControllerPath,\n ControllerType,\n Engine,\n SchemaVersion,\n convertConditionWrapperFromV2,\n formDefinitionSchema,\n formDefinitionV2Schema,\n generateConditionAlias,\n hasComponents,\n hasRepeater,\n isConditionWrapperV2,\n yesNoListId,\n yesNoListName,\n type ComponentDef,\n type ConditionWrapper,\n type ConditionWrapperV2,\n type ConditionsModelData,\n type DateUnits,\n type FormDefinition,\n type List,\n type Page\n} from '@defra/forms-model'\nimport { add, format } from 'date-fns'\nimport { Parser, type Value } from 'expr-eval'\nimport joi from 'joi'\n\nimport { createLogger } from '~/src/server/common/helpers/logging/logger.js'\nimport { type ListFormComponent } from '~/src/server/plugins/engine/components/ListFormComponent.js'\nimport {} from '~/src/server/plugins/engine/components/YesNoField.js'\nimport {\n hasListFormField,\n type Component\n} from '~/src/server/plugins/engine/components/helpers/components.js'\nimport { todayAsDateOnly } from '~/src/server/plugins/engine/date-helper.js'\nimport {\n findPage,\n getError,\n getPage,\n setPageTitles\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type ExecutableCondition } from '~/src/server/plugins/engine/models/types.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport {\n createPage,\n type PageControllerClass\n} from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport { validationOptions as opts } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'\nimport * as defaultServices from '~/src/server/plugins/engine/services/index.js'\nimport {\n type FormContext,\n type FormContextRequest,\n type FormState,\n type FormSubmissionError,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\nimport { FormAction } from '~/src/server/routes/types.js'\nimport { merge } from '~/src/server/services/cacheService.js'\nimport { type Services } from '~/src/server/types.js'\n\nconst logger = createLogger()\n\nexport class FormModel {\n /** The runtime engine that should be used */\n engine?: Engine\n\n schemaVersion: SchemaVersion\n\n /** the entire form JSON as an object */\n def: FormDefinition\n\n lists: FormDefinition['lists']\n sections: FormDefinition['sections'] = []\n name: string\n values: FormDefinition\n basePath: string\n versionNumber?: number\n conditions: Partial<Record<string, ExecutableCondition>>\n pages: PageControllerClass[]\n services: Services\n\n controllers?: Record<string, typeof PageController>\n pageDefMap: Map<string, Page>\n\n listDefMap: Map<string, List>\n listDefIdMap: Map<string, List>\n\n componentDefMap: Map<string, ComponentDef>\n componentDefIdMap: Map<string, ComponentDef>\n\n pageMap: Map<string, PageControllerClass>\n componentMap: Map<string, Component>\n\n constructor(\n def: typeof this.def,\n options: { basePath: string; versionNumber?: number },\n services: Services = defaultServices,\n controllers?: Record<string, typeof PageController>\n ) {\n let schema = formDefinitionV2Schema\n\n if (!def.schema || def.schema === SchemaVersion.V1) {\n logger.warn(\n `[DEPRECATION NOTICE] Form \"${def.name}\" constructed with legacy V1 schema. See https://defra.github.io/forms-engine-plugin/schemas/form-definition-schema.html.`\n )\n schema = formDefinitionSchema\n }\n\n const result = schema.validate(def, { abortEarly: false })\n\n if (result.error) {\n throw result.error\n }\n\n // Make a clone of the shallow copy returned\n // by joi so as not to change the source data.\n def = structuredClone(result.value)\n\n // Add default lists\n def.lists.push({\n id: def.schema === SchemaVersion.V1 ? yesNoListName : yesNoListId,\n name: '__yesNo',\n title: 'Yes/No',\n type: 'boolean',\n items: [\n {\n id: '02900d42-83d1-4c72-a719-c4e8228952fa',\n text: 'Yes',\n value: true\n },\n {\n id: 'f39000eb-c51b-4019-8f82-bbda0423f04d',\n text: 'No',\n value: false\n }\n ]\n })\n\n // Fix up page titles\n setPageTitles(def)\n\n this.engine = def.engine\n this.schemaVersion = def.schema ?? SchemaVersion.V1\n this.def = def\n this.lists = def.lists\n this.sections = def.sections\n this.name = def.name ?? ''\n this.values = result.value\n this.basePath = options.basePath\n this.versionNumber = options.versionNumber\n this.conditions = {}\n this.services = services\n this.controllers = controllers\n\n this.pageDefMap = new Map(def.pages.map((page) => [page.path, page]))\n this.listDefMap = new Map(def.lists.map((list) => [list.name, list]))\n this.listDefIdMap = new Map(\n def.lists\n .filter((list) => list.id) // Skip lists without an ID\n // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style\n .map((list) => [list.id as string, list])\n )\n this.componentDefMap = new Map(\n def.pages\n .filter(hasComponents)\n .flatMap((page) =>\n page.components.map((component) => [component.name, component])\n )\n )\n this.componentDefIdMap = new Map(\n def.pages.filter(hasComponents).flatMap((page) =>\n page.components\n .filter((component) => component.id) // Skip components without an ID\n .map((component) => {\n // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style\n return [component.id as string, component]\n })\n )\n )\n\n def.conditions.forEach((conditionDef) => {\n const condition = this.makeCondition(\n isConditionWrapperV2(conditionDef)\n ? convertConditionWrapperFromV2(conditionDef, this)\n : conditionDef\n )\n this.conditions[condition.name] = condition\n })\n\n this.pages = def.pages.map((pageDef) => createPage(this, pageDef))\n\n if (\n !def.pages.some(\n ({ controller }) =>\n // Check for user-provided status page (optional)\n controller === ControllerType.Status\n )\n ) {\n this.pages.push(\n createPage(this, {\n title: 'Form submitted',\n path: ControllerPath.Status,\n controller: ControllerType.Status\n })\n )\n }\n\n this.pageMap = new Map(this.pages.map((page) => [page.path, page]))\n this.componentMap = new Map(\n this.pages.flatMap((page) =>\n page.collection.components.map((component) => [\n component.name,\n component\n ])\n )\n )\n }\n\n /**\n * build the entire model schema from individual pages/sections and filter out answers\n * for pages which are no longer accessible due to an answer that has been changed\n */\n makeFilteredSchema(relevantPages: PageControllerClass[]) {\n // Build the entire model schema\n // from the individual pages/sections\n let schema = joi.object<FormSubmissionState>().required()\n\n relevantPages.forEach((page) => {\n schema = schema.concat(page.collection.stateSchema)\n })\n\n return schema\n }\n\n /**\n * Instantiates a Condition based on {@link ConditionWrapper}\n * @param condition\n */\n makeCondition(condition: ConditionWrapper): ExecutableCondition {\n const parser = new Parser({\n operators: {\n logical: true\n }\n })\n\n Object.assign(parser.functions, {\n dateForComparison(timePeriod: number, timeUnit: DateUnits) {\n // The time element must be stripped (hence using startOfDay() which has no time element),\n // then formatted as YYYY-MM-DD otherwise we can hit time element and BST issues giving the\n // wrong date to compare against.\n // Do not use .toISOString() to format the date as that introduces BST errors.\n return format(\n add(todayAsDateOnly(), { [timeUnit]: timePeriod }),\n 'yyyy-MM-dd'\n )\n }\n })\n\n const { name, displayName, value } = condition\n const expr = this.toConditionExpression(value, parser)\n\n const fn = (evaluationState: FormState) => {\n const ctx = this.toConditionContext(evaluationState, this.conditions)\n try {\n return expr.evaluate(ctx) as boolean\n } catch {\n return false\n }\n }\n\n return {\n name,\n displayName,\n value,\n expr,\n fn\n }\n }\n\n toConditionContext(\n evaluationState: FormState,\n conditions: Partial<Record<string, ExecutableCondition>>\n ) {\n const context = { ...evaluationState }\n\n for (const conditionId in conditions) {\n const propertyName =\n this.schemaVersion === SchemaVersion.V2\n ? generateConditionAlias(conditionId)\n : conditionId\n\n Object.defineProperty(context, propertyName, {\n get() {\n return conditions[conditionId]?.fn(evaluationState)\n }\n })\n }\n\n return context as Extract<Value, Record<string, Value>>\n }\n\n toConditionExpression(value: ConditionsModelData, parser: Parser) {\n const conditions = ConditionsModel.from(value)\n return parser.parse(conditions.toExpression())\n }\n\n getList(nameOrId: string): List | undefined {\n return this.schemaVersion === SchemaVersion.V1\n ? this.lists.find((list) => list.name === nameOrId)\n : this.lists.find((list) => list.id === nameOrId)\n }\n\n /**\n * Form context for the current page\n */\n getFormContext(\n request: FormContextRequest,\n state: FormState,\n errors?: FormSubmissionError[]\n ): FormContext {\n const { query } = request\n\n const page = getPage(this, request)\n\n // Determine form paths\n const currentPath = page.path\n const startPath = page.getStartPath()\n\n // Preview URL direct access is allowed\n const isForceAccess = 'force' in query\n\n let context: FormContext = {\n evaluationState: {},\n relevantState: {},\n relevantPages: [],\n payload: page.getFormDataFromState(request, state),\n state,\n paths: [],\n errors,\n isForceAccess,\n data: {},\n pageDefMap: this.pageDefMap,\n listDefMap: this.listDefMap,\n componentDefMap: this.componentDefMap,\n pageMap: this.pageMap,\n componentMap: this.componentMap,\n referenceNumber: getReferenceNumber(state),\n submittedVersionNumber: this.versionNumber\n }\n\n // Validate current page\n context = validateFormPayload(request, page, context)\n\n // Find start page\n let nextPage = findPage(this, startPath)\n\n this.initialiseContext(context)\n\n // Walk form pages from start\n while (nextPage) {\n // Add page to context\n context.relevantPages.push(nextPage)\n\n // Engine.V2 is excluded here as this will have already been done in initialiseContext()\n if (this.engine !== Engine.V2) {\n this.assignEvaluationState(context, nextPage)\n }\n\n this.assignRelevantState(context, nextPage)\n\n // Stop at current page\n if (\n this.pageStateIsInvalid(context, nextPage) ||\n nextPage.path === currentPath\n ) {\n break\n }\n\n // Apply conditions to determine next page\n nextPage = findPage(this, nextPage.getNextPath(context))\n }\n\n // Validate form state\n context = validateFormState(request, page, context)\n\n // Add paths for navigation\n this.assignPaths(context)\n\n return context\n }\n\n private initialiseContext(context: FormContext) {\n // For the V2 engine, we initialise `evaluationState` for all keys.\n // This is because the current condition evaluation library (eval-expr)\n // will throw if an expression uses a key that is undefined.\n if (this.engine === Engine.V2) {\n for (const page of this.pages) {\n this.assignEvaluationState(context, page)\n }\n }\n }\n\n private assignEvaluationState(\n context: FormContext,\n page: PageControllerClass\n ) {\n const { collection, pageDef } = page\n // Skip evaluation state for repeater pages\n\n if (!hasRepeater(pageDef)) {\n Object.assign(\n context.evaluationState,\n collection.getContextValueFromState(context.state)\n )\n }\n }\n\n private assignRelevantState(context: FormContext, page: PageControllerClass) {\n // Copy relevant state by expected keys\n for (const key of page.keys) {\n if (typeof context.state[key] !== 'undefined') {\n context.relevantState[key] = context.state[key]\n }\n }\n }\n\n private pageStateIsInvalid(context: FormContext, page: PageControllerClass) {\n // Get any list-bound fields on the page\n const listFields = page.collection.fields.filter(hasListFormField)\n\n // For each list field that is bound to a list that contains any conditional items,\n // we need to check any answers are still valid. Do this by evaluating the conditions\n // and ensuring any current answers are all included in the set of valid answers\n for (const field of listFields) {\n const list = field.list\n\n // Filter out YesNo as they can't be conditional\n if (list !== undefined && field.type !== ComponentType.YesNoField) {\n const hasOptionalItems =\n list.items.filter((item) => item.condition).length > 0\n\n if (hasOptionalItems) {\n return this.fieldStateIsInvalid(context, field, list)\n }\n }\n }\n }\n\n private fieldStateIsInvalid(\n context: FormContext,\n field: ListFormComponent,\n list: List\n ) {\n const { evaluationState, state } = context\n\n const validValues = list.items\n .filter((item) =>\n item.condition\n ? this.conditions[item.condition]?.fn(evaluationState)\n : true\n )\n .map((item) => item.value)\n\n // Get the field state\n const fieldState = field.getFormValueFromState(state)\n\n if (fieldState !== undefined) {\n let isInvalid = false\n const isArray = Array.isArray(fieldState)\n\n // Check if any saved state value(s) are still valid\n // and return true if any are invalid\n if (isArray) {\n isInvalid = !fieldState.every((item) => validValues.includes(item))\n } else {\n isInvalid = !validValues.includes(fieldState)\n }\n\n if (isInvalid) {\n context.errors ??= []\n\n const text =\n 'Options are different because you changed a previous answer'\n\n context.errors.push({\n text,\n name: field.name,\n href: `#${field.name}`,\n path: [`#${field.name}`]\n })\n }\n\n return isInvalid\n }\n }\n\n private assignPaths(context: FormContext) {\n for (const { keys, path } of context.relevantPages) {\n context.paths.push(path)\n\n // Stop at page with errors\n if (\n context.errors?.some(({ name, path }) => {\n return keys.includes(name) || keys.some((key) => path.includes(key))\n })\n ) {\n break\n }\n }\n }\n\n getComponentById(componentId: string): ComponentDef | undefined {\n return this.componentDefIdMap.get(componentId)\n }\n\n getListById(listId: string): List | undefined {\n return this.listDefIdMap.get(listId)\n }\n\n /**\n * Returns a condition by its ID. O(n) lookup time.\n * @param conditionId\n * @returns\n */\n getConditionById(conditionId: string): ConditionWrapperV2 | undefined {\n return this.def.conditions\n .filter(isConditionWrapperV2)\n .find((condition) => condition.id === conditionId)\n }\n}\n\n/**\n * Validate current page only\n */\nfunction validateFormPayload(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n): FormContext {\n const { collection } = page\n const { payload, state } = context\n\n const { action } = page.getFormParams(request)\n\n // Skip validation GET requests or other actions\n if (\n !request.payload ||\n (action && ![FormAction.Validate, FormAction.SaveAndExit].includes(action))\n ) {\n return context\n }\n\n // For checkbox fields missing in the payload (i.e. unchecked),\n // explicitly set their value to undefined so that any previously\n // stored value is cleared and required field validation is enforced.\n const update = { ...request.payload }\n collection.fields.forEach((field) => {\n if (\n field.type === ComponentType.CheckboxesField &&\n !(field.name in update)\n ) {\n update[field.name] = undefined\n }\n })\n\n const { value, errors } = collection.validate({\n ...payload,\n ...update\n })\n\n // Add sanitised payload (ready to save)\n const formState = page.getStateFromValidForm(request, state, value)\n\n return {\n ...context,\n payload: merge(payload, value),\n state: merge(state, formState),\n errors\n }\n}\n\n/**\n * Validate entire form state\n */\nfunction validateFormState(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n): FormContext {\n const { errors = [], relevantPages, relevantState } = context\n\n // Exclude current page\n const previousPages = relevantPages.filter(\n (relevantPage) => relevantPage !== page\n )\n\n // Validate relevant state\n const { error } = page.model\n .makeFilteredSchema(previousPages)\n .validate(relevantState, { ...opts, stripUnknown: true })\n\n // Add relevant state errors\n if (error) {\n const errorsState = error.details.map(getError)\n return { ...context, errors: errors.concat(errorsState) }\n }\n\n return context\n}\n\nfunction getReferenceNumber(state: FormState): string {\n if (\n !state.$$__referenceNumber ||\n typeof state.$$__referenceNumber !== 'string'\n ) {\n throw Error('Reference number not found in form state')\n }\n\n return state.$$__referenceNumber\n}\n"],"mappings":"AAAA,SACEA,aAAa,EACbC,eAAe,EACfC,cAAc,EACdC,cAAc,EACdC,MAAM,EACNC,aAAa,EACbC,6BAA6B,EAC7BC,oBAAoB,EACpBC,sBAAsB,EACtBC,sBAAsB,EACtBC,aAAa,EACbC,WAAW,EACXC,oBAAoB,EACpBC,WAAW,EACXC,aAAa,QASR,oBAAoB;AAC3B,SAASC,GAAG,EAAEC,MAAM,QAAQ,UAAU;AACtC,SAASC,MAAM,QAAoB,WAAW;AAC9C,OAAOC,GAAG,MAAM,KAAK;AAErB,SAASC,YAAY;AAErB;AACA,SACEC,gBAAgB;AAGlB,SAASC,eAAe;AACxB,SACEC,QAAQ,EACRC,QAAQ,EACRC,OAAO,EACPC,aAAa;AAIf,SACEC,UAAU;AAGZ,SAASC,iBAAiB,IAAIC,IAAI;AAClC,OAAO,KAAKC,eAAe;AAQ3B,SAASC,UAAU;AACnB,SAASC,KAAK;AAGd,MAAMC,MAAM,GAAGb,YAAY,CAAC,CAAC;AAE7B,OAAO,MAAMc,SAAS,CAAC;EACrB;EACAC,MAAM;EAENC,aAAa;;EAEb;EACAC,GAAG;EAEHC,KAAK;EACLC,QAAQ,GAA+B,EAAE;EACzCC,IAAI;EACJC,MAAM;EACNC,QAAQ;EACRC,aAAa;EACbC,UAAU;EACVC,KAAK;EACLC,QAAQ;EAERC,WAAW;EACXC,UAAU;EAEVC,UAAU;EACVC,YAAY;EAEZC,eAAe;EACfC,iBAAiB;EAEjBC,OAAO;EACPC,YAAY;EAEZC,WAAWA,CACTlB,GAAoB,EACpBmB,OAAqD,EACrDV,QAAkB,GAAGhB,eAAe,EACpCiB,WAAmD,EACnD;IACA,IAAIU,MAAM,GAAGhD,sBAAsB;IAEnC,IAAI,CAAC4B,GAAG,CAACoB,MAAM,IAAIpB,GAAG,CAACoB,MAAM,KAAKnD,aAAa,CAACoD,EAAE,EAAE;MAClDzB,MAAM,CAAC0B,IAAI,CACT,8BAA8BtB,GAAG,CAACG,IAAI,2HACxC,CAAC;MACDiB,MAAM,GAAGjD,oBAAoB;IAC/B;IAEA,MAAMoD,MAAM,GAAGH,MAAM,CAACI,QAAQ,CAACxB,GAAG,EAAE;MAAEyB,UAAU,EAAE;IAAM,CAAC,CAAC;IAE1D,IAAIF,MAAM,CAACG,KAAK,EAAE;MAChB,MAAMH,MAAM,CAACG,KAAK;IACpB;;IAEA;IACA;IACA1B,GAAG,GAAG2B,eAAe,CAACJ,MAAM,CAACK,KAAK,CAAC;;IAEnC;IACA5B,GAAG,CAACC,KAAK,CAAC4B,IAAI,CAAC;MACbC,EAAE,EAAE9B,GAAG,CAACoB,MAAM,KAAKnD,aAAa,CAACoD,EAAE,GAAG3C,aAAa,GAAGD,WAAW;MACjE0B,IAAI,EAAE,SAAS;MACf4B,KAAK,EAAE,QAAQ;MACfC,IAAI,EAAE,SAAS;MACfC,KAAK,EAAE,CACL;QACEH,EAAE,EAAE,sCAAsC;QAC1CI,IAAI,EAAE,KAAK;QACXN,KAAK,EAAE;MACT,CAAC,EACD;QACEE,EAAE,EAAE,sCAAsC;QAC1CI,IAAI,EAAE,IAAI;QACVN,KAAK,EAAE;MACT,CAAC;IAEL,CAAC,CAAC;;IAEF;IACAvC,aAAa,CAACW,GAAG,CAAC;IAElB,IAAI,CAACF,MAAM,GAAGE,GAAG,CAACF,MAAM;IACxB,IAAI,CAACC,aAAa,GAAGC,GAAG,CAACoB,MAAM,IAAInD,aAAa,CAACoD,EAAE;IACnD,IAAI,CAACrB,GAAG,GAAGA,GAAG;IACd,IAAI,CAACC,KAAK,GAAGD,GAAG,CAACC,KAAK;IACtB,IAAI,CAACC,QAAQ,GAAGF,GAAG,CAACE,QAAQ;IAC5B,IAAI,CAACC,IAAI,GAAGH,GAAG,CAACG,IAAI,IAAI,EAAE;IAC1B,IAAI,CAACC,MAAM,GAAGmB,MAAM,CAACK,KAAK;IAC1B,IAAI,CAACvB,QAAQ,GAAGc,OAAO,CAACd,QAAQ;IAChC,IAAI,CAACC,aAAa,GAAGa,OAAO,CAACb,aAAa;IAC1C,IAAI,CAACC,UAAU,GAAG,CAAC,CAAC;IACpB,IAAI,CAACE,QAAQ,GAAGA,QAAQ;IACxB,IAAI,CAACC,WAAW,GAAGA,WAAW;IAE9B,IAAI,CAACC,UAAU,GAAG,IAAIwB,GAAG,CAACnC,GAAG,CAACQ,KAAK,CAAC4B,GAAG,CAAEC,IAAI,IAAK,CAACA,IAAI,CAACC,IAAI,EAAED,IAAI,CAAC,CAAC,CAAC;IACrE,IAAI,CAACzB,UAAU,GAAG,IAAIuB,GAAG,CAACnC,GAAG,CAACC,KAAK,CAACmC,GAAG,CAAEG,IAAI,IAAK,CAACA,IAAI,CAACpC,IAAI,EAAEoC,IAAI,CAAC,CAAC,CAAC;IACrE,IAAI,CAAC1B,YAAY,GAAG,IAAIsB,GAAG,CACzBnC,GAAG,CAACC,KAAK,CACNuC,MAAM,CAAED,IAAI,IAAKA,IAAI,CAACT,EAAE,CAAC,CAAC;IAC3B;IAAA,CACCM,GAAG,CAAEG,IAAI,IAAK,CAACA,IAAI,CAACT,EAAE,EAAYS,IAAI,CAAC,CAC5C,CAAC;IACD,IAAI,CAACzB,eAAe,GAAG,IAAIqB,GAAG,CAC5BnC,GAAG,CAACQ,KAAK,CACNgC,MAAM,CAAClE,aAAa,CAAC,CACrBmE,OAAO,CAAEJ,IAAI,IACZA,IAAI,CAACK,UAAU,CAACN,GAAG,CAAEO,SAAS,IAAK,CAACA,SAAS,CAACxC,IAAI,EAAEwC,SAAS,CAAC,CAChE,CACJ,CAAC;IACD,IAAI,CAAC5B,iBAAiB,GAAG,IAAIoB,GAAG,CAC9BnC,GAAG,CAACQ,KAAK,CAACgC,MAAM,CAAClE,aAAa,CAAC,CAACmE,OAAO,CAAEJ,IAAI,IAC3CA,IAAI,CAACK,UAAU,CACZF,MAAM,CAAEG,SAAS,IAAKA,SAAS,CAACb,EAAE,CAAC,CAAC;IAAA,CACpCM,GAAG,CAAEO,SAAS,IAAK;MAClB;MACA,OAAO,CAACA,SAAS,CAACb,EAAE,EAAYa,SAAS,CAAC;IAC5C,CAAC,CACL,CACF,CAAC;IAED3C,GAAG,CAACO,UAAU,CAACqC,OAAO,CAAEC,YAAY,IAAK;MACvC,MAAMC,SAAS,GAAG,IAAI,CAACC,aAAa,CAClCvE,oBAAoB,CAACqE,YAAY,CAAC,GAC9B3E,6BAA6B,CAAC2E,YAAY,EAAE,IAAI,CAAC,GACjDA,YACN,CAAC;MACD,IAAI,CAACtC,UAAU,CAACuC,SAAS,CAAC3C,IAAI,CAAC,GAAG2C,SAAS;IAC7C,CAAC,CAAC;IAEF,IAAI,CAACtC,KAAK,GAAGR,GAAG,CAACQ,KAAK,CAAC4B,GAAG,CAAEY,OAAO,IAAK1D,UAAU,CAAC,IAAI,EAAE0D,OAAO,CAAC,CAAC;IAElE,IACE,CAAChD,GAAG,CAACQ,KAAK,CAACyC,IAAI,CACb,CAAC;MAAEC;IAAW,CAAC;IACb;IACAA,UAAU,KAAKnF,cAAc,CAACoF,MAClC,CAAC,EACD;MACA,IAAI,CAAC3C,KAAK,CAACqB,IAAI,CACbvC,UAAU,CAAC,IAAI,EAAE;QACfyC,KAAK,EAAE,gBAAgB;QACvBO,IAAI,EAAExE,cAAc,CAACqF,MAAM;QAC3BD,UAAU,EAAEnF,cAAc,CAACoF;MAC7B,CAAC,CACH,CAAC;IACH;IAEA,IAAI,CAACnC,OAAO,GAAG,IAAImB,GAAG,CAAC,IAAI,CAAC3B,KAAK,CAAC4B,GAAG,CAAEC,IAAI,IAAK,CAACA,IAAI,CAACC,IAAI,EAAED,IAAI,CAAC,CAAC,CAAC;IACnE,IAAI,CAACpB,YAAY,GAAG,IAAIkB,GAAG,CACzB,IAAI,CAAC3B,KAAK,CAACiC,OAAO,CAAEJ,IAAI,IACtBA,IAAI,CAACe,UAAU,CAACV,UAAU,CAACN,GAAG,CAAEO,SAAS,IAAK,CAC5CA,SAAS,CAACxC,IAAI,EACdwC,SAAS,CACV,CACH,CACF,CAAC;EACH;;EAEA;AACF;AACA;AACA;EACEU,kBAAkBA,CAACC,aAAoC,EAAE;IACvD;IACA;IACA,IAAIlC,MAAM,GAAGtC,GAAG,CAACyE,MAAM,CAAsB,CAAC,CAACC,QAAQ,CAAC,CAAC;IAEzDF,aAAa,CAACV,OAAO,CAAEP,IAAI,IAAK;MAC9BjB,MAAM,GAAGA,MAAM,CAACqC,MAAM,CAACpB,IAAI,CAACe,UAAU,CAACM,WAAW,CAAC;IACrD,CAAC,CAAC;IAEF,OAAOtC,MAAM;EACf;;EAEA;AACF;AACA;AACA;EACE2B,aAAaA,CAACD,SAA2B,EAAuB;IAC9D,MAAMa,MAAM,GAAG,IAAI9E,MAAM,CAAC;MACxB+E,SAAS,EAAE;QACTC,OAAO,EAAE;MACX;IACF,CAAC,CAAC;IAEFC,MAAM,CAACC,MAAM,CAACJ,MAAM,CAACK,SAAS,EAAE;MAC9BC,iBAAiBA,CAACC,UAAkB,EAAEC,QAAmB,EAAE;QACzD;QACA;QACA;QACA;QACA,OAAOvF,MAAM,CACXD,GAAG,CAACM,eAAe,CAAC,CAAC,EAAE;UAAE,CAACkF,QAAQ,GAAGD;QAAW,CAAC,CAAC,EAClD,YACF,CAAC;MACH;IACF,CAAC,CAAC;IAEF,MAAM;MAAE/D,IAAI;MAAEiE,WAAW;MAAExC;IAAM,CAAC,GAAGkB,SAAS;IAC9C,MAAMuB,IAAI,GAAG,IAAI,CAACC,qBAAqB,CAAC1C,KAAK,EAAE+B,MAAM,CAAC;IAEtD,MAAMY,EAAE,GAAIC,eAA0B,IAAK;MACzC,MAAMC,GAAG,GAAG,IAAI,CAACC,kBAAkB,CAACF,eAAe,EAAE,IAAI,CAACjE,UAAU,CAAC;MACrE,IAAI;QACF,OAAO8D,IAAI,CAACM,QAAQ,CAACF,GAAG,CAAC;MAC3B,CAAC,CAAC,MAAM;QACN,OAAO,KAAK;MACd;IACF,CAAC;IAED,OAAO;MACLtE,IAAI;MACJiE,WAAW;MACXxC,KAAK;MACLyC,IAAI;MACJE;IACF,CAAC;EACH;EAEAG,kBAAkBA,CAChBF,eAA0B,EAC1BjE,UAAwD,EACxD;IACA,MAAMqE,OAAO,GAAG;MAAE,GAAGJ;IAAgB,CAAC;IAEtC,KAAK,MAAMK,WAAW,IAAItE,UAAU,EAAE;MACpC,MAAMuE,YAAY,GAChB,IAAI,CAAC/E,aAAa,KAAK9B,aAAa,CAAC8G,EAAE,GACnC1G,sBAAsB,CAACwG,WAAW,CAAC,GACnCA,WAAW;MAEjBf,MAAM,CAACkB,cAAc,CAACJ,OAAO,EAAEE,YAAY,EAAE;QAC3CG,GAAGA,CAAA,EAAG;UACJ,OAAO1E,UAAU,CAACsE,WAAW,CAAC,EAAEN,EAAE,CAACC,eAAe,CAAC;QACrD;MACF,CAAC,CAAC;IACJ;IAEA,OAAOI,OAAO;EAChB;EAEAN,qBAAqBA,CAAC1C,KAA0B,EAAE+B,MAAc,EAAE;IAChE,MAAMpD,UAAU,GAAG1C,eAAe,CAACqH,IAAI,CAACtD,KAAK,CAAC;IAC9C,OAAO+B,MAAM,CAACwB,KAAK,CAAC5E,UAAU,CAAC6E,YAAY,CAAC,CAAC,CAAC;EAChD;EAEAC,OAAOA,CAACC,QAAgB,EAAoB;IAC1C,OAAO,IAAI,CAACvF,aAAa,KAAK9B,aAAa,CAACoD,EAAE,GAC1C,IAAI,CAACpB,KAAK,CAACsF,IAAI,CAAEhD,IAAI,IAAKA,IAAI,CAACpC,IAAI,KAAKmF,QAAQ,CAAC,GACjD,IAAI,CAACrF,KAAK,CAACsF,IAAI,CAAEhD,IAAI,IAAKA,IAAI,CAACT,EAAE,KAAKwD,QAAQ,CAAC;EACrD;;EAEA;AACF;AACA;EACEE,cAAcA,CACZC,OAA2B,EAC3BC,KAAgB,EAChBC,MAA8B,EACjB;IACb,MAAM;MAAEC;IAAM,CAAC,GAAGH,OAAO;IAEzB,MAAMpD,IAAI,GAAGjD,OAAO,CAAC,IAAI,EAAEqG,OAAO,CAAC;;IAEnC;IACA,MAAMI,WAAW,GAAGxD,IAAI,CAACC,IAAI;IAC7B,MAAMwD,SAAS,GAAGzD,IAAI,CAAC0D,YAAY,CAAC,CAAC;;IAErC;IACA,MAAMC,aAAa,GAAG,OAAO,IAAIJ,KAAK;IAEtC,IAAIhB,OAAoB,GAAG;MACzBJ,eAAe,EAAE,CAAC,CAAC;MACnByB,aAAa,EAAE,CAAC,CAAC;MACjB3C,aAAa,EAAE,EAAE;MACjB4C,OAAO,EAAE7D,IAAI,CAAC8D,oBAAoB,CAACV,OAAO,EAAEC,KAAK,CAAC;MAClDA,KAAK;MACLU,KAAK,EAAE,EAAE;MACTT,MAAM;MACNK,aAAa;MACbK,IAAI,EAAE,CAAC,CAAC;MACR1F,UAAU,EAAE,IAAI,CAACA,UAAU;MAC3BC,UAAU,EAAE,IAAI,CAACA,UAAU;MAC3BE,eAAe,EAAE,IAAI,CAACA,eAAe;MACrCE,OAAO,EAAE,IAAI,CAACA,OAAO;MACrBC,YAAY,EAAE,IAAI,CAACA,YAAY;MAC/BqF,eAAe,EAAEC,kBAAkB,CAACb,KAAK,CAAC;MAC1Cc,sBAAsB,EAAE,IAAI,CAAClG;IAC/B,CAAC;;IAED;IACAsE,OAAO,GAAG6B,mBAAmB,CAAChB,OAAO,EAAEpD,IAAI,EAAEuC,OAAO,CAAC;;IAErD;IACA,IAAI8B,QAAQ,GAAGxH,QAAQ,CAAC,IAAI,EAAE4G,SAAS,CAAC;IAExC,IAAI,CAACa,iBAAiB,CAAC/B,OAAO,CAAC;;IAE/B;IACA,OAAO8B,QAAQ,EAAE;MACf;MACA9B,OAAO,CAACtB,aAAa,CAACzB,IAAI,CAAC6E,QAAQ,CAAC;;MAEpC;MACA,IAAI,IAAI,CAAC5G,MAAM,KAAK9B,MAAM,CAAC+G,EAAE,EAAE;QAC7B,IAAI,CAAC6B,qBAAqB,CAAChC,OAAO,EAAE8B,QAAQ,CAAC;MAC/C;MAEA,IAAI,CAACG,mBAAmB,CAACjC,OAAO,EAAE8B,QAAQ,CAAC;;MAE3C;MACA,IACE,IAAI,CAACI,kBAAkB,CAAClC,OAAO,EAAE8B,QAAQ,CAAC,IAC1CA,QAAQ,CAACpE,IAAI,KAAKuD,WAAW,EAC7B;QACA;MACF;;MAEA;MACAa,QAAQ,GAAGxH,QAAQ,CAAC,IAAI,EAAEwH,QAAQ,CAACK,WAAW,CAACnC,OAAO,CAAC,CAAC;IAC1D;;IAEA;IACAA,OAAO,GAAGoC,iBAAiB,CAACvB,OAAO,EAAEpD,IAAI,EAAEuC,OAAO,CAAC;;IAEnD;IACA,IAAI,CAACqC,WAAW,CAACrC,OAAO,CAAC;IAEzB,OAAOA,OAAO;EAChB;EAEQ+B,iBAAiBA,CAAC/B,OAAoB,EAAE;IAC9C;IACA;IACA;IACA,IAAI,IAAI,CAAC9E,MAAM,KAAK9B,MAAM,CAAC+G,EAAE,EAAE;MAC7B,KAAK,MAAM1C,IAAI,IAAI,IAAI,CAAC7B,KAAK,EAAE;QAC7B,IAAI,CAACoG,qBAAqB,CAAChC,OAAO,EAAEvC,IAAI,CAAC;MAC3C;IACF;EACF;EAEQuE,qBAAqBA,CAC3BhC,OAAoB,EACpBvC,IAAyB,EACzB;IACA,MAAM;MAAEe,UAAU;MAAEJ;IAAQ,CAAC,GAAGX,IAAI;IACpC;;IAEA,IAAI,CAAC9D,WAAW,CAACyE,OAAO,CAAC,EAAE;MACzBc,MAAM,CAACC,MAAM,CACXa,OAAO,CAACJ,eAAe,EACvBpB,UAAU,CAAC8D,wBAAwB,CAACtC,OAAO,CAACc,KAAK,CACnD,CAAC;IACH;EACF;EAEQmB,mBAAmBA,CAACjC,OAAoB,EAAEvC,IAAyB,EAAE;IAC3E;IACA,KAAK,MAAM8E,GAAG,IAAI9E,IAAI,CAAC+E,IAAI,EAAE;MAC3B,IAAI,OAAOxC,OAAO,CAACc,KAAK,CAACyB,GAAG,CAAC,KAAK,WAAW,EAAE;QAC7CvC,OAAO,CAACqB,aAAa,CAACkB,GAAG,CAAC,GAAGvC,OAAO,CAACc,KAAK,CAACyB,GAAG,CAAC;MACjD;IACF;EACF;EAEQL,kBAAkBA,CAAClC,OAAoB,EAAEvC,IAAyB,EAAE;IAC1E;IACA,MAAMgF,UAAU,GAAGhF,IAAI,CAACe,UAAU,CAACkE,MAAM,CAAC9E,MAAM,CAACxD,gBAAgB,CAAC;;IAElE;IACA;IACA;IACA,KAAK,MAAMuI,KAAK,IAAIF,UAAU,EAAE;MAC9B,MAAM9E,IAAI,GAAGgF,KAAK,CAAChF,IAAI;;MAEvB;MACA,IAAIA,IAAI,KAAKiF,SAAS,IAAID,KAAK,CAACvF,IAAI,KAAKpE,aAAa,CAAC6J,UAAU,EAAE;QACjE,MAAMC,gBAAgB,GACpBnF,IAAI,CAACN,KAAK,CAACO,MAAM,CAAEmF,IAAI,IAAKA,IAAI,CAAC7E,SAAS,CAAC,CAAC8E,MAAM,GAAG,CAAC;QAExD,IAAIF,gBAAgB,EAAE;UACpB,OAAO,IAAI,CAACG,mBAAmB,CAACjD,OAAO,EAAE2C,KAAK,EAAEhF,IAAI,CAAC;QACvD;MACF;IACF;EACF;EAEQsF,mBAAmBA,CACzBjD,OAAoB,EACpB2C,KAAwB,EACxBhF,IAAU,EACV;IACA,MAAM;MAAEiC,eAAe;MAAEkB;IAAM,CAAC,GAAGd,OAAO;IAE1C,MAAMkD,WAAW,GAAGvF,IAAI,CAACN,KAAK,CAC3BO,MAAM,CAAEmF,IAAI,IACXA,IAAI,CAAC7E,SAAS,GACV,IAAI,CAACvC,UAAU,CAACoH,IAAI,CAAC7E,SAAS,CAAC,EAAEyB,EAAE,CAACC,eAAe,CAAC,GACpD,IACN,CAAC,CACApC,GAAG,CAAEuF,IAAI,IAAKA,IAAI,CAAC/F,KAAK,CAAC;;IAE5B;IACA,MAAMmG,UAAU,GAAGR,KAAK,CAACS,qBAAqB,CAACtC,KAAK,CAAC;IAErD,IAAIqC,UAAU,KAAKP,SAAS,EAAE;MAC5B,IAAIS,SAAS,GAAG,KAAK;MACrB,MAAMC,OAAO,GAAGC,KAAK,CAACD,OAAO,CAACH,UAAU,CAAC;;MAEzC;MACA;MACA,IAAIG,OAAO,EAAE;QACXD,SAAS,GAAG,CAACF,UAAU,CAACK,KAAK,CAAET,IAAI,IAAKG,WAAW,CAACO,QAAQ,CAACV,IAAI,CAAC,CAAC;MACrE,CAAC,MAAM;QACLM,SAAS,GAAG,CAACH,WAAW,CAACO,QAAQ,CAACN,UAAU,CAAC;MAC/C;MAEA,IAAIE,SAAS,EAAE;QACbrD,OAAO,CAACe,MAAM,KAAK,EAAE;QAErB,MAAMzD,IAAI,GACR,6DAA6D;QAE/D0C,OAAO,CAACe,MAAM,CAAC9D,IAAI,CAAC;UAClBK,IAAI;UACJ/B,IAAI,EAAEoH,KAAK,CAACpH,IAAI;UAChBmI,IAAI,EAAE,IAAIf,KAAK,CAACpH,IAAI,EAAE;UACtBmC,IAAI,EAAE,CAAC,IAAIiF,KAAK,CAACpH,IAAI,EAAE;QACzB,CAAC,CAAC;MACJ;MAEA,OAAO8H,SAAS;IAClB;EACF;EAEQhB,WAAWA,CAACrC,OAAoB,EAAE;IACxC,KAAK,MAAM;MAAEwC,IAAI;MAAE9E;IAAK,CAAC,IAAIsC,OAAO,CAACtB,aAAa,EAAE;MAClDsB,OAAO,CAACwB,KAAK,CAACvE,IAAI,CAACS,IAAI,CAAC;;MAExB;MACA,IACEsC,OAAO,CAACe,MAAM,EAAE1C,IAAI,CAAC,CAAC;QAAE9C,IAAI;QAAEmC;MAAK,CAAC,KAAK;QACvC,OAAO8E,IAAI,CAACiB,QAAQ,CAAClI,IAAI,CAAC,IAAIiH,IAAI,CAACnE,IAAI,CAAEkE,GAAG,IAAK7E,IAAI,CAAC+F,QAAQ,CAAClB,GAAG,CAAC,CAAC;MACtE,CAAC,CAAC,EACF;QACA;MACF;IACF;EACF;EAEAoB,gBAAgBA,CAACC,WAAmB,EAA4B;IAC9D,OAAO,IAAI,CAACzH,iBAAiB,CAACkE,GAAG,CAACuD,WAAW,CAAC;EAChD;EAEAC,WAAWA,CAACC,MAAc,EAAoB;IAC5C,OAAO,IAAI,CAAC7H,YAAY,CAACoE,GAAG,CAACyD,MAAM,CAAC;EACtC;;EAEA;AACF;AACA;AACA;AACA;EACEC,gBAAgBA,CAAC9D,WAAmB,EAAkC;IACpE,OAAO,IAAI,CAAC7E,GAAG,CAACO,UAAU,CACvBiC,MAAM,CAAChE,oBAAoB,CAAC,CAC5B+G,IAAI,CAAEzC,SAAS,IAAKA,SAAS,CAAChB,EAAE,KAAK+C,WAAW,CAAC;EACtD;AACF;;AAEA;AACA;AACA;AACA,SAAS4B,mBAAmBA,CAC1BhB,OAA2B,EAC3BpD,IAAyB,EACzBuC,OAAoB,EACP;EACb,MAAM;IAAExB;EAAW,CAAC,GAAGf,IAAI;EAC3B,MAAM;IAAE6D,OAAO;IAAER;EAAM,CAAC,GAAGd,OAAO;EAElC,MAAM;IAAEgE;EAAO,CAAC,GAAGvG,IAAI,CAACwG,aAAa,CAACpD,OAAO,CAAC;;EAE9C;EACA,IACE,CAACA,OAAO,CAACS,OAAO,IACf0C,MAAM,IAAI,CAAC,CAAClJ,UAAU,CAACoJ,QAAQ,EAAEpJ,UAAU,CAACqJ,WAAW,CAAC,CAACV,QAAQ,CAACO,MAAM,CAAE,EAC3E;IACA,OAAOhE,OAAO;EAChB;;EAEA;EACA;EACA;EACA,MAAMoE,MAAM,GAAG;IAAE,GAAGvD,OAAO,CAACS;EAAQ,CAAC;EACrC9C,UAAU,CAACkE,MAAM,CAAC1E,OAAO,CAAE2E,KAAK,IAAK;IACnC,IACEA,KAAK,CAACvF,IAAI,KAAKpE,aAAa,CAACqL,eAAe,IAC5C,EAAE1B,KAAK,CAACpH,IAAI,IAAI6I,MAAM,CAAC,EACvB;MACAA,MAAM,CAACzB,KAAK,CAACpH,IAAI,CAAC,GAAGqH,SAAS;IAChC;EACF,CAAC,CAAC;EAEF,MAAM;IAAE5F,KAAK;IAAE+D;EAAO,CAAC,GAAGvC,UAAU,CAAC5B,QAAQ,CAAC;IAC5C,GAAG0E,OAAO;IACV,GAAG8C;EACL,CAAC,CAAC;;EAEF;EACA,MAAME,SAAS,GAAG7G,IAAI,CAAC8G,qBAAqB,CAAC1D,OAAO,EAAEC,KAAK,EAAE9D,KAAK,CAAC;EAEnE,OAAO;IACL,GAAGgD,OAAO;IACVsB,OAAO,EAAEvG,KAAK,CAACuG,OAAO,EAAEtE,KAAK,CAAC;IAC9B8D,KAAK,EAAE/F,KAAK,CAAC+F,KAAK,EAAEwD,SAAS,CAAC;IAC9BvD;EACF,CAAC;AACH;;AAEA;AACA;AACA;AACA,SAASqB,iBAAiBA,CACxBvB,OAA2B,EAC3BpD,IAAyB,EACzBuC,OAAoB,EACP;EACb,MAAM;IAAEe,MAAM,GAAG,EAAE;IAAErC,aAAa;IAAE2C;EAAc,CAAC,GAAGrB,OAAO;;EAE7D;EACA,MAAMwE,aAAa,GAAG9F,aAAa,CAACd,MAAM,CACvC6G,YAAY,IAAKA,YAAY,KAAKhH,IACrC,CAAC;;EAED;EACA,MAAM;IAAEX;EAAM,CAAC,GAAGW,IAAI,CAACiH,KAAK,CACzBjG,kBAAkB,CAAC+F,aAAa,CAAC,CACjC5H,QAAQ,CAACyE,aAAa,EAAE;IAAE,GAAGzG,IAAI;IAAE+J,YAAY,EAAE;EAAK,CAAC,CAAC;;EAE3D;EACA,IAAI7H,KAAK,EAAE;IACT,MAAM8H,WAAW,GAAG9H,KAAK,CAAC+H,OAAO,CAACrH,GAAG,CAACjD,QAAQ,CAAC;IAC/C,OAAO;MAAE,GAAGyF,OAAO;MAAEe,MAAM,EAAEA,MAAM,CAAClC,MAAM,CAAC+F,WAAW;IAAE,CAAC;EAC3D;EAEA,OAAO5E,OAAO;AAChB;AAEA,SAAS2B,kBAAkBA,CAACb,KAAgB,EAAU;EACpD,IACE,CAACA,KAAK,CAACgE,mBAAmB,IAC1B,OAAOhE,KAAK,CAACgE,mBAAmB,KAAK,QAAQ,EAC7C;IACA,MAAMC,KAAK,CAAC,0CAA0C,CAAC;EACzD;EAEA,OAAOjE,KAAK,CAACgE,mBAAmB;AAClC","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"FormModel.js","names":["ComponentType","ConditionsModel","ControllerPath","ControllerType","SchemaVersion","convertConditionWrapperFromV2","formDefinitionSchema","formDefinitionV2Schema","generateConditionAlias","hasComponents","hasRepeater","isConditionWrapperV2","yesNoListId","yesNoListName","add","format","Parser","joi","createLogger","hasListFormField","todayAsDateOnly","findPage","getError","getPage","setPageTitles","createPage","validationOptions","opts","defaultServices","FormAction","merge","logger","FormModel","engine","schemaVersion","def","lists","sections","name","values","basePath","versionNumber","conditions","pages","services","controllers","pageDefMap","listDefMap","listDefIdMap","componentDefMap","componentDefIdMap","pageMap","componentMap","constructor","options","schema","V1","warn","result","validate","abortEarly","error","structuredClone","value","push","id","title","type","items","text","Map","map","page","path","list","filter","flatMap","components","component","forEach","conditionDef","condition","makeCondition","pageDef","some","controller","Status","collection","makeFilteredSchema","relevantPages","object","required","concat","stateSchema","parser","operators","logical","Object","assign","functions","dateForComparison","timePeriod","timeUnit","displayName","expr","toConditionExpression","fn","evaluationState","ctx","toConditionContext","evaluate","context","conditionId","propertyName","V2","defineProperty","get","from","parse","toExpression","getList","nameOrId","find","getFormContext","request","state","errors","query","currentPath","startPath","getStartPath","isForceAccess","relevantState","payload","getFormDataFromState","paths","data","referenceNumber","getReferenceNumber","submittedVersionNumber","validateFormPayload","nextPage","initialiseContext","assignEvaluationState","assignRelevantState","pageStateIsInvalid","getNextPath","validateFormState","assignPaths","emptyState","freeze","getContextValueFromState","key","keys","listFields","fields","field","undefined","YesNoField","hasOptionalItems","item","length","fieldStateIsInvalid","validValues","fieldState","getFormValueFromState","isInvalid","isArray","Array","every","includes","href","getComponentById","componentId","getListById","listId","getConditionById","action","getFormParams","Validate","SaveAndExit","update","CheckboxesField","formState","getStateFromValidForm","previousPages","relevantPage","model","stripUnknown","errorsState","details","$$__referenceNumber","Error"],"sources":["../../../../../src/server/plugins/engine/models/FormModel.ts"],"sourcesContent":["import {\n ComponentType,\n ConditionsModel,\n ControllerPath,\n ControllerType,\n SchemaVersion,\n convertConditionWrapperFromV2,\n formDefinitionSchema,\n formDefinitionV2Schema,\n generateConditionAlias,\n hasComponents,\n hasRepeater,\n isConditionWrapperV2,\n yesNoListId,\n yesNoListName,\n type ComponentDef,\n type ConditionWrapper,\n type ConditionWrapperV2,\n type ConditionsModelData,\n type DateUnits,\n type Engine,\n type FormDefinition,\n type List,\n type Page\n} from '@defra/forms-model'\nimport { add, format } from 'date-fns'\nimport { Parser, type Value } from 'expr-eval'\nimport joi from 'joi'\n\nimport { createLogger } from '~/src/server/common/helpers/logging/logger.js'\nimport { type ListFormComponent } from '~/src/server/plugins/engine/components/ListFormComponent.js'\nimport {} from '~/src/server/plugins/engine/components/YesNoField.js'\nimport {\n hasListFormField,\n type Component\n} from '~/src/server/plugins/engine/components/helpers/components.js'\nimport { todayAsDateOnly } from '~/src/server/plugins/engine/date-helper.js'\nimport {\n findPage,\n getError,\n getPage,\n setPageTitles\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type ExecutableCondition } from '~/src/server/plugins/engine/models/types.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport {\n createPage,\n type PageControllerClass\n} from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport { validationOptions as opts } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'\nimport * as defaultServices from '~/src/server/plugins/engine/services/index.js'\nimport {\n type FormContext,\n type FormContextRequest,\n type FormState,\n type FormSubmissionError,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\nimport { FormAction } from '~/src/server/routes/types.js'\nimport { merge } from '~/src/server/services/cacheService.js'\nimport { type Services } from '~/src/server/types.js'\n\nconst logger = createLogger()\n\nexport class FormModel {\n /** The runtime engine that should be used */\n engine?: Engine\n\n schemaVersion: SchemaVersion\n\n /** the entire form JSON as an object */\n def: FormDefinition\n\n lists: FormDefinition['lists']\n sections: FormDefinition['sections'] = []\n name: string\n values: FormDefinition\n basePath: string\n versionNumber?: number\n conditions: Partial<Record<string, ExecutableCondition>>\n pages: PageControllerClass[]\n services: Services\n\n controllers?: Record<string, typeof PageController>\n pageDefMap: Map<string, Page>\n\n listDefMap: Map<string, List>\n listDefIdMap: Map<string, List>\n\n componentDefMap: Map<string, ComponentDef>\n componentDefIdMap: Map<string, ComponentDef>\n\n pageMap: Map<string, PageControllerClass>\n componentMap: Map<string, Component>\n\n constructor(\n def: typeof this.def,\n options: { basePath: string; versionNumber?: number },\n services: Services = defaultServices,\n controllers?: Record<string, typeof PageController>\n ) {\n let schema = formDefinitionV2Schema\n\n if (!def.schema || def.schema === SchemaVersion.V1) {\n logger.warn(\n `[DEPRECATION NOTICE] Form \"${def.name}\" constructed with legacy V1 schema. See https://defra.github.io/forms-engine-plugin/schemas/form-definition-schema.html.`\n )\n schema = formDefinitionSchema\n }\n\n const result = schema.validate(def, { abortEarly: false })\n\n if (result.error) {\n throw result.error\n }\n\n // Make a clone of the shallow copy returned\n // by joi so as not to change the source data.\n def = structuredClone(result.value)\n\n // Add default lists\n def.lists.push({\n id: def.schema === SchemaVersion.V1 ? yesNoListName : yesNoListId,\n name: '__yesNo',\n title: 'Yes/No',\n type: 'boolean',\n items: [\n {\n id: '02900d42-83d1-4c72-a719-c4e8228952fa',\n text: 'Yes',\n value: true\n },\n {\n id: 'f39000eb-c51b-4019-8f82-bbda0423f04d',\n text: 'No',\n value: false\n }\n ]\n })\n\n // Fix up page titles\n setPageTitles(def)\n\n this.engine = def.engine\n this.schemaVersion = def.schema ?? SchemaVersion.V1\n this.def = def\n this.lists = def.lists\n this.sections = def.sections\n this.name = def.name ?? ''\n this.values = result.value\n this.basePath = options.basePath\n this.versionNumber = options.versionNumber\n this.conditions = {}\n this.services = services\n this.controllers = controllers\n\n this.pageDefMap = new Map(def.pages.map((page) => [page.path, page]))\n this.listDefMap = new Map(def.lists.map((list) => [list.name, list]))\n this.listDefIdMap = new Map(\n def.lists\n .filter((list) => list.id) // Skip lists without an ID\n // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style\n .map((list) => [list.id as string, list])\n )\n this.componentDefMap = new Map(\n def.pages\n .filter(hasComponents)\n .flatMap((page) =>\n page.components.map((component) => [component.name, component])\n )\n )\n this.componentDefIdMap = new Map(\n def.pages.filter(hasComponents).flatMap((page) =>\n page.components\n .filter((component) => component.id) // Skip components without an ID\n .map((component) => {\n // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style\n return [component.id as string, component]\n })\n )\n )\n\n def.conditions.forEach((conditionDef) => {\n const condition = this.makeCondition(\n isConditionWrapperV2(conditionDef)\n ? convertConditionWrapperFromV2(conditionDef, this)\n : conditionDef\n )\n this.conditions[condition.name] = condition\n })\n\n this.pages = def.pages.map((pageDef) => createPage(this, pageDef))\n\n if (\n !def.pages.some(\n ({ controller }) =>\n // Check for user-provided status page (optional)\n controller === ControllerType.Status\n )\n ) {\n this.pages.push(\n createPage(this, {\n title: 'Form submitted',\n path: ControllerPath.Status,\n controller: ControllerType.Status\n })\n )\n }\n\n this.pageMap = new Map(this.pages.map((page) => [page.path, page]))\n this.componentMap = new Map(\n this.pages.flatMap((page) =>\n page.collection.components.map((component) => [\n component.name,\n component\n ])\n )\n )\n }\n\n /**\n * build the entire model schema from individual pages/sections and filter out answers\n * for pages which are no longer accessible due to an answer that has been changed\n */\n makeFilteredSchema(relevantPages: PageControllerClass[]) {\n // Build the entire model schema\n // from the individual pages/sections\n let schema = joi.object<FormSubmissionState>().required()\n\n relevantPages.forEach((page) => {\n schema = schema.concat(page.collection.stateSchema)\n })\n\n return schema\n }\n\n /**\n * Instantiates a Condition based on {@link ConditionWrapper}\n * @param condition\n */\n makeCondition(condition: ConditionWrapper): ExecutableCondition {\n const parser = new Parser({\n operators: {\n logical: true\n }\n })\n\n Object.assign(parser.functions, {\n dateForComparison(timePeriod: number, timeUnit: DateUnits) {\n // The time element must be stripped (hence using startOfDay() which has no time element),\n // then formatted as YYYY-MM-DD otherwise we can hit time element and BST issues giving the\n // wrong date to compare against.\n // Do not use .toISOString() to format the date as that introduces BST errors.\n return format(\n add(todayAsDateOnly(), { [timeUnit]: timePeriod }),\n 'yyyy-MM-dd'\n )\n }\n })\n\n const { name, displayName, value } = condition\n const expr = this.toConditionExpression(value, parser)\n\n const fn = (evaluationState: FormState) => {\n const ctx = this.toConditionContext(evaluationState, this.conditions)\n try {\n return expr.evaluate(ctx) as boolean\n } catch {\n return false\n }\n }\n\n return {\n name,\n displayName,\n value,\n expr,\n fn\n }\n }\n\n toConditionContext(\n evaluationState: FormState,\n conditions: Partial<Record<string, ExecutableCondition>>\n ) {\n const context = { ...evaluationState }\n\n for (const conditionId in conditions) {\n const propertyName =\n this.schemaVersion === SchemaVersion.V2\n ? generateConditionAlias(conditionId)\n : conditionId\n\n Object.defineProperty(context, propertyName, {\n get() {\n return conditions[conditionId]?.fn(evaluationState)\n }\n })\n }\n\n return context as Extract<Value, Record<string, Value>>\n }\n\n toConditionExpression(value: ConditionsModelData, parser: Parser) {\n const conditions = ConditionsModel.from(value)\n return parser.parse(conditions.toExpression())\n }\n\n getList(nameOrId: string): List | undefined {\n return this.schemaVersion === SchemaVersion.V1\n ? this.lists.find((list) => list.name === nameOrId)\n : this.lists.find((list) => list.id === nameOrId)\n }\n\n /**\n * Form context for the current page\n */\n getFormContext(\n request: FormContextRequest,\n state: FormState,\n errors?: FormSubmissionError[]\n ): FormContext {\n const { query } = request\n\n const page = getPage(this, request)\n\n // Determine form paths\n const currentPath = page.path\n const startPath = page.getStartPath()\n\n // Preview URL direct access is allowed\n const isForceAccess = 'force' in query\n\n let context: FormContext = {\n evaluationState: {},\n relevantState: {},\n relevantPages: [],\n payload: page.getFormDataFromState(request, state),\n state,\n paths: [],\n errors,\n isForceAccess,\n data: {},\n pageDefMap: this.pageDefMap,\n listDefMap: this.listDefMap,\n componentDefMap: this.componentDefMap,\n pageMap: this.pageMap,\n componentMap: this.componentMap,\n referenceNumber: getReferenceNumber(state),\n submittedVersionNumber: this.versionNumber\n }\n\n // Validate current page\n context = validateFormPayload(request, page, context)\n\n // Find start page\n let nextPage = findPage(this, startPath)\n\n this.initialiseContext(context)\n\n // Walk form pages from start\n while (nextPage) {\n // Add page to context\n context.relevantPages.push(nextPage)\n\n this.assignEvaluationState(context, nextPage)\n\n this.assignRelevantState(context, nextPage)\n\n // Stop at current page\n if (\n this.pageStateIsInvalid(context, nextPage) ||\n nextPage.path === currentPath\n ) {\n break\n }\n\n // Apply conditions to determine next page\n nextPage = findPage(this, nextPage.getNextPath(context))\n }\n\n // Validate form state\n context = validateFormState(request, page, context)\n\n // Add paths for navigation\n this.assignPaths(context)\n\n return context\n }\n\n private initialiseContext(context: FormContext) {\n // Initialise `evaluationState` for all keys using empty state.\n // This is because the current condition evaluation library (eval-expr)\n // will throw if an expression uses a key that is undefined.\n const emptyState = Object.freeze({})\n\n for (const page of this.pages) {\n const { collection, pageDef } = page\n\n if (!hasRepeater(pageDef)) {\n Object.assign(\n context.evaluationState,\n collection.getContextValueFromState(emptyState)\n )\n }\n }\n }\n\n private assignEvaluationState(\n context: FormContext,\n page: PageControllerClass\n ) {\n const { collection, pageDef } = page\n // Skip evaluation state for repeater pages\n\n if (!hasRepeater(pageDef)) {\n Object.assign(\n context.evaluationState,\n collection.getContextValueFromState(context.state)\n )\n }\n }\n\n private assignRelevantState(context: FormContext, page: PageControllerClass) {\n // Copy relevant state by expected keys\n for (const key of page.keys) {\n if (typeof context.state[key] !== 'undefined') {\n context.relevantState[key] = context.state[key]\n }\n }\n }\n\n private pageStateIsInvalid(context: FormContext, page: PageControllerClass) {\n // Get any list-bound fields on the page\n const listFields = page.collection.fields.filter(hasListFormField)\n\n // For each list field that is bound to a list that contains any conditional items,\n // we need to check any answers are still valid. Do this by evaluating the conditions\n // and ensuring any current answers are all included in the set of valid answers\n for (const field of listFields) {\n const list = field.list\n\n // Filter out YesNo as they can't be conditional\n if (list !== undefined && field.type !== ComponentType.YesNoField) {\n const hasOptionalItems =\n list.items.filter((item) => item.condition).length > 0\n\n if (hasOptionalItems) {\n return this.fieldStateIsInvalid(context, field, list)\n }\n }\n }\n }\n\n private fieldStateIsInvalid(\n context: FormContext,\n field: ListFormComponent,\n list: List\n ) {\n const { evaluationState, state } = context\n\n const validValues = list.items\n .filter((item) =>\n item.condition\n ? this.conditions[item.condition]?.fn(evaluationState)\n : true\n )\n .map((item) => item.value)\n\n // Get the field state\n const fieldState = field.getFormValueFromState(state)\n\n if (fieldState !== undefined) {\n let isInvalid = false\n const isArray = Array.isArray(fieldState)\n\n // Check if any saved state value(s) are still valid\n // and return true if any are invalid\n if (isArray) {\n isInvalid = !fieldState.every((item) => validValues.includes(item))\n } else {\n isInvalid = !validValues.includes(fieldState)\n }\n\n if (isInvalid) {\n context.errors ??= []\n\n const text =\n 'Options are different because you changed a previous answer'\n\n context.errors.push({\n text,\n name: field.name,\n href: `#${field.name}`,\n path: [`#${field.name}`]\n })\n }\n\n return isInvalid\n }\n }\n\n private assignPaths(context: FormContext) {\n for (const { keys, path } of context.relevantPages) {\n context.paths.push(path)\n\n // Stop at page with errors\n if (\n context.errors?.some(({ name, path }) => {\n return keys.includes(name) || keys.some((key) => path.includes(key))\n })\n ) {\n break\n }\n }\n }\n\n getComponentById(componentId: string): ComponentDef | undefined {\n return this.componentDefIdMap.get(componentId)\n }\n\n getListById(listId: string): List | undefined {\n return this.listDefIdMap.get(listId)\n }\n\n /**\n * Returns a condition by its ID. O(n) lookup time.\n * @param conditionId\n * @returns\n */\n getConditionById(conditionId: string): ConditionWrapperV2 | undefined {\n return this.def.conditions\n .filter(isConditionWrapperV2)\n .find((condition) => condition.id === conditionId)\n }\n}\n\n/**\n * Validate current page only\n */\nfunction validateFormPayload(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n): FormContext {\n const { collection } = page\n const { payload, state } = context\n\n const { action } = page.getFormParams(request)\n\n // Skip validation GET requests or other actions\n if (\n !request.payload ||\n (action && ![FormAction.Validate, FormAction.SaveAndExit].includes(action))\n ) {\n return context\n }\n\n // For checkbox fields missing in the payload (i.e. unchecked),\n // explicitly set their value to undefined so that any previously\n // stored value is cleared and required field validation is enforced.\n const update = { ...request.payload }\n collection.fields.forEach((field) => {\n if (\n field.type === ComponentType.CheckboxesField &&\n !(field.name in update)\n ) {\n update[field.name] = undefined\n }\n })\n\n const { value, errors } = collection.validate({\n ...payload,\n ...update\n })\n\n // Add sanitised payload (ready to save)\n const formState = page.getStateFromValidForm(request, state, value)\n\n return {\n ...context,\n payload: merge(payload, value),\n state: merge(state, formState),\n errors\n }\n}\n\n/**\n * Validate entire form state\n */\nfunction validateFormState(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n): FormContext {\n const { errors = [], relevantPages, relevantState } = context\n\n // Exclude current page\n const previousPages = relevantPages.filter(\n (relevantPage) => relevantPage !== page\n )\n\n // Validate relevant state\n const { error } = page.model\n .makeFilteredSchema(previousPages)\n .validate(relevantState, { ...opts, stripUnknown: true })\n\n // Add relevant state errors\n if (error) {\n const errorsState = error.details.map(getError)\n return { ...context, errors: errors.concat(errorsState) }\n }\n\n return context\n}\n\nfunction getReferenceNumber(state: FormState): string {\n if (\n !state.$$__referenceNumber ||\n typeof state.$$__referenceNumber !== 'string'\n ) {\n throw Error('Reference number not found in form state')\n }\n\n return state.$$__referenceNumber\n}\n"],"mappings":"AAAA,SACEA,aAAa,EACbC,eAAe,EACfC,cAAc,EACdC,cAAc,EACdC,aAAa,EACbC,6BAA6B,EAC7BC,oBAAoB,EACpBC,sBAAsB,EACtBC,sBAAsB,EACtBC,aAAa,EACbC,WAAW,EACXC,oBAAoB,EACpBC,WAAW,EACXC,aAAa,QAUR,oBAAoB;AAC3B,SAASC,GAAG,EAAEC,MAAM,QAAQ,UAAU;AACtC,SAASC,MAAM,QAAoB,WAAW;AAC9C,OAAOC,GAAG,MAAM,KAAK;AAErB,SAASC,YAAY;AAErB;AACA,SACEC,gBAAgB;AAGlB,SAASC,eAAe;AACxB,SACEC,QAAQ,EACRC,QAAQ,EACRC,OAAO,EACPC,aAAa;AAIf,SACEC,UAAU;AAGZ,SAASC,iBAAiB,IAAIC,IAAI;AAClC,OAAO,KAAKC,eAAe;AAQ3B,SAASC,UAAU;AACnB,SAASC,KAAK;AAGd,MAAMC,MAAM,GAAGb,YAAY,CAAC,CAAC;AAE7B,OAAO,MAAMc,SAAS,CAAC;EACrB;EACAC,MAAM;EAENC,aAAa;;EAEb;EACAC,GAAG;EAEHC,KAAK;EACLC,QAAQ,GAA+B,EAAE;EACzCC,IAAI;EACJC,MAAM;EACNC,QAAQ;EACRC,aAAa;EACbC,UAAU;EACVC,KAAK;EACLC,QAAQ;EAERC,WAAW;EACXC,UAAU;EAEVC,UAAU;EACVC,YAAY;EAEZC,eAAe;EACfC,iBAAiB;EAEjBC,OAAO;EACPC,YAAY;EAEZC,WAAWA,CACTlB,GAAoB,EACpBmB,OAAqD,EACrDV,QAAkB,GAAGhB,eAAe,EACpCiB,WAAmD,EACnD;IACA,IAAIU,MAAM,GAAGhD,sBAAsB;IAEnC,IAAI,CAAC4B,GAAG,CAACoB,MAAM,IAAIpB,GAAG,CAACoB,MAAM,KAAKnD,aAAa,CAACoD,EAAE,EAAE;MAClDzB,MAAM,CAAC0B,IAAI,CACT,8BAA8BtB,GAAG,CAACG,IAAI,2HACxC,CAAC;MACDiB,MAAM,GAAGjD,oBAAoB;IAC/B;IAEA,MAAMoD,MAAM,GAAGH,MAAM,CAACI,QAAQ,CAACxB,GAAG,EAAE;MAAEyB,UAAU,EAAE;IAAM,CAAC,CAAC;IAE1D,IAAIF,MAAM,CAACG,KAAK,EAAE;MAChB,MAAMH,MAAM,CAACG,KAAK;IACpB;;IAEA;IACA;IACA1B,GAAG,GAAG2B,eAAe,CAACJ,MAAM,CAACK,KAAK,CAAC;;IAEnC;IACA5B,GAAG,CAACC,KAAK,CAAC4B,IAAI,CAAC;MACbC,EAAE,EAAE9B,GAAG,CAACoB,MAAM,KAAKnD,aAAa,CAACoD,EAAE,GAAG3C,aAAa,GAAGD,WAAW;MACjE0B,IAAI,EAAE,SAAS;MACf4B,KAAK,EAAE,QAAQ;MACfC,IAAI,EAAE,SAAS;MACfC,KAAK,EAAE,CACL;QACEH,EAAE,EAAE,sCAAsC;QAC1CI,IAAI,EAAE,KAAK;QACXN,KAAK,EAAE;MACT,CAAC,EACD;QACEE,EAAE,EAAE,sCAAsC;QAC1CI,IAAI,EAAE,IAAI;QACVN,KAAK,EAAE;MACT,CAAC;IAEL,CAAC,CAAC;;IAEF;IACAvC,aAAa,CAACW,GAAG,CAAC;IAElB,IAAI,CAACF,MAAM,GAAGE,GAAG,CAACF,MAAM;IACxB,IAAI,CAACC,aAAa,GAAGC,GAAG,CAACoB,MAAM,IAAInD,aAAa,CAACoD,EAAE;IACnD,IAAI,CAACrB,GAAG,GAAGA,GAAG;IACd,IAAI,CAACC,KAAK,GAAGD,GAAG,CAACC,KAAK;IACtB,IAAI,CAACC,QAAQ,GAAGF,GAAG,CAACE,QAAQ;IAC5B,IAAI,CAACC,IAAI,GAAGH,GAAG,CAACG,IAAI,IAAI,EAAE;IAC1B,IAAI,CAACC,MAAM,GAAGmB,MAAM,CAACK,KAAK;IAC1B,IAAI,CAACvB,QAAQ,GAAGc,OAAO,CAACd,QAAQ;IAChC,IAAI,CAACC,aAAa,GAAGa,OAAO,CAACb,aAAa;IAC1C,IAAI,CAACC,UAAU,GAAG,CAAC,CAAC;IACpB,IAAI,CAACE,QAAQ,GAAGA,QAAQ;IACxB,IAAI,CAACC,WAAW,GAAGA,WAAW;IAE9B,IAAI,CAACC,UAAU,GAAG,IAAIwB,GAAG,CAACnC,GAAG,CAACQ,KAAK,CAAC4B,GAAG,CAAEC,IAAI,IAAK,CAACA,IAAI,CAACC,IAAI,EAAED,IAAI,CAAC,CAAC,CAAC;IACrE,IAAI,CAACzB,UAAU,GAAG,IAAIuB,GAAG,CAACnC,GAAG,CAACC,KAAK,CAACmC,GAAG,CAAEG,IAAI,IAAK,CAACA,IAAI,CAACpC,IAAI,EAAEoC,IAAI,CAAC,CAAC,CAAC;IACrE,IAAI,CAAC1B,YAAY,GAAG,IAAIsB,GAAG,CACzBnC,GAAG,CAACC,KAAK,CACNuC,MAAM,CAAED,IAAI,IAAKA,IAAI,CAACT,EAAE,CAAC,CAAC;IAC3B;IAAA,CACCM,GAAG,CAAEG,IAAI,IAAK,CAACA,IAAI,CAACT,EAAE,EAAYS,IAAI,CAAC,CAC5C,CAAC;IACD,IAAI,CAACzB,eAAe,GAAG,IAAIqB,GAAG,CAC5BnC,GAAG,CAACQ,KAAK,CACNgC,MAAM,CAAClE,aAAa,CAAC,CACrBmE,OAAO,CAAEJ,IAAI,IACZA,IAAI,CAACK,UAAU,CAACN,GAAG,CAAEO,SAAS,IAAK,CAACA,SAAS,CAACxC,IAAI,EAAEwC,SAAS,CAAC,CAChE,CACJ,CAAC;IACD,IAAI,CAAC5B,iBAAiB,GAAG,IAAIoB,GAAG,CAC9BnC,GAAG,CAACQ,KAAK,CAACgC,MAAM,CAAClE,aAAa,CAAC,CAACmE,OAAO,CAAEJ,IAAI,IAC3CA,IAAI,CAACK,UAAU,CACZF,MAAM,CAAEG,SAAS,IAAKA,SAAS,CAACb,EAAE,CAAC,CAAC;IAAA,CACpCM,GAAG,CAAEO,SAAS,IAAK;MAClB;MACA,OAAO,CAACA,SAAS,CAACb,EAAE,EAAYa,SAAS,CAAC;IAC5C,CAAC,CACL,CACF,CAAC;IAED3C,GAAG,CAACO,UAAU,CAACqC,OAAO,CAAEC,YAAY,IAAK;MACvC,MAAMC,SAAS,GAAG,IAAI,CAACC,aAAa,CAClCvE,oBAAoB,CAACqE,YAAY,CAAC,GAC9B3E,6BAA6B,CAAC2E,YAAY,EAAE,IAAI,CAAC,GACjDA,YACN,CAAC;MACD,IAAI,CAACtC,UAAU,CAACuC,SAAS,CAAC3C,IAAI,CAAC,GAAG2C,SAAS;IAC7C,CAAC,CAAC;IAEF,IAAI,CAACtC,KAAK,GAAGR,GAAG,CAACQ,KAAK,CAAC4B,GAAG,CAAEY,OAAO,IAAK1D,UAAU,CAAC,IAAI,EAAE0D,OAAO,CAAC,CAAC;IAElE,IACE,CAAChD,GAAG,CAACQ,KAAK,CAACyC,IAAI,CACb,CAAC;MAAEC;IAAW,CAAC;IACb;IACAA,UAAU,KAAKlF,cAAc,CAACmF,MAClC,CAAC,EACD;MACA,IAAI,CAAC3C,KAAK,CAACqB,IAAI,CACbvC,UAAU,CAAC,IAAI,EAAE;QACfyC,KAAK,EAAE,gBAAgB;QACvBO,IAAI,EAAEvE,cAAc,CAACoF,MAAM;QAC3BD,UAAU,EAAElF,cAAc,CAACmF;MAC7B,CAAC,CACH,CAAC;IACH;IAEA,IAAI,CAACnC,OAAO,GAAG,IAAImB,GAAG,CAAC,IAAI,CAAC3B,KAAK,CAAC4B,GAAG,CAAEC,IAAI,IAAK,CAACA,IAAI,CAACC,IAAI,EAAED,IAAI,CAAC,CAAC,CAAC;IACnE,IAAI,CAACpB,YAAY,GAAG,IAAIkB,GAAG,CACzB,IAAI,CAAC3B,KAAK,CAACiC,OAAO,CAAEJ,IAAI,IACtBA,IAAI,CAACe,UAAU,CAACV,UAAU,CAACN,GAAG,CAAEO,SAAS,IAAK,CAC5CA,SAAS,CAACxC,IAAI,EACdwC,SAAS,CACV,CACH,CACF,CAAC;EACH;;EAEA;AACF;AACA;AACA;EACEU,kBAAkBA,CAACC,aAAoC,EAAE;IACvD;IACA;IACA,IAAIlC,MAAM,GAAGtC,GAAG,CAACyE,MAAM,CAAsB,CAAC,CAACC,QAAQ,CAAC,CAAC;IAEzDF,aAAa,CAACV,OAAO,CAAEP,IAAI,IAAK;MAC9BjB,MAAM,GAAGA,MAAM,CAACqC,MAAM,CAACpB,IAAI,CAACe,UAAU,CAACM,WAAW,CAAC;IACrD,CAAC,CAAC;IAEF,OAAOtC,MAAM;EACf;;EAEA;AACF;AACA;AACA;EACE2B,aAAaA,CAACD,SAA2B,EAAuB;IAC9D,MAAMa,MAAM,GAAG,IAAI9E,MAAM,CAAC;MACxB+E,SAAS,EAAE;QACTC,OAAO,EAAE;MACX;IACF,CAAC,CAAC;IAEFC,MAAM,CAACC,MAAM,CAACJ,MAAM,CAACK,SAAS,EAAE;MAC9BC,iBAAiBA,CAACC,UAAkB,EAAEC,QAAmB,EAAE;QACzD;QACA;QACA;QACA;QACA,OAAOvF,MAAM,CACXD,GAAG,CAACM,eAAe,CAAC,CAAC,EAAE;UAAE,CAACkF,QAAQ,GAAGD;QAAW,CAAC,CAAC,EAClD,YACF,CAAC;MACH;IACF,CAAC,CAAC;IAEF,MAAM;MAAE/D,IAAI;MAAEiE,WAAW;MAAExC;IAAM,CAAC,GAAGkB,SAAS;IAC9C,MAAMuB,IAAI,GAAG,IAAI,CAACC,qBAAqB,CAAC1C,KAAK,EAAE+B,MAAM,CAAC;IAEtD,MAAMY,EAAE,GAAIC,eAA0B,IAAK;MACzC,MAAMC,GAAG,GAAG,IAAI,CAACC,kBAAkB,CAACF,eAAe,EAAE,IAAI,CAACjE,UAAU,CAAC;MACrE,IAAI;QACF,OAAO8D,IAAI,CAACM,QAAQ,CAACF,GAAG,CAAC;MAC3B,CAAC,CAAC,MAAM;QACN,OAAO,KAAK;MACd;IACF,CAAC;IAED,OAAO;MACLtE,IAAI;MACJiE,WAAW;MACXxC,KAAK;MACLyC,IAAI;MACJE;IACF,CAAC;EACH;EAEAG,kBAAkBA,CAChBF,eAA0B,EAC1BjE,UAAwD,EACxD;IACA,MAAMqE,OAAO,GAAG;MAAE,GAAGJ;IAAgB,CAAC;IAEtC,KAAK,MAAMK,WAAW,IAAItE,UAAU,EAAE;MACpC,MAAMuE,YAAY,GAChB,IAAI,CAAC/E,aAAa,KAAK9B,aAAa,CAAC8G,EAAE,GACnC1G,sBAAsB,CAACwG,WAAW,CAAC,GACnCA,WAAW;MAEjBf,MAAM,CAACkB,cAAc,CAACJ,OAAO,EAAEE,YAAY,EAAE;QAC3CG,GAAGA,CAAA,EAAG;UACJ,OAAO1E,UAAU,CAACsE,WAAW,CAAC,EAAEN,EAAE,CAACC,eAAe,CAAC;QACrD;MACF,CAAC,CAAC;IACJ;IAEA,OAAOI,OAAO;EAChB;EAEAN,qBAAqBA,CAAC1C,KAA0B,EAAE+B,MAAc,EAAE;IAChE,MAAMpD,UAAU,GAAGzC,eAAe,CAACoH,IAAI,CAACtD,KAAK,CAAC;IAC9C,OAAO+B,MAAM,CAACwB,KAAK,CAAC5E,UAAU,CAAC6E,YAAY,CAAC,CAAC,CAAC;EAChD;EAEAC,OAAOA,CAACC,QAAgB,EAAoB;IAC1C,OAAO,IAAI,CAACvF,aAAa,KAAK9B,aAAa,CAACoD,EAAE,GAC1C,IAAI,CAACpB,KAAK,CAACsF,IAAI,CAAEhD,IAAI,IAAKA,IAAI,CAACpC,IAAI,KAAKmF,QAAQ,CAAC,GACjD,IAAI,CAACrF,KAAK,CAACsF,IAAI,CAAEhD,IAAI,IAAKA,IAAI,CAACT,EAAE,KAAKwD,QAAQ,CAAC;EACrD;;EAEA;AACF;AACA;EACEE,cAAcA,CACZC,OAA2B,EAC3BC,KAAgB,EAChBC,MAA8B,EACjB;IACb,MAAM;MAAEC;IAAM,CAAC,GAAGH,OAAO;IAEzB,MAAMpD,IAAI,GAAGjD,OAAO,CAAC,IAAI,EAAEqG,OAAO,CAAC;;IAEnC;IACA,MAAMI,WAAW,GAAGxD,IAAI,CAACC,IAAI;IAC7B,MAAMwD,SAAS,GAAGzD,IAAI,CAAC0D,YAAY,CAAC,CAAC;;IAErC;IACA,MAAMC,aAAa,GAAG,OAAO,IAAIJ,KAAK;IAEtC,IAAIhB,OAAoB,GAAG;MACzBJ,eAAe,EAAE,CAAC,CAAC;MACnByB,aAAa,EAAE,CAAC,CAAC;MACjB3C,aAAa,EAAE,EAAE;MACjB4C,OAAO,EAAE7D,IAAI,CAAC8D,oBAAoB,CAACV,OAAO,EAAEC,KAAK,CAAC;MAClDA,KAAK;MACLU,KAAK,EAAE,EAAE;MACTT,MAAM;MACNK,aAAa;MACbK,IAAI,EAAE,CAAC,CAAC;MACR1F,UAAU,EAAE,IAAI,CAACA,UAAU;MAC3BC,UAAU,EAAE,IAAI,CAACA,UAAU;MAC3BE,eAAe,EAAE,IAAI,CAACA,eAAe;MACrCE,OAAO,EAAE,IAAI,CAACA,OAAO;MACrBC,YAAY,EAAE,IAAI,CAACA,YAAY;MAC/BqF,eAAe,EAAEC,kBAAkB,CAACb,KAAK,CAAC;MAC1Cc,sBAAsB,EAAE,IAAI,CAAClG;IAC/B,CAAC;;IAED;IACAsE,OAAO,GAAG6B,mBAAmB,CAAChB,OAAO,EAAEpD,IAAI,EAAEuC,OAAO,CAAC;;IAErD;IACA,IAAI8B,QAAQ,GAAGxH,QAAQ,CAAC,IAAI,EAAE4G,SAAS,CAAC;IAExC,IAAI,CAACa,iBAAiB,CAAC/B,OAAO,CAAC;;IAE/B;IACA,OAAO8B,QAAQ,EAAE;MACf;MACA9B,OAAO,CAACtB,aAAa,CAACzB,IAAI,CAAC6E,QAAQ,CAAC;MAEpC,IAAI,CAACE,qBAAqB,CAAChC,OAAO,EAAE8B,QAAQ,CAAC;MAE7C,IAAI,CAACG,mBAAmB,CAACjC,OAAO,EAAE8B,QAAQ,CAAC;;MAE3C;MACA,IACE,IAAI,CAACI,kBAAkB,CAAClC,OAAO,EAAE8B,QAAQ,CAAC,IAC1CA,QAAQ,CAACpE,IAAI,KAAKuD,WAAW,EAC7B;QACA;MACF;;MAEA;MACAa,QAAQ,GAAGxH,QAAQ,CAAC,IAAI,EAAEwH,QAAQ,CAACK,WAAW,CAACnC,OAAO,CAAC,CAAC;IAC1D;;IAEA;IACAA,OAAO,GAAGoC,iBAAiB,CAACvB,OAAO,EAAEpD,IAAI,EAAEuC,OAAO,CAAC;;IAEnD;IACA,IAAI,CAACqC,WAAW,CAACrC,OAAO,CAAC;IAEzB,OAAOA,OAAO;EAChB;EAEQ+B,iBAAiBA,CAAC/B,OAAoB,EAAE;IAC9C;IACA;IACA;IACA,MAAMsC,UAAU,GAAGpD,MAAM,CAACqD,MAAM,CAAC,CAAC,CAAC,CAAC;IAEpC,KAAK,MAAM9E,IAAI,IAAI,IAAI,CAAC7B,KAAK,EAAE;MAC7B,MAAM;QAAE4C,UAAU;QAAEJ;MAAQ,CAAC,GAAGX,IAAI;MAEpC,IAAI,CAAC9D,WAAW,CAACyE,OAAO,CAAC,EAAE;QACzBc,MAAM,CAACC,MAAM,CACXa,OAAO,CAACJ,eAAe,EACvBpB,UAAU,CAACgE,wBAAwB,CAACF,UAAU,CAChD,CAAC;MACH;IACF;EACF;EAEQN,qBAAqBA,CAC3BhC,OAAoB,EACpBvC,IAAyB,EACzB;IACA,MAAM;MAAEe,UAAU;MAAEJ;IAAQ,CAAC,GAAGX,IAAI;IACpC;;IAEA,IAAI,CAAC9D,WAAW,CAACyE,OAAO,CAAC,EAAE;MACzBc,MAAM,CAACC,MAAM,CACXa,OAAO,CAACJ,eAAe,EACvBpB,UAAU,CAACgE,wBAAwB,CAACxC,OAAO,CAACc,KAAK,CACnD,CAAC;IACH;EACF;EAEQmB,mBAAmBA,CAACjC,OAAoB,EAAEvC,IAAyB,EAAE;IAC3E;IACA,KAAK,MAAMgF,GAAG,IAAIhF,IAAI,CAACiF,IAAI,EAAE;MAC3B,IAAI,OAAO1C,OAAO,CAACc,KAAK,CAAC2B,GAAG,CAAC,KAAK,WAAW,EAAE;QAC7CzC,OAAO,CAACqB,aAAa,CAACoB,GAAG,CAAC,GAAGzC,OAAO,CAACc,KAAK,CAAC2B,GAAG,CAAC;MACjD;IACF;EACF;EAEQP,kBAAkBA,CAAClC,OAAoB,EAAEvC,IAAyB,EAAE;IAC1E;IACA,MAAMkF,UAAU,GAAGlF,IAAI,CAACe,UAAU,CAACoE,MAAM,CAAChF,MAAM,CAACxD,gBAAgB,CAAC;;IAElE;IACA;IACA;IACA,KAAK,MAAMyI,KAAK,IAAIF,UAAU,EAAE;MAC9B,MAAMhF,IAAI,GAAGkF,KAAK,CAAClF,IAAI;;MAEvB;MACA,IAAIA,IAAI,KAAKmF,SAAS,IAAID,KAAK,CAACzF,IAAI,KAAKnE,aAAa,CAAC8J,UAAU,EAAE;QACjE,MAAMC,gBAAgB,GACpBrF,IAAI,CAACN,KAAK,CAACO,MAAM,CAAEqF,IAAI,IAAKA,IAAI,CAAC/E,SAAS,CAAC,CAACgF,MAAM,GAAG,CAAC;QAExD,IAAIF,gBAAgB,EAAE;UACpB,OAAO,IAAI,CAACG,mBAAmB,CAACnD,OAAO,EAAE6C,KAAK,EAAElF,IAAI,CAAC;QACvD;MACF;IACF;EACF;EAEQwF,mBAAmBA,CACzBnD,OAAoB,EACpB6C,KAAwB,EACxBlF,IAAU,EACV;IACA,MAAM;MAAEiC,eAAe;MAAEkB;IAAM,CAAC,GAAGd,OAAO;IAE1C,MAAMoD,WAAW,GAAGzF,IAAI,CAACN,KAAK,CAC3BO,MAAM,CAAEqF,IAAI,IACXA,IAAI,CAAC/E,SAAS,GACV,IAAI,CAACvC,UAAU,CAACsH,IAAI,CAAC/E,SAAS,CAAC,EAAEyB,EAAE,CAACC,eAAe,CAAC,GACpD,IACN,CAAC,CACApC,GAAG,CAAEyF,IAAI,IAAKA,IAAI,CAACjG,KAAK,CAAC;;IAE5B;IACA,MAAMqG,UAAU,GAAGR,KAAK,CAACS,qBAAqB,CAACxC,KAAK,CAAC;IAErD,IAAIuC,UAAU,KAAKP,SAAS,EAAE;MAC5B,IAAIS,SAAS,GAAG,KAAK;MACrB,MAAMC,OAAO,GAAGC,KAAK,CAACD,OAAO,CAACH,UAAU,CAAC;;MAEzC;MACA;MACA,IAAIG,OAAO,EAAE;QACXD,SAAS,GAAG,CAACF,UAAU,CAACK,KAAK,CAAET,IAAI,IAAKG,WAAW,CAACO,QAAQ,CAACV,IAAI,CAAC,CAAC;MACrE,CAAC,MAAM;QACLM,SAAS,GAAG,CAACH,WAAW,CAACO,QAAQ,CAACN,UAAU,CAAC;MAC/C;MAEA,IAAIE,SAAS,EAAE;QACbvD,OAAO,CAACe,MAAM,KAAK,EAAE;QAErB,MAAMzD,IAAI,GACR,6DAA6D;QAE/D0C,OAAO,CAACe,MAAM,CAAC9D,IAAI,CAAC;UAClBK,IAAI;UACJ/B,IAAI,EAAEsH,KAAK,CAACtH,IAAI;UAChBqI,IAAI,EAAE,IAAIf,KAAK,CAACtH,IAAI,EAAE;UACtBmC,IAAI,EAAE,CAAC,IAAImF,KAAK,CAACtH,IAAI,EAAE;QACzB,CAAC,CAAC;MACJ;MAEA,OAAOgI,SAAS;IAClB;EACF;EAEQlB,WAAWA,CAACrC,OAAoB,EAAE;IACxC,KAAK,MAAM;MAAE0C,IAAI;MAAEhF;IAAK,CAAC,IAAIsC,OAAO,CAACtB,aAAa,EAAE;MAClDsB,OAAO,CAACwB,KAAK,CAACvE,IAAI,CAACS,IAAI,CAAC;;MAExB;MACA,IACEsC,OAAO,CAACe,MAAM,EAAE1C,IAAI,CAAC,CAAC;QAAE9C,IAAI;QAAEmC;MAAK,CAAC,KAAK;QACvC,OAAOgF,IAAI,CAACiB,QAAQ,CAACpI,IAAI,CAAC,IAAImH,IAAI,CAACrE,IAAI,CAAEoE,GAAG,IAAK/E,IAAI,CAACiG,QAAQ,CAAClB,GAAG,CAAC,CAAC;MACtE,CAAC,CAAC,EACF;QACA;MACF;IACF;EACF;EAEAoB,gBAAgBA,CAACC,WAAmB,EAA4B;IAC9D,OAAO,IAAI,CAAC3H,iBAAiB,CAACkE,GAAG,CAACyD,WAAW,CAAC;EAChD;EAEAC,WAAWA,CAACC,MAAc,EAAoB;IAC5C,OAAO,IAAI,CAAC/H,YAAY,CAACoE,GAAG,CAAC2D,MAAM,CAAC;EACtC;;EAEA;AACF;AACA;AACA;AACA;EACEC,gBAAgBA,CAAChE,WAAmB,EAAkC;IACpE,OAAO,IAAI,CAAC7E,GAAG,CAACO,UAAU,CACvBiC,MAAM,CAAChE,oBAAoB,CAAC,CAC5B+G,IAAI,CAAEzC,SAAS,IAAKA,SAAS,CAAChB,EAAE,KAAK+C,WAAW,CAAC;EACtD;AACF;;AAEA;AACA;AACA;AACA,SAAS4B,mBAAmBA,CAC1BhB,OAA2B,EAC3BpD,IAAyB,EACzBuC,OAAoB,EACP;EACb,MAAM;IAAExB;EAAW,CAAC,GAAGf,IAAI;EAC3B,MAAM;IAAE6D,OAAO;IAAER;EAAM,CAAC,GAAGd,OAAO;EAElC,MAAM;IAAEkE;EAAO,CAAC,GAAGzG,IAAI,CAAC0G,aAAa,CAACtD,OAAO,CAAC;;EAE9C;EACA,IACE,CAACA,OAAO,CAACS,OAAO,IACf4C,MAAM,IAAI,CAAC,CAACpJ,UAAU,CAACsJ,QAAQ,EAAEtJ,UAAU,CAACuJ,WAAW,CAAC,CAACV,QAAQ,CAACO,MAAM,CAAE,EAC3E;IACA,OAAOlE,OAAO;EAChB;;EAEA;EACA;EACA;EACA,MAAMsE,MAAM,GAAG;IAAE,GAAGzD,OAAO,CAACS;EAAQ,CAAC;EACrC9C,UAAU,CAACoE,MAAM,CAAC5E,OAAO,CAAE6E,KAAK,IAAK;IACnC,IACEA,KAAK,CAACzF,IAAI,KAAKnE,aAAa,CAACsL,eAAe,IAC5C,EAAE1B,KAAK,CAACtH,IAAI,IAAI+I,MAAM,CAAC,EACvB;MACAA,MAAM,CAACzB,KAAK,CAACtH,IAAI,CAAC,GAAGuH,SAAS;IAChC;EACF,CAAC,CAAC;EAEF,MAAM;IAAE9F,KAAK;IAAE+D;EAAO,CAAC,GAAGvC,UAAU,CAAC5B,QAAQ,CAAC;IAC5C,GAAG0E,OAAO;IACV,GAAGgD;EACL,CAAC,CAAC;;EAEF;EACA,MAAME,SAAS,GAAG/G,IAAI,CAACgH,qBAAqB,CAAC5D,OAAO,EAAEC,KAAK,EAAE9D,KAAK,CAAC;EAEnE,OAAO;IACL,GAAGgD,OAAO;IACVsB,OAAO,EAAEvG,KAAK,CAACuG,OAAO,EAAEtE,KAAK,CAAC;IAC9B8D,KAAK,EAAE/F,KAAK,CAAC+F,KAAK,EAAE0D,SAAS,CAAC;IAC9BzD;EACF,CAAC;AACH;;AAEA;AACA;AACA;AACA,SAASqB,iBAAiBA,CACxBvB,OAA2B,EAC3BpD,IAAyB,EACzBuC,OAAoB,EACP;EACb,MAAM;IAAEe,MAAM,GAAG,EAAE;IAAErC,aAAa;IAAE2C;EAAc,CAAC,GAAGrB,OAAO;;EAE7D;EACA,MAAM0E,aAAa,GAAGhG,aAAa,CAACd,MAAM,CACvC+G,YAAY,IAAKA,YAAY,KAAKlH,IACrC,CAAC;;EAED;EACA,MAAM;IAAEX;EAAM,CAAC,GAAGW,IAAI,CAACmH,KAAK,CACzBnG,kBAAkB,CAACiG,aAAa,CAAC,CACjC9H,QAAQ,CAACyE,aAAa,EAAE;IAAE,GAAGzG,IAAI;IAAEiK,YAAY,EAAE;EAAK,CAAC,CAAC;;EAE3D;EACA,IAAI/H,KAAK,EAAE;IACT,MAAMgI,WAAW,GAAGhI,KAAK,CAACiI,OAAO,CAACvH,GAAG,CAACjD,QAAQ,CAAC;IAC/C,OAAO;MAAE,GAAGyF,OAAO;MAAEe,MAAM,EAAEA,MAAM,CAAClC,MAAM,CAACiG,WAAW;IAAE,CAAC;EAC3D;EAEA,OAAO9E,OAAO;AAChB;AAEA,SAAS2B,kBAAkBA,CAACb,KAAgB,EAAU;EACpD,IACE,CAACA,KAAK,CAACkE,mBAAmB,IAC1B,OAAOlE,KAAK,CAACkE,mBAAmB,KAAK,QAAQ,EAC7C;IACA,MAAMC,KAAK,CAAC,0CAA0C,CAAC;EACzD;EAEA,OAAOnE,KAAK,CAACkE,mBAAmB;AAClC","ignoreList":[]}
|
|
@@ -12,7 +12,9 @@ export async function getHandler(request, h) {
|
|
|
12
12
|
error: 'Status check failed'
|
|
13
13
|
}).code(400);
|
|
14
14
|
}
|
|
15
|
-
return h.response(
|
|
15
|
+
return h.response({
|
|
16
|
+
uploadStatus: status.uploadStatus
|
|
17
|
+
});
|
|
16
18
|
} catch (err) {
|
|
17
19
|
request.logger.error(err, `[uploadStatusFailed] Upload status check failed for uploadId: ${uploadId} - ${getErrorMessage(err)}`);
|
|
18
20
|
return h.response({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-upload.js","names":["getErrorMessage","Joi","getUploadStatus","getHandler","request","h","uploadId","params","status","response","error","code","err","logger","getRoutes","method","path","handler","options","plugins","crumb","validate","object","keys","string","guid","required"],"sources":["../../../../../src/server/plugins/engine/routes/file-upload.ts"],"sourcesContent":["import { getErrorMessage } from '@defra/forms-model'\nimport { type ResponseToolkit, type ServerRoute } from '@hapi/hapi'\nimport Joi from 'joi'\n\nimport { getUploadStatus } from '~/src/server/plugins/engine/services/uploadService.js'\nimport {\n type FormRequest,\n type FormRequestRefs\n} from '~/src/server/routes/types.js'\n\nexport async function getHandler(\n request: FormRequest,\n h: Pick<ResponseToolkit, 'response'>\n) {\n const { uploadId } = request.params as unknown as {\n uploadId: string\n }\n try {\n const status = await getUploadStatus(uploadId)\n\n if (!status) {\n return h.response({ error: 'Status check failed' }).code(400)\n }\n\n return h.response(status)\n } catch (err) {\n request.logger.error(\n err,\n `[uploadStatusFailed] Upload status check failed for uploadId: ${uploadId} - ${getErrorMessage(err)}`\n )\n return h.response({ error: 'Status check error' }).code(500)\n }\n}\n\nexport function getRoutes(): ServerRoute<FormRequestRefs>[] {\n return [\n {\n method: 'get',\n path: '/upload-status/{uploadId}',\n handler: getHandler,\n options: {\n plugins: {\n crumb: false\n },\n validate: {\n params: Joi.object().keys({\n uploadId: Joi.string().guid().required()\n })\n }\n }\n }\n ]\n}\n"],"mappings":"AAAA,SAASA,eAAe,QAAQ,oBAAoB;AAEpD,OAAOC,GAAG,MAAM,KAAK;AAErB,SAASC,eAAe;AAMxB,OAAO,eAAeC,UAAUA,CAC9BC,OAAoB,EACpBC,CAAoC,EACpC;EACA,MAAM;IAAEC;EAAS,CAAC,GAAGF,OAAO,CAACG,MAE5B;EACD,IAAI;IACF,MAAMC,MAAM,GAAG,MAAMN,eAAe,CAACI,QAAQ,CAAC;IAE9C,IAAI,CAACE,MAAM,EAAE;MACX,OAAOH,CAAC,CAACI,QAAQ,CAAC;QAAEC,KAAK,EAAE;MAAsB,CAAC,CAAC,CAACC,IAAI,CAAC,GAAG,CAAC;IAC/D;IAEA,OAAON,CAAC,CAACI,QAAQ,
|
|
1
|
+
{"version":3,"file":"file-upload.js","names":["getErrorMessage","Joi","getUploadStatus","getHandler","request","h","uploadId","params","status","response","error","code","uploadStatus","err","logger","getRoutes","method","path","handler","options","plugins","crumb","validate","object","keys","string","guid","required"],"sources":["../../../../../src/server/plugins/engine/routes/file-upload.ts"],"sourcesContent":["import { getErrorMessage } from '@defra/forms-model'\nimport { type ResponseToolkit, type ServerRoute } from '@hapi/hapi'\nimport Joi from 'joi'\n\nimport { getUploadStatus } from '~/src/server/plugins/engine/services/uploadService.js'\nimport {\n type FormRequest,\n type FormRequestRefs\n} from '~/src/server/routes/types.js'\n\nexport async function getHandler(\n request: FormRequest,\n h: Pick<ResponseToolkit, 'response'>\n) {\n const { uploadId } = request.params as unknown as {\n uploadId: string\n }\n try {\n const status = await getUploadStatus(uploadId)\n\n if (!status) {\n return h.response({ error: 'Status check failed' }).code(400)\n }\n\n return h.response({\n uploadStatus: status.uploadStatus\n })\n } catch (err) {\n request.logger.error(\n err,\n `[uploadStatusFailed] Upload status check failed for uploadId: ${uploadId} - ${getErrorMessage(err)}`\n )\n return h.response({ error: 'Status check error' }).code(500)\n }\n}\n\nexport function getRoutes(): ServerRoute<FormRequestRefs>[] {\n return [\n {\n method: 'get',\n path: '/upload-status/{uploadId}',\n handler: getHandler,\n options: {\n plugins: {\n crumb: false\n },\n validate: {\n params: Joi.object().keys({\n uploadId: Joi.string().guid().required()\n })\n }\n }\n }\n ]\n}\n"],"mappings":"AAAA,SAASA,eAAe,QAAQ,oBAAoB;AAEpD,OAAOC,GAAG,MAAM,KAAK;AAErB,SAASC,eAAe;AAMxB,OAAO,eAAeC,UAAUA,CAC9BC,OAAoB,EACpBC,CAAoC,EACpC;EACA,MAAM;IAAEC;EAAS,CAAC,GAAGF,OAAO,CAACG,MAE5B;EACD,IAAI;IACF,MAAMC,MAAM,GAAG,MAAMN,eAAe,CAACI,QAAQ,CAAC;IAE9C,IAAI,CAACE,MAAM,EAAE;MACX,OAAOH,CAAC,CAACI,QAAQ,CAAC;QAAEC,KAAK,EAAE;MAAsB,CAAC,CAAC,CAACC,IAAI,CAAC,GAAG,CAAC;IAC/D;IAEA,OAAON,CAAC,CAACI,QAAQ,CAAC;MAChBG,YAAY,EAAEJ,MAAM,CAACI;IACvB,CAAC,CAAC;EACJ,CAAC,CAAC,OAAOC,GAAG,EAAE;IACZT,OAAO,CAACU,MAAM,CAACJ,KAAK,CAClBG,GAAG,EACH,iEAAiEP,QAAQ,MAAMN,eAAe,CAACa,GAAG,CAAC,EACrG,CAAC;IACD,OAAOR,CAAC,CAACI,QAAQ,CAAC;MAAEC,KAAK,EAAE;IAAqB,CAAC,CAAC,CAACC,IAAI,CAAC,GAAG,CAAC;EAC9D;AACF;AAEA,OAAO,SAASI,SAASA,CAAA,EAAmC;EAC1D,OAAO,CACL;IACEC,MAAM,EAAE,KAAK;IACbC,IAAI,EAAE,2BAA2B;IACjCC,OAAO,EAAEf,UAAU;IACnBgB,OAAO,EAAE;MACPC,OAAO,EAAE;QACPC,KAAK,EAAE;MACT,CAAC;MACDC,QAAQ,EAAE;QACRf,MAAM,EAAEN,GAAG,CAACsB,MAAM,CAAC,CAAC,CAACC,IAAI,CAAC;UACxBlB,QAAQ,EAAEL,GAAG,CAACwB,MAAM,CAAC,CAAC,CAACC,IAAI,CAAC,CAAC,CAACC,QAAQ,CAAC;QACzC,CAAC;MACH;IACF;EACF,CAAC,CACF;AACH","ignoreList":[]}
|
package/package.json
CHANGED
|
@@ -230,13 +230,27 @@ function reloadPage() {
|
|
|
230
230
|
|
|
231
231
|
/**
|
|
232
232
|
* Build the upload status URL given the current pathname and the upload ID.
|
|
233
|
+
* This only works when called on a file upload page that has a maximum depth of 1 URL segments following the slug.
|
|
233
234
|
* @param {string} pathname – e.g. window.location.pathname
|
|
234
235
|
* @param {string} uploadId
|
|
235
236
|
* @returns {string} e.g. "/form/upload-status/abc123"
|
|
236
237
|
*/
|
|
237
238
|
export function buildUploadStatusUrl(pathname, uploadId) {
|
|
238
|
-
|
|
239
|
-
const
|
|
239
|
+
// Remove preview markers and duplicate slashes
|
|
240
|
+
const normalisedPath = pathname
|
|
241
|
+
.replace(/\/preview\/(draft|live)/g, '')
|
|
242
|
+
.replace(/\/{2,}/g, '/')
|
|
243
|
+
.replace(/\/$/, '')
|
|
244
|
+
|
|
245
|
+
const segments = normalisedPath.split('/').filter(Boolean)
|
|
246
|
+
|
|
247
|
+
// The slug is always the second to last segment
|
|
248
|
+
// The prefix is everything before the slug
|
|
249
|
+
const prefix =
|
|
250
|
+
segments.length > 2
|
|
251
|
+
? `/${segments.slice(0, segments.length - 2).join('/')}`
|
|
252
|
+
: ''
|
|
253
|
+
|
|
240
254
|
return `${prefix}/upload-status/${uploadId}`
|
|
241
255
|
}
|
|
242
256
|
|
package/src/server/index.test.ts
CHANGED
|
@@ -508,7 +508,9 @@ describe('Upload status route', () => {
|
|
|
508
508
|
const res = await server.inject(options)
|
|
509
509
|
|
|
510
510
|
expect(res.statusCode).toBe(StatusCodes.OK)
|
|
511
|
-
expect(res.result).toEqual(
|
|
511
|
+
expect(res.result).toEqual({
|
|
512
|
+
uploadStatus: UploadStatus.ready
|
|
513
|
+
})
|
|
512
514
|
expect(getUploadStatus).toHaveBeenCalledWith(
|
|
513
515
|
'123e4567-e89b-12d3-a456-426614174000'
|
|
514
516
|
)
|
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
ConditionsModel,
|
|
4
4
|
ControllerPath,
|
|
5
5
|
ControllerType,
|
|
6
|
-
Engine,
|
|
7
6
|
SchemaVersion,
|
|
8
7
|
convertConditionWrapperFromV2,
|
|
9
8
|
formDefinitionSchema,
|
|
@@ -19,6 +18,7 @@ import {
|
|
|
19
18
|
type ConditionWrapperV2,
|
|
20
19
|
type ConditionsModelData,
|
|
21
20
|
type DateUnits,
|
|
21
|
+
type Engine,
|
|
22
22
|
type FormDefinition,
|
|
23
23
|
type List,
|
|
24
24
|
type Page
|
|
@@ -363,10 +363,7 @@ export class FormModel {
|
|
|
363
363
|
// Add page to context
|
|
364
364
|
context.relevantPages.push(nextPage)
|
|
365
365
|
|
|
366
|
-
|
|
367
|
-
if (this.engine !== Engine.V2) {
|
|
368
|
-
this.assignEvaluationState(context, nextPage)
|
|
369
|
-
}
|
|
366
|
+
this.assignEvaluationState(context, nextPage)
|
|
370
367
|
|
|
371
368
|
this.assignRelevantState(context, nextPage)
|
|
372
369
|
|
|
@@ -392,12 +389,19 @@ export class FormModel {
|
|
|
392
389
|
}
|
|
393
390
|
|
|
394
391
|
private initialiseContext(context: FormContext) {
|
|
395
|
-
//
|
|
392
|
+
// Initialise `evaluationState` for all keys using empty state.
|
|
396
393
|
// This is because the current condition evaluation library (eval-expr)
|
|
397
394
|
// will throw if an expression uses a key that is undefined.
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
395
|
+
const emptyState = Object.freeze({})
|
|
396
|
+
|
|
397
|
+
for (const page of this.pages) {
|
|
398
|
+
const { collection, pageDef } = page
|
|
399
|
+
|
|
400
|
+
if (!hasRepeater(pageDef)) {
|
|
401
|
+
Object.assign(
|
|
402
|
+
context.evaluationState,
|
|
403
|
+
collection.getContextValueFromState(emptyState)
|
|
404
|
+
)
|
|
401
405
|
}
|
|
402
406
|
}
|
|
403
407
|
}
|