@defra/forms-engine-plugin 0.1.27 → 0.1.29

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.
Files changed (39) hide show
  1. package/.public/javascripts/application.min.js +1 -1
  2. package/.public/javascripts/application.min.js.map +1 -1
  3. package/.public/javascripts/shared.min.js +1 -1
  4. package/.public/javascripts/shared.min.js.map +1 -1
  5. package/.public/javascripts/vendor/accessible-autocomplete.min.js.map +1 -1
  6. package/.public/stylesheets/application.min.css +1 -1
  7. package/.public/stylesheets/application.min.css.map +1 -1
  8. package/.server/client/stylesheets/_tag-env.scss +0 -9
  9. package/.server/config/index.d.ts +17 -19
  10. package/.server/config/index.js +0 -9
  11. package/.server/config/index.js.map +1 -1
  12. package/.server/server/plugins/engine/components/FormComponent.js +1 -0
  13. package/.server/server/plugins/engine/components/FormComponent.js.map +1 -1
  14. package/.server/server/plugins/engine/components/helpers.js +1 -3
  15. package/.server/server/plugins/engine/components/helpers.js.map +1 -1
  16. package/.server/server/plugins/engine/index.js +3 -2
  17. package/.server/server/plugins/engine/index.js.map +1 -1
  18. package/.server/server/plugins/engine/models/FormModel.js +1 -3
  19. package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
  20. package/.server/server/plugins/engine/views/components/tag-env/template.njk +5 -28
  21. package/.server/server/plugins/engine/views/components/tag-env/template.test.js +19 -53
  22. package/.server/server/plugins/engine/views/components/tag-env/template.test.js.map +1 -1
  23. package/.server/server/plugins/nunjucks/environment.d.ts +1 -0
  24. package/.server/server/plugins/nunjucks/environment.js +4 -1
  25. package/.server/server/plugins/nunjucks/environment.js.map +1 -1
  26. package/.server/server/services/cacheService.js +2 -2
  27. package/.server/server/services/cacheService.js.map +1 -1
  28. package/.server/server/services/httpService.d.ts +1 -1
  29. package/package.json +24 -24
  30. package/src/client/stylesheets/_tag-env.scss +0 -9
  31. package/src/config/index.ts +0 -10
  32. package/src/server/plugins/engine/components/FormComponent.ts +1 -0
  33. package/src/server/plugins/engine/components/helpers.ts +1 -3
  34. package/src/server/plugins/engine/index.ts +4 -2
  35. package/src/server/plugins/engine/models/FormModel.ts +1 -3
  36. package/src/server/plugins/engine/views/components/tag-env/template.njk +5 -28
  37. package/src/server/plugins/engine/views/components/tag-env/template.test.js +18 -56
  38. package/src/server/plugins/nunjucks/environment.js +6 -1
  39. package/src/server/services/cacheService.ts +2 -2
@@ -1,59 +1,25 @@
1
1
  import { renderMacro } from "../../../../../../../test/helpers/component-helpers.js";
2
2
  describe('Tag environment component', () => {
3
- describe.each([{
4
- text: 'Local',
5
- env: 'local',
6
- colour: 'green'
7
- }, {
8
- text: 'Development',
9
- env: 'dev',
10
- colour: 'grey'
11
- }, {
12
- text: 'Test',
13
- env: 'test',
14
- colour: 'yellow'
15
- }, {
16
- text: 'External test',
17
- env: 'ext-test',
18
- colour: 'yellow'
19
- }, {
20
- text: 'Performance test',
21
- env: 'perf-test',
22
- colour: 'yellow'
23
- }, {
24
- text: 'Production',
25
- env: 'prod',
26
- colour: 'red'
27
- }, {
28
- text: 'Unknown environment',
29
- env: 'unknown-environment',
30
- colour: 'grey'
31
- }])('Environment: $text', ({
32
- text,
33
- env,
34
- colour
35
- }) => {
36
- let $component = /** @type {HTMLElement | null} */null;
37
- beforeEach(() => {
38
- const {
39
- container
40
- } = renderMacro('appTagEnv', 'components/tag-env/macro.njk', {
41
- params: {
42
- env
43
- }
44
- });
45
- $component = container.getByRole('strong');
46
- });
47
- it('should render contents', () => {
48
- expect($component).toBeInTheDocument();
49
- expect($component).toHaveClass('govuk-tag');
50
- });
51
- it('should have text content', () => {
52
- expect($component).toHaveTextContent(text);
53
- });
54
- it('should use environment colour', () => {
55
- expect($component).toHaveClass(`govuk-tag--${colour}`);
3
+ let $component = /** @type {HTMLElement | null} */null;
4
+ beforeEach(() => {
5
+ const {
6
+ container
7
+ } = renderMacro('appTagEnv', 'components/tag-env/macro.njk', {
8
+ params: {
9
+ env: 'Devtool'
10
+ }
56
11
  });
12
+ $component = container.getByRole('strong');
13
+ });
14
+ it('should render contents', () => {
15
+ expect($component).toBeInTheDocument();
16
+ expect($component).toHaveClass('govuk-tag');
17
+ });
18
+ it('should have text content', () => {
19
+ expect($component).toHaveTextContent('Devtool');
20
+ });
21
+ it('should use environment colour', () => {
22
+ expect($component).toHaveClass(`govuk-tag--grey`);
57
23
  });
58
24
  });
59
25
  //# sourceMappingURL=template.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"template.test.js","names":["renderMacro","describe","each","text","env","colour","$component","beforeEach","container","params","getByRole","it","expect","toBeInTheDocument","toHaveClass","toHaveTextContent"],"sources":["../../../../../../../src/server/plugins/engine/views/components/tag-env/template.test.js"],"sourcesContent":["import { renderMacro } from '~/test/helpers/component-helpers.js'\n\ndescribe('Tag environment component', () => {\n describe.each([\n {\n text: 'Local',\n env: 'local',\n colour: 'green'\n },\n {\n text: 'Development',\n env: 'dev',\n colour: 'grey'\n },\n {\n text: 'Test',\n env: 'test',\n colour: 'yellow'\n },\n {\n text: 'External test',\n env: 'ext-test',\n colour: 'yellow'\n },\n {\n text: 'Performance test',\n env: 'perf-test',\n colour: 'yellow'\n },\n {\n text: 'Production',\n env: 'prod',\n colour: 'red'\n },\n {\n text: 'Unknown environment',\n env: 'unknown-environment',\n colour: 'grey'\n }\n ])('Environment: $text', ({ text, env, colour }) => {\n let $component = /** @type {HTMLElement | null} */ (null)\n\n beforeEach(() => {\n const { container } = renderMacro(\n 'appTagEnv',\n 'components/tag-env/macro.njk',\n { params: { env } }\n )\n\n $component = container.getByRole('strong')\n })\n\n it('should render contents', () => {\n expect($component).toBeInTheDocument()\n expect($component).toHaveClass('govuk-tag')\n })\n\n it('should have text content', () => {\n expect($component).toHaveTextContent(text)\n })\n\n it('should use environment colour', () => {\n expect($component).toHaveClass(`govuk-tag--${colour}`)\n })\n })\n})\n"],"mappings":"AAAA,SAASA,WAAW;AAEpBC,QAAQ,CAAC,2BAA2B,EAAE,MAAM;EAC1CA,QAAQ,CAACC,IAAI,CAAC,CACZ;IACEC,IAAI,EAAE,OAAO;IACbC,GAAG,EAAE,OAAO;IACZC,MAAM,EAAE;EACV,CAAC,EACD;IACEF,IAAI,EAAE,aAAa;IACnBC,GAAG,EAAE,KAAK;IACVC,MAAM,EAAE;EACV,CAAC,EACD;IACEF,IAAI,EAAE,MAAM;IACZC,GAAG,EAAE,MAAM;IACXC,MAAM,EAAE;EACV,CAAC,EACD;IACEF,IAAI,EAAE,eAAe;IACrBC,GAAG,EAAE,UAAU;IACfC,MAAM,EAAE;EACV,CAAC,EACD;IACEF,IAAI,EAAE,kBAAkB;IACxBC,GAAG,EAAE,WAAW;IAChBC,MAAM,EAAE;EACV,CAAC,EACD;IACEF,IAAI,EAAE,YAAY;IAClBC,GAAG,EAAE,MAAM;IACXC,MAAM,EAAE;EACV,CAAC,EACD;IACEF,IAAI,EAAE,qBAAqB;IAC3BC,GAAG,EAAE,qBAAqB;IAC1BC,MAAM,EAAE;EACV,CAAC,CACF,CAAC,CAAC,oBAAoB,EAAE,CAAC;IAAEF,IAAI;IAAEC,GAAG;IAAEC;EAAO,CAAC,KAAK;IAClD,IAAIC,UAAU,GAAG,iCAAmC,IAAK;IAEzDC,UAAU,CAAC,MAAM;MACf,MAAM;QAAEC;MAAU,CAAC,GAAGR,WAAW,CAC/B,WAAW,EACX,8BAA8B,EAC9B;QAAES,MAAM,EAAE;UAAEL;QAAI;MAAE,CACpB,CAAC;MAEDE,UAAU,GAAGE,SAAS,CAACE,SAAS,CAAC,QAAQ,CAAC;IAC5C,CAAC,CAAC;IAEFC,EAAE,CAAC,wBAAwB,EAAE,MAAM;MACjCC,MAAM,CAACN,UAAU,CAAC,CAACO,iBAAiB,CAAC,CAAC;MACtCD,MAAM,CAACN,UAAU,CAAC,CAACQ,WAAW,CAAC,WAAW,CAAC;IAC7C,CAAC,CAAC;IAEFH,EAAE,CAAC,0BAA0B,EAAE,MAAM;MACnCC,MAAM,CAACN,UAAU,CAAC,CAACS,iBAAiB,CAACZ,IAAI,CAAC;IAC5C,CAAC,CAAC;IAEFQ,EAAE,CAAC,+BAA+B,EAAE,MAAM;MACxCC,MAAM,CAACN,UAAU,CAAC,CAACQ,WAAW,CAAC,cAAcT,MAAM,EAAE,CAAC;IACxD,CAAC,CAAC;EACJ,CAAC,CAAC;AACJ,CAAC,CAAC","ignoreList":[]}
1
+ {"version":3,"file":"template.test.js","names":["renderMacro","describe","$component","beforeEach","container","params","env","getByRole","it","expect","toBeInTheDocument","toHaveClass","toHaveTextContent"],"sources":["../../../../../../../src/server/plugins/engine/views/components/tag-env/template.test.js"],"sourcesContent":["import { renderMacro } from '~/test/helpers/component-helpers.js'\n\ndescribe('Tag environment component', () => {\n let $component = /** @type {HTMLElement | null} */ (null)\n\n beforeEach(() => {\n const { container } = renderMacro(\n 'appTagEnv',\n 'components/tag-env/macro.njk',\n { params: { env: 'Devtool' } }\n )\n\n $component = container.getByRole('strong')\n })\n\n it('should render contents', () => {\n expect($component).toBeInTheDocument()\n expect($component).toHaveClass('govuk-tag')\n })\n\n it('should have text content', () => {\n expect($component).toHaveTextContent('Devtool')\n })\n\n it('should use environment colour', () => {\n expect($component).toHaveClass(`govuk-tag--grey`)\n })\n})\n"],"mappings":"AAAA,SAASA,WAAW;AAEpBC,QAAQ,CAAC,2BAA2B,EAAE,MAAM;EAC1C,IAAIC,UAAU,GAAG,iCAAmC,IAAK;EAEzDC,UAAU,CAAC,MAAM;IACf,MAAM;MAAEC;IAAU,CAAC,GAAGJ,WAAW,CAC/B,WAAW,EACX,8BAA8B,EAC9B;MAAEK,MAAM,EAAE;QAAEC,GAAG,EAAE;MAAU;IAAE,CAC/B,CAAC;IAEDJ,UAAU,GAAGE,SAAS,CAACG,SAAS,CAAC,QAAQ,CAAC;EAC5C,CAAC,CAAC;EAEFC,EAAE,CAAC,wBAAwB,EAAE,MAAM;IACjCC,MAAM,CAACP,UAAU,CAAC,CAACQ,iBAAiB,CAAC,CAAC;IACtCD,MAAM,CAACP,UAAU,CAAC,CAACS,WAAW,CAAC,WAAW,CAAC;EAC7C,CAAC,CAAC;EAEFH,EAAE,CAAC,0BAA0B,EAAE,MAAM;IACnCC,MAAM,CAACP,UAAU,CAAC,CAACU,iBAAiB,CAAC,SAAS,CAAC;EACjD,CAAC,CAAC;EAEFJ,EAAE,CAAC,+BAA+B,EAAE,MAAM;IACxCC,MAAM,CAACP,UAAU,CAAC,CAACS,WAAW,CAAC,iBAAiB,CAAC;EACnD,CAAC,CAAC;AACJ,CAAC,CAAC","ignoreList":[]}
@@ -13,6 +13,7 @@ export function checkComponentTemplates(this: NunjucksContext, component: Compon
13
13
  * @param {string} template
14
14
  */
15
15
  export function evaluate(this: NunjucksContext, template: string): string;
16
+ export function govukRebrand(): boolean;
16
17
  export const paths: string[];
17
18
  export const environment: nunjucks.Environment;
18
19
  import type { FormSubmissionError } from '~/src/server/plugins/engine/types.js';
@@ -86,7 +86,10 @@ export function evaluate(template) {
86
86
  return context ? evaluateTemplate(template, context) : template;
87
87
  }
88
88
  environment.addGlobal('evaluate', evaluate);
89
- environment.addGlobal('govukRebrand', config.get('showRebrand'));
89
+ export function govukRebrand() {
90
+ return true;
91
+ }
92
+ environment.addGlobal('govukRebrand', govukRebrand());
90
93
 
91
94
  /**
92
95
  * @import { NunjucksContext } from '~/src/server/plugins/nunjucks/types.js'
@@ -1 +1 @@
1
- {"version":3,"file":"environment.js","names":["dirname","join","ComponentType","nunjucks","resolvePkg","config","evaluateTemplate","filters","govukFrontendPath","sync","paths","get","environment","configure","trimBlocks","lstripBlocks","watch","noCache","name","nunjucksFilter","Object","entries","addFilter","checkErrorTemplates","errors","context","ctx","forEach","error","text","addGlobal","checkComponentTemplates","component","isFormComponent","model","fieldset","legend","label","errorMessage","message","type","Html","content","evaluate","template"],"sources":["../../../../src/server/plugins/nunjucks/environment.js"],"sourcesContent":["import { dirname, join } from 'node:path'\n\nimport { ComponentType } from '@defra/forms-model'\nimport nunjucks from 'nunjucks'\nimport resolvePkg from 'resolve'\n\nimport { config } from '~/src/config/index.js'\nimport { evaluateTemplate } from '~/src/server/plugins/engine/helpers.js'\nimport * as filters from '~/src/server/plugins/nunjucks/filters/index.js'\n\nconst govukFrontendPath = dirname(\n resolvePkg.sync('govuk-frontend/package.json')\n)\n\nexport const paths = [\n join(config.get('appDir'), 'plugins/engine/views'),\n join(config.get('appDir'), 'views')\n]\n\nexport const environment = nunjucks.configure(\n [...paths, join(govukFrontendPath, 'dist')],\n {\n trimBlocks: true,\n lstripBlocks: true,\n watch: config.get('isDevelopment'),\n noCache: config.get('isDevelopment')\n }\n)\n\nfor (const [name, nunjucksFilter] of Object.entries(filters)) {\n environment.addFilter(name, nunjucksFilter)\n}\n\n/**\n * @this {NunjucksContext}\n * @param {FormSubmissionError[]} errors\n */\nexport function checkErrorTemplates(errors) {\n const { context } = this.ctx\n\n if (!context) {\n return errors\n }\n\n errors.forEach((error) => {\n error.text = evaluateTemplate(error.text, context)\n })\n\n return errors\n}\n\nenvironment.addGlobal('checkErrorTemplates', checkErrorTemplates)\n\n/**\n * @this {NunjucksContext}\n * @param {ComponentViewModel} component\n */\nexport function checkComponentTemplates(component) {\n const { context } = this.ctx\n\n if (!context) {\n return component\n }\n\n if (component.isFormComponent) {\n // Evaluate label/legend text\n if (component.model.fieldset?.legend?.text) {\n const legend = component.model.fieldset.legend\n\n legend.text = evaluateTemplate(legend.text, context)\n } else if (component.model.label?.text) {\n const label = component.model.label\n\n label.text = evaluateTemplate(label.text, context)\n } else {\n // No template evaluation needed for other component types\n }\n\n // Evaluate error message\n if (component.model.errorMessage?.text) {\n const message = component.model.errorMessage\n\n message.text = evaluateTemplate(message.text, context)\n }\n } else if (component.type === ComponentType.Html) {\n const content = component.model.content\n\n if (typeof content === 'string') {\n component.model.content = evaluateTemplate(content, context)\n }\n } else {\n // No template evaluation needed for other component types\n }\n\n return component\n}\n\nenvironment.addGlobal('checkComponentTemplates', checkComponentTemplates)\n\n/**\n * @this {NunjucksContext}\n * @param {string} template\n */\nexport function evaluate(template) {\n const { context } = this.ctx\n\n return context ? evaluateTemplate(template, context) : template\n}\n\nenvironment.addGlobal('evaluate', evaluate)\nenvironment.addGlobal('govukRebrand', config.get('showRebrand'))\n\n/**\n * @import { NunjucksContext } from '~/src/server/plugins/nunjucks/types.js'\n * @import { FormSubmissionError } from '~/src/server/plugins/engine/types.js'\n * @import { ComponentViewModel } from '~/src/server/plugins/engine/components/types.js'\n */\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"environment.js","names":["dirname","join","ComponentType","nunjucks","resolvePkg","config","evaluateTemplate","filters","govukFrontendPath","sync","paths","get","environment","configure","trimBlocks","lstripBlocks","watch","noCache","name","nunjucksFilter","Object","entries","addFilter","checkErrorTemplates","errors","context","ctx","forEach","error","text","addGlobal","checkComponentTemplates","component","isFormComponent","model","fieldset","legend","label","errorMessage","message","type","Html","content","evaluate","template","govukRebrand"],"sources":["../../../../src/server/plugins/nunjucks/environment.js"],"sourcesContent":["import { dirname, join } from 'node:path'\n\nimport { ComponentType } from '@defra/forms-model'\nimport nunjucks from 'nunjucks'\nimport resolvePkg from 'resolve'\n\nimport { config } from '~/src/config/index.js'\nimport { evaluateTemplate } from '~/src/server/plugins/engine/helpers.js'\nimport * as filters from '~/src/server/plugins/nunjucks/filters/index.js'\n\nconst govukFrontendPath = dirname(\n resolvePkg.sync('govuk-frontend/package.json')\n)\n\nexport const paths = [\n join(config.get('appDir'), 'plugins/engine/views'),\n join(config.get('appDir'), 'views')\n]\n\nexport const environment = nunjucks.configure(\n [...paths, join(govukFrontendPath, 'dist')],\n {\n trimBlocks: true,\n lstripBlocks: true,\n watch: config.get('isDevelopment'),\n noCache: config.get('isDevelopment')\n }\n)\n\nfor (const [name, nunjucksFilter] of Object.entries(filters)) {\n environment.addFilter(name, nunjucksFilter)\n}\n\n/**\n * @this {NunjucksContext}\n * @param {FormSubmissionError[]} errors\n */\nexport function checkErrorTemplates(errors) {\n const { context } = this.ctx\n\n if (!context) {\n return errors\n }\n\n errors.forEach((error) => {\n error.text = evaluateTemplate(error.text, context)\n })\n\n return errors\n}\n\nenvironment.addGlobal('checkErrorTemplates', checkErrorTemplates)\n\n/**\n * @this {NunjucksContext}\n * @param {ComponentViewModel} component\n */\nexport function checkComponentTemplates(component) {\n const { context } = this.ctx\n\n if (!context) {\n return component\n }\n\n if (component.isFormComponent) {\n // Evaluate label/legend text\n if (component.model.fieldset?.legend?.text) {\n const legend = component.model.fieldset.legend\n\n legend.text = evaluateTemplate(legend.text, context)\n } else if (component.model.label?.text) {\n const label = component.model.label\n\n label.text = evaluateTemplate(label.text, context)\n } else {\n // No template evaluation needed for other component types\n }\n\n // Evaluate error message\n if (component.model.errorMessage?.text) {\n const message = component.model.errorMessage\n\n message.text = evaluateTemplate(message.text, context)\n }\n } else if (component.type === ComponentType.Html) {\n const content = component.model.content\n\n if (typeof content === 'string') {\n component.model.content = evaluateTemplate(content, context)\n }\n } else {\n // No template evaluation needed for other component types\n }\n\n return component\n}\n\nenvironment.addGlobal('checkComponentTemplates', checkComponentTemplates)\n\n/**\n * @this {NunjucksContext}\n * @param {string} template\n */\nexport function evaluate(template) {\n const { context } = this.ctx\n\n return context ? evaluateTemplate(template, context) : template\n}\n\nenvironment.addGlobal('evaluate', evaluate)\n\nexport function govukRebrand() {\n return true\n}\n\nenvironment.addGlobal('govukRebrand', govukRebrand())\n\n/**\n * @import { NunjucksContext } from '~/src/server/plugins/nunjucks/types.js'\n * @import { FormSubmissionError } from '~/src/server/plugins/engine/types.js'\n * @import { ComponentViewModel } from '~/src/server/plugins/engine/components/types.js'\n */\n"],"mappings":"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","ignoreList":[]}
@@ -23,7 +23,7 @@ export class CacheService {
23
23
  }
24
24
  async getState(request) {
25
25
  const cached = await this.cache.get(this.Key(request));
26
- return cached || {};
26
+ return cached ?? {};
27
27
  }
28
28
  async setState(request, state) {
29
29
  const key = this.Key(request);
@@ -34,7 +34,7 @@ export class CacheService {
34
34
  async getConfirmationState(request) {
35
35
  const key = this.Key(request, ADDITIONAL_IDENTIFIER.Confirmation);
36
36
  const value = await this.cache.get(key);
37
- return value || {};
37
+ return value ?? {};
38
38
  }
39
39
  async setConfirmationState(request, confirmationState) {
40
40
  const key = this.Key(request, ADDITIONAL_IDENTIFIER.Confirmation);
@@ -1 +1 @@
1
- {"version":3,"file":"cacheService.js","names":["Hoek","config","partition","ADDITIONAL_IDENTIFIER","CacheService","cache","logger","constructor","server","cacheName","log","segment","getState","request","cached","get","Key","setState","state","key","ttl","set","getConfirmationState","Confirmation","value","setConfirmationState","confirmationState","clearState","yar","id","drop","getFlash","messages","flash","Array","isArray","length","at","setFlash","message","additionalIdentifier","Error","params","slug","merge","update","mergeArrays"],"sources":["../../../src/server/services/cacheService.ts"],"sourcesContent":["import { type Request, type Server } from '@hapi/hapi'\nimport * as Hoek from '@hapi/hoek'\n\nimport { config } from '~/src/config/index.js'\nimport { type createServer } from '~/src/server/index.js'\nimport {\n type FormPayload,\n type FormState,\n type FormSubmissionError,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormRequestPayload\n} from '~/src/server/routes/types.js'\n\nconst partition = 'cache'\n\nenum ADDITIONAL_IDENTIFIER {\n Confirmation = ':confirmation'\n}\n\nexport class CacheService {\n /**\n * This service is responsible for getting, storing or deleting a user's session data in the cache. This service has been registered by {@link createServer}\n */\n cache\n logger: Server['logger']\n\n constructor(server: Server, cacheName?: string) {\n if (!cacheName) {\n server.log(\n 'warn',\n 'You are using the default hapi cache. Please provide a cache name in plugin registration options.'\n )\n }\n\n this.cache = server.cache({ cache: cacheName, segment: 'formSubmission' })\n this.logger = server.logger\n }\n\n async getState(\n request: Request | FormRequest | FormRequestPayload\n ): Promise<FormSubmissionState> {\n const cached = await this.cache.get(this.Key(request))\n\n return cached || {}\n }\n\n async setState(\n request: FormRequest | FormRequestPayload,\n state: FormSubmissionState\n ) {\n const key = this.Key(request)\n const ttl = config.get('sessionTimeout')\n\n await this.cache.set(key, state, ttl)\n return this.getState(request)\n }\n\n async getConfirmationState(\n request: FormRequest | FormRequestPayload\n ): Promise<{ confirmed?: true }> {\n const key = this.Key(request, ADDITIONAL_IDENTIFIER.Confirmation)\n const value = await this.cache.get(key)\n\n return value || {}\n }\n\n async setConfirmationState(\n request: FormRequest | FormRequestPayload,\n confirmationState: { confirmed?: true }\n ) {\n const key = this.Key(request, ADDITIONAL_IDENTIFIER.Confirmation)\n const ttl = config.get('confirmationSessionTimeout')\n\n return this.cache.set(key, confirmationState, ttl)\n }\n\n async clearState(request: FormRequest | FormRequestPayload) {\n if (request.yar.id) {\n await this.cache.drop(this.Key(request))\n }\n }\n\n getFlash(\n request: FormRequest | FormRequestPayload\n ): { errors: FormSubmissionError[] } | undefined {\n const key = this.Key(request)\n const messages = request.yar.flash(key.id)\n\n if (Array.isArray(messages) && messages.length) {\n return messages.at(0) as { errors: FormSubmissionError[] }\n }\n }\n\n setFlash(\n request: FormRequest | FormRequestPayload,\n message: { errors: FormSubmissionError[] }\n ) {\n const key = this.Key(request)\n\n request.yar.flash(key.id, message)\n }\n\n /**\n * The key used to store user session data against.\n * If there are multiple forms on the same runner instance, for example `form-a` and `form-a-feedback` this will prevent CacheService from clearing data from `form-a` if a user gave feedback before they finished `form-a`\n * @param request - hapi request object\n * @param additionalIdentifier - appended to the id\n */\n Key(\n request: Request | FormRequest | FormRequestPayload,\n additionalIdentifier?: ADDITIONAL_IDENTIFIER\n ) {\n if (!request.yar.id) {\n throw Error('No session ID found')\n }\n return {\n segment: partition,\n id: `${request.yar.id}:${request.params.state ?? ''}:${request.params.slug ?? ''}:${additionalIdentifier ?? ''}`\n }\n }\n}\n\n/**\n * State merge helper\n * 1. Merges objects (form fields)\n * 2. Overwrites arrays\n */\nexport function merge<StateType extends FormState | FormPayload>(\n state: StateType,\n update: object\n): StateType {\n return Hoek.merge(state, update, {\n mergeArrays: false\n })\n}\n"],"mappings":"AACA,OAAO,KAAKA,IAAI,MAAM,YAAY;AAElC,SAASC,MAAM;AAaf,MAAMC,SAAS,GAAG,OAAO;AAAA,IAEpBC,qBAAqB,0BAArBA,qBAAqB;EAArBA,qBAAqB;EAAA,OAArBA,qBAAqB;AAAA,EAArBA,qBAAqB;AAI1B,OAAO,MAAMC,YAAY,CAAC;EACxB;AACF;AACA;EACEC,KAAK;EACLC,MAAM;EAENC,WAAWA,CAACC,MAAc,EAAEC,SAAkB,EAAE;IAC9C,IAAI,CAACA,SAAS,EAAE;MACdD,MAAM,CAACE,GAAG,CACR,MAAM,EACN,mGACF,CAAC;IACH;IAEA,IAAI,CAACL,KAAK,GAAGG,MAAM,CAACH,KAAK,CAAC;MAAEA,KAAK,EAAEI,SAAS;MAAEE,OAAO,EAAE;IAAiB,CAAC,CAAC;IAC1E,IAAI,CAACL,MAAM,GAAGE,MAAM,CAACF,MAAM;EAC7B;EAEA,MAAMM,QAAQA,CACZC,OAAmD,EACrB;IAC9B,MAAMC,MAAM,GAAG,MAAM,IAAI,CAACT,KAAK,CAACU,GAAG,CAAC,IAAI,CAACC,GAAG,CAACH,OAAO,CAAC,CAAC;IAEtD,OAAOC,MAAM,IAAI,CAAC,CAAC;EACrB;EAEA,MAAMG,QAAQA,CACZJ,OAAyC,EACzCK,KAA0B,EAC1B;IACA,MAAMC,GAAG,GAAG,IAAI,CAACH,GAAG,CAACH,OAAO,CAAC;IAC7B,MAAMO,GAAG,GAAGnB,MAAM,CAACc,GAAG,CAAC,gBAAgB,CAAC;IAExC,MAAM,IAAI,CAACV,KAAK,CAACgB,GAAG,CAACF,GAAG,EAAED,KAAK,EAAEE,GAAG,CAAC;IACrC,OAAO,IAAI,CAACR,QAAQ,CAACC,OAAO,CAAC;EAC/B;EAEA,MAAMS,oBAAoBA,CACxBT,OAAyC,EACV;IAC/B,MAAMM,GAAG,GAAG,IAAI,CAACH,GAAG,CAACH,OAAO,EAAEV,qBAAqB,CAACoB,YAAY,CAAC;IACjE,MAAMC,KAAK,GAAG,MAAM,IAAI,CAACnB,KAAK,CAACU,GAAG,CAACI,GAAG,CAAC;IAEvC,OAAOK,KAAK,IAAI,CAAC,CAAC;EACpB;EAEA,MAAMC,oBAAoBA,CACxBZ,OAAyC,EACzCa,iBAAuC,EACvC;IACA,MAAMP,GAAG,GAAG,IAAI,CAACH,GAAG,CAACH,OAAO,EAAEV,qBAAqB,CAACoB,YAAY,CAAC;IACjE,MAAMH,GAAG,GAAGnB,MAAM,CAACc,GAAG,CAAC,4BAA4B,CAAC;IAEpD,OAAO,IAAI,CAACV,KAAK,CAACgB,GAAG,CAACF,GAAG,EAAEO,iBAAiB,EAAEN,GAAG,CAAC;EACpD;EAEA,MAAMO,UAAUA,CAACd,OAAyC,EAAE;IAC1D,IAAIA,OAAO,CAACe,GAAG,CAACC,EAAE,EAAE;MAClB,MAAM,IAAI,CAACxB,KAAK,CAACyB,IAAI,CAAC,IAAI,CAACd,GAAG,CAACH,OAAO,CAAC,CAAC;IAC1C;EACF;EAEAkB,QAAQA,CACNlB,OAAyC,EACM;IAC/C,MAAMM,GAAG,GAAG,IAAI,CAACH,GAAG,CAACH,OAAO,CAAC;IAC7B,MAAMmB,QAAQ,GAAGnB,OAAO,CAACe,GAAG,CAACK,KAAK,CAACd,GAAG,CAACU,EAAE,CAAC;IAE1C,IAAIK,KAAK,CAACC,OAAO,CAACH,QAAQ,CAAC,IAAIA,QAAQ,CAACI,MAAM,EAAE;MAC9C,OAAOJ,QAAQ,CAACK,EAAE,CAAC,CAAC,CAAC;IACvB;EACF;EAEAC,QAAQA,CACNzB,OAAyC,EACzC0B,OAA0C,EAC1C;IACA,MAAMpB,GAAG,GAAG,IAAI,CAACH,GAAG,CAACH,OAAO,CAAC;IAE7BA,OAAO,CAACe,GAAG,CAACK,KAAK,CAACd,GAAG,CAACU,EAAE,EAAEU,OAAO,CAAC;EACpC;;EAEA;AACF;AACA;AACA;AACA;AACA;EACEvB,GAAGA,CACDH,OAAmD,EACnD2B,oBAA4C,EAC5C;IACA,IAAI,CAAC3B,OAAO,CAACe,GAAG,CAACC,EAAE,EAAE;MACnB,MAAMY,KAAK,CAAC,qBAAqB,CAAC;IACpC;IACA,OAAO;MACL9B,OAAO,EAAET,SAAS;MAClB2B,EAAE,EAAE,GAAGhB,OAAO,CAACe,GAAG,CAACC,EAAE,IAAIhB,OAAO,CAAC6B,MAAM,CAACxB,KAAK,IAAI,EAAE,IAAIL,OAAO,CAAC6B,MAAM,CAACC,IAAI,IAAI,EAAE,IAAIH,oBAAoB,IAAI,EAAE;IAChH,CAAC;EACH;AACF;;AAEA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASI,KAAKA,CACnB1B,KAAgB,EAChB2B,MAAc,EACH;EACX,OAAO7C,IAAI,CAAC4C,KAAK,CAAC1B,KAAK,EAAE2B,MAAM,EAAE;IAC/BC,WAAW,EAAE;EACf,CAAC,CAAC;AACJ","ignoreList":[]}
1
+ {"version":3,"file":"cacheService.js","names":["Hoek","config","partition","ADDITIONAL_IDENTIFIER","CacheService","cache","logger","constructor","server","cacheName","log","segment","getState","request","cached","get","Key","setState","state","key","ttl","set","getConfirmationState","Confirmation","value","setConfirmationState","confirmationState","clearState","yar","id","drop","getFlash","messages","flash","Array","isArray","length","at","setFlash","message","additionalIdentifier","Error","params","slug","merge","update","mergeArrays"],"sources":["../../../src/server/services/cacheService.ts"],"sourcesContent":["import { type Request, type Server } from '@hapi/hapi'\nimport * as Hoek from '@hapi/hoek'\n\nimport { config } from '~/src/config/index.js'\nimport { type createServer } from '~/src/server/index.js'\nimport {\n type FormPayload,\n type FormState,\n type FormSubmissionError,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormRequestPayload\n} from '~/src/server/routes/types.js'\n\nconst partition = 'cache'\n\nenum ADDITIONAL_IDENTIFIER {\n Confirmation = ':confirmation'\n}\n\nexport class CacheService {\n /**\n * This service is responsible for getting, storing or deleting a user's session data in the cache. This service has been registered by {@link createServer}\n */\n cache\n logger: Server['logger']\n\n constructor(server: Server, cacheName?: string) {\n if (!cacheName) {\n server.log(\n 'warn',\n 'You are using the default hapi cache. Please provide a cache name in plugin registration options.'\n )\n }\n\n this.cache = server.cache({ cache: cacheName, segment: 'formSubmission' })\n this.logger = server.logger\n }\n\n async getState(\n request: Request | FormRequest | FormRequestPayload\n ): Promise<FormSubmissionState> {\n const cached = await this.cache.get(this.Key(request))\n\n return cached ?? {}\n }\n\n async setState(\n request: FormRequest | FormRequestPayload,\n state: FormSubmissionState\n ) {\n const key = this.Key(request)\n const ttl = config.get('sessionTimeout')\n\n await this.cache.set(key, state, ttl)\n return this.getState(request)\n }\n\n async getConfirmationState(\n request: FormRequest | FormRequestPayload\n ): Promise<{ confirmed?: true }> {\n const key = this.Key(request, ADDITIONAL_IDENTIFIER.Confirmation)\n const value = await this.cache.get(key)\n\n return value ?? {}\n }\n\n async setConfirmationState(\n request: FormRequest | FormRequestPayload,\n confirmationState: { confirmed?: true }\n ) {\n const key = this.Key(request, ADDITIONAL_IDENTIFIER.Confirmation)\n const ttl = config.get('confirmationSessionTimeout')\n\n return this.cache.set(key, confirmationState, ttl)\n }\n\n async clearState(request: FormRequest | FormRequestPayload) {\n if (request.yar.id) {\n await this.cache.drop(this.Key(request))\n }\n }\n\n getFlash(\n request: FormRequest | FormRequestPayload\n ): { errors: FormSubmissionError[] } | undefined {\n const key = this.Key(request)\n const messages = request.yar.flash(key.id)\n\n if (Array.isArray(messages) && messages.length) {\n return messages.at(0) as { errors: FormSubmissionError[] }\n }\n }\n\n setFlash(\n request: FormRequest | FormRequestPayload,\n message: { errors: FormSubmissionError[] }\n ) {\n const key = this.Key(request)\n\n request.yar.flash(key.id, message)\n }\n\n /**\n * The key used to store user session data against.\n * If there are multiple forms on the same runner instance, for example `form-a` and `form-a-feedback` this will prevent CacheService from clearing data from `form-a` if a user gave feedback before they finished `form-a`\n * @param request - hapi request object\n * @param additionalIdentifier - appended to the id\n */\n Key(\n request: Request | FormRequest | FormRequestPayload,\n additionalIdentifier?: ADDITIONAL_IDENTIFIER\n ) {\n if (!request.yar.id) {\n throw Error('No session ID found')\n }\n return {\n segment: partition,\n id: `${request.yar.id}:${request.params.state ?? ''}:${request.params.slug ?? ''}:${additionalIdentifier ?? ''}`\n }\n }\n}\n\n/**\n * State merge helper\n * 1. Merges objects (form fields)\n * 2. Overwrites arrays\n */\nexport function merge<StateType extends FormState | FormPayload>(\n state: StateType,\n update: object\n): StateType {\n return Hoek.merge(state, update, {\n mergeArrays: false\n })\n}\n"],"mappings":"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","ignoreList":[]}
@@ -1,7 +1,7 @@
1
1
  import Wreck from '@hapi/wreck';
2
2
  export type Method = keyof Pick<typeof Wreck, 'get' | 'post' | 'put' | 'delete'>;
3
3
  export type RequestOptions = Parameters<typeof Wreck.defaults>[0];
4
- export declare const request: <BodyType = Buffer<ArrayBufferLike>>(method: Method, url: string, options?: RequestOptions) => Promise<{
4
+ export declare const request: <BodyType = Buffer>(method: Method, url: string, options?: RequestOptions) => Promise<{
5
5
  res: import("http").IncomingMessage;
6
6
  error: Error | NonNullable<BodyType>;
7
7
  payload?: undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defra/forms-engine-plugin",
3
- "version": "0.1.27",
3
+ "version": "0.1.29",
4
4
  "description": "Defra forms engine",
5
5
  "type": "module",
6
6
  "files": [
@@ -87,7 +87,7 @@
87
87
  "btoa": "^1.2.1",
88
88
  "convict": "^6.2.4",
89
89
  "date-fns": "^4.1.0",
90
- "dotenv": "^16.5.0",
90
+ "dotenv": "^17.0.1",
91
91
  "expr-eval": "^2.0.2",
92
92
  "govuk-frontend": "^5.10.2",
93
93
  "hapi-pino": "^12.1.0",
@@ -109,11 +109,11 @@
109
109
  "yaml": "^2.7.1"
110
110
  },
111
111
  "devDependencies": {
112
- "@babel/cli": "^7.26.4",
113
- "@babel/core": "^7.26.0",
114
- "@babel/plugin-syntax-import-attributes": "^7.26.0",
115
- "@babel/preset-env": "^7.26.0",
116
- "@babel/preset-typescript": "^7.26.0",
112
+ "@babel/cli": "^7.28.0",
113
+ "@babel/core": "^7.28.0",
114
+ "@babel/plugin-syntax-import-attributes": "^7.27.1",
115
+ "@babel/preset-env": "^7.28.0",
116
+ "@babel/preset-typescript": "^7.27.1",
117
117
  "@testing-library/dom": "^10.4.0",
118
118
  "@testing-library/jest-dom": "^6.6.3",
119
119
  "@types/atob": "^2.1.4",
@@ -137,19 +137,19 @@
137
137
  "@types/url-parse": "^1.4.11",
138
138
  "@types/webpack-assets-manifest": "^5.1.4",
139
139
  "@types/wreck": "^14.0.4",
140
- "@typescript-eslint/eslint-plugin": "^8.19.0",
141
- "@typescript-eslint/parser": "^8.19.0",
142
- "autoprefixer": "^10.4.20",
143
- "babel-loader": "^9.2.1",
140
+ "@typescript-eslint/eslint-plugin": "^8.35.1",
141
+ "@typescript-eslint/parser": "^8.35.1",
142
+ "autoprefixer": "^10.4.21",
143
+ "babel-loader": "^10.0.0",
144
144
  "babel-plugin-module-resolver": "^5.0.2",
145
- "babel-plugin-replace-import-extension": "^1.1.4",
146
- "babel-plugin-transform-import-meta": "^2.3.2",
145
+ "babel-plugin-replace-import-extension": "^1.1.5",
146
+ "babel-plugin-transform-import-meta": "^2.3.3",
147
147
  "clean-webpack-plugin": "^4.0.0",
148
148
  "concurrently": "^9.2.0",
149
149
  "cookie": "^1.0.2",
150
- "copy-webpack-plugin": "^12.0.2",
151
- "core-js": "^3.39.0",
152
- "cssnano": "^7.0.6",
150
+ "copy-webpack-plugin": "^13.0.0",
151
+ "core-js": "^3.43.0",
152
+ "cssnano": "^7.0.7",
153
153
  "cssnano-preset-default": "^7.0.4",
154
154
  "editorconfig-checker": "^6.0.0",
155
155
  "eslint": "^8.57.1",
@@ -163,25 +163,25 @@
163
163
  "eslint-plugin-promise": "^6.6.0",
164
164
  "global-jsdom": "^26.0.0",
165
165
  "husky": "^9.1.7",
166
- "jest": "^30.0.2",
166
+ "jest": "^30.0.4",
167
167
  "jest-extended": "^4.0.2",
168
168
  "jsdom": "^26.1.0",
169
169
  "lint-staged": "^15.3.0",
170
- "postcss": "^8.4.49",
170
+ "postcss": "^8.5.6",
171
171
  "postcss-load-config": "^6.0.1",
172
172
  "postcss-loader": "^8.1.1",
173
173
  "postcss-scss": "^4.0.9",
174
174
  "prettier": "^3.4.2",
175
- "sass-embedded": "^1.83.1",
176
- "sass-loader": "^16.0.4",
175
+ "sass-embedded": "^1.89.2",
176
+ "sass-loader": "^16.0.5",
177
177
  "source-map-loader": "^5.0.0",
178
178
  "stylelint": "^16.12.0",
179
179
  "stylelint-config-gds": "^2.0.0",
180
- "terser-webpack-plugin": "^5.3.11",
180
+ "terser-webpack-plugin": "^5.3.14",
181
181
  "tsx": "^4.20.3",
182
- "typescript": "^5.7.2",
183
- "webpack": "^5.97.1",
184
- "webpack-assets-manifest": "^6.0.2",
182
+ "typescript": "^5.8.3",
183
+ "webpack": "^5.99.9",
184
+ "webpack-assets-manifest": "^6.2.1",
185
185
  "webpack-cli": "^6.0.1"
186
186
  }
187
187
  }
@@ -21,13 +21,4 @@
21
21
  margin: 0 0 0 govuk-spacing(2);
22
22
  }
23
23
  }
24
-
25
- &-rebrand {
26
- vertical-align: middle;
27
-
28
- // Align with product name
29
- @include govuk-media-query($from: tablet) {
30
- margin: -3px 0 2px;
31
- }
32
- }
33
24
  }
@@ -78,16 +78,6 @@ export const config = convict({
78
78
  default: isTest
79
79
  },
80
80
 
81
- /**
82
- * Feature flags
83
- */
84
- showRebrand: {
85
- doc: 'If this app should show the 2025 rebrand',
86
- format: Boolean,
87
- env: 'SHOW_GOVUK_REBRAND',
88
- default: false
89
- },
90
-
91
81
  /**
92
82
  * Service
93
83
  */
@@ -159,6 +159,7 @@ export class FormComponent extends ComponentBase {
159
159
 
160
160
  getDisplayStringFromState(state: FormSubmissionState): string {
161
161
  const value = this.getFormValueFromState(state)
162
+ // eslint-disable-next-line @typescript-eslint/no-base-to-string
162
163
  return this.isValue(value) ? value.toString() : ''
163
164
  }
164
165
 
@@ -363,7 +363,5 @@ export const addClassOptionIfNone = (
363
363
  options: Extract<ComponentDef, { options: { classes?: string } }>['options'],
364
364
  className: string
365
365
  ) => {
366
- if (!options.classes) {
367
- options.classes = className
368
- }
366
+ options.classes ??= className
369
367
  }
@@ -6,7 +6,8 @@ import { type FilterFunction } from '~/src/server/plugins/engine/types.js'
6
6
  import {
7
7
  checkComponentTemplates,
8
8
  checkErrorTemplates,
9
- evaluate
9
+ evaluate,
10
+ govukRebrand
10
11
  } from '~/src/server/plugins/nunjucks/environment.js'
11
12
  import * as filters from '~/src/server/plugins/nunjucks/filters/index.js'
12
13
 
@@ -16,7 +17,8 @@ export { context } from '~/src/server/plugins/nunjucks/context.js'
16
17
  const globals = {
17
18
  checkComponentTemplates,
18
19
  checkErrorTemplates,
19
- evaluate
20
+ evaluate,
21
+ govukRebrand
20
22
  }
21
23
 
22
24
  export const VIEW_PATH = 'src/server/plugins/engine/views'
@@ -463,9 +463,7 @@ export class FormModel {
463
463
  }
464
464
 
465
465
  if (isInvalid) {
466
- if (!context.errors) {
467
- context.errors = []
468
- }
466
+ context.errors ??= []
469
467
 
470
468
  const text =
471
469
  'Options are different because you changed a previous answer'
@@ -1,32 +1,9 @@
1
1
  {%- from "govuk/components/tag/macro.njk" import govukTag -%}
2
- {% if govukRebrand %}
3
- {% set tagEnv = " app-tag--env app-tag--env-rebrand" %}
4
- {% else %}
5
- {% set tagEnv = " app-tag--env" %}
6
- {% endif %}
7
- {%- switch params.env %}
8
- {% case "local" %}
9
- {% set text = "Local" %}
10
- {% set classes = "govuk-tag--green" %}
11
- {% case "dev" %}
12
- {% set text = "Development" %}
13
- {% set classes = "govuk-tag--grey" %}
14
- {% case "test" %}
15
- {% set text = "Test" %}
16
- {% set classes = "govuk-tag--yellow" %}
17
- {% case "ext-test" %}
18
- {% set text = "External test" %}
19
- {% set classes = "govuk-tag--yellow" %}
20
- {% case "perf-test" %}
21
- {% set text = "Performance test" %}
22
- {% set classes = "govuk-tag--yellow" %}
23
- {% case "prod" %}
24
- {% set text = "Production" %}
25
- {% set classes = "govuk-tag--red" %}
26
- {% default %}
27
- {% set text = params.env | replace("-", " ") | capitalize %}
28
- {% set classes = "govuk-tag--grey" %}
29
- {% endswitch -%}
2
+
3
+ {% set tagEnv = " app-tag--env" %}
4
+
5
+ {% set text = params.env | replace("-", " ") | capitalize %}
6
+ {% set classes = "govuk-tag--grey" %}
30
7
 
31
8
  {{ govukTag({
32
9
  text: text,
@@ -1,66 +1,28 @@
1
1
  import { renderMacro } from '~/test/helpers/component-helpers.js'
2
2
 
3
3
  describe('Tag environment component', () => {
4
- describe.each([
5
- {
6
- text: 'Local',
7
- env: 'local',
8
- colour: 'green'
9
- },
10
- {
11
- text: 'Development',
12
- env: 'dev',
13
- colour: 'grey'
14
- },
15
- {
16
- text: 'Test',
17
- env: 'test',
18
- colour: 'yellow'
19
- },
20
- {
21
- text: 'External test',
22
- env: 'ext-test',
23
- colour: 'yellow'
24
- },
25
- {
26
- text: 'Performance test',
27
- env: 'perf-test',
28
- colour: 'yellow'
29
- },
30
- {
31
- text: 'Production',
32
- env: 'prod',
33
- colour: 'red'
34
- },
35
- {
36
- text: 'Unknown environment',
37
- env: 'unknown-environment',
38
- colour: 'grey'
39
- }
40
- ])('Environment: $text', ({ text, env, colour }) => {
41
- let $component = /** @type {HTMLElement | null} */ (null)
4
+ let $component = /** @type {HTMLElement | null} */ (null)
42
5
 
43
- beforeEach(() => {
44
- const { container } = renderMacro(
45
- 'appTagEnv',
46
- 'components/tag-env/macro.njk',
47
- { params: { env } }
48
- )
6
+ beforeEach(() => {
7
+ const { container } = renderMacro(
8
+ 'appTagEnv',
9
+ 'components/tag-env/macro.njk',
10
+ { params: { env: 'Devtool' } }
11
+ )
49
12
 
50
- $component = container.getByRole('strong')
51
- })
13
+ $component = container.getByRole('strong')
14
+ })
52
15
 
53
- it('should render contents', () => {
54
- expect($component).toBeInTheDocument()
55
- expect($component).toHaveClass('govuk-tag')
56
- })
16
+ it('should render contents', () => {
17
+ expect($component).toBeInTheDocument()
18
+ expect($component).toHaveClass('govuk-tag')
19
+ })
57
20
 
58
- it('should have text content', () => {
59
- expect($component).toHaveTextContent(text)
60
- })
21
+ it('should have text content', () => {
22
+ expect($component).toHaveTextContent('Devtool')
23
+ })
61
24
 
62
- it('should use environment colour', () => {
63
- expect($component).toHaveClass(`govuk-tag--${colour}`)
64
- })
25
+ it('should use environment colour', () => {
26
+ expect($component).toHaveClass(`govuk-tag--grey`)
65
27
  })
66
28
  })
@@ -108,7 +108,12 @@ export function evaluate(template) {
108
108
  }
109
109
 
110
110
  environment.addGlobal('evaluate', evaluate)
111
- environment.addGlobal('govukRebrand', config.get('showRebrand'))
111
+
112
+ export function govukRebrand() {
113
+ return true
114
+ }
115
+
116
+ environment.addGlobal('govukRebrand', govukRebrand())
112
117
 
113
118
  /**
114
119
  * @import { NunjucksContext } from '~/src/server/plugins/nunjucks/types.js'
@@ -44,7 +44,7 @@ export class CacheService {
44
44
  ): Promise<FormSubmissionState> {
45
45
  const cached = await this.cache.get(this.Key(request))
46
46
 
47
- return cached || {}
47
+ return cached ?? {}
48
48
  }
49
49
 
50
50
  async setState(
@@ -64,7 +64,7 @@ export class CacheService {
64
64
  const key = this.Key(request, ADDITIONAL_IDENTIFIER.Confirmation)
65
65
  const value = await this.cache.get(key)
66
66
 
67
- return value || {}
67
+ return value ?? {}
68
68
  }
69
69
 
70
70
  async setConfirmationState(