@govuk-one-login/frontend-ui 5.1.2 → 5.2.0

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 (65) hide show
  1. package/build/all.css +1 -1
  2. package/build/cjs/backend/__tests__/cookie-banner.test.d.ts +2 -0
  3. package/build/cjs/backend/__tests__/cookie-banner.test.d.ts.map +1 -0
  4. package/build/cjs/backend/__tests__/footer.test.d.ts +2 -0
  5. package/build/cjs/backend/__tests__/footer.test.d.ts.map +1 -0
  6. package/build/cjs/backend/__tests__/header.test.d.ts +2 -0
  7. package/build/cjs/backend/__tests__/header.test.d.ts.map +1 -0
  8. package/build/cjs/backend/__tests__/index.spec.d.ts +2 -0
  9. package/build/cjs/backend/__tests__/index.spec.d.ts.map +1 -0
  10. package/build/cjs/backend/__tests__/language-select.test.d.ts +2 -0
  11. package/build/cjs/backend/__tests__/language-select.test.d.ts.map +1 -0
  12. package/build/cjs/backend/__tests__/logger.spec.d.ts +2 -0
  13. package/build/cjs/backend/__tests__/logger.spec.d.ts.map +1 -0
  14. package/build/cjs/backend/__tests__/mobile-base.test.d.ts +2 -0
  15. package/build/cjs/backend/__tests__/mobile-base.test.d.ts.map +1 -0
  16. package/build/cjs/backend/__tests__/phase-banner.test.d.ts +2 -0
  17. package/build/cjs/backend/__tests__/phase-banner.test.d.ts.map +1 -0
  18. package/build/cjs/backend/__tests__/progress-button.test.d.ts +2 -0
  19. package/build/cjs/backend/__tests__/progress-button.test.d.ts.map +1 -0
  20. package/build/cjs/backend/__tests__/skip-link.test.d.ts +2 -0
  21. package/build/cjs/backend/__tests__/skip-link.test.d.ts.map +1 -0
  22. package/build/cjs/backend/__tests__/spinner.test.d.ts +2 -0
  23. package/build/cjs/backend/__tests__/spinner.test.d.ts.map +1 -0
  24. package/build/cjs/backend/index.cjs +62 -34
  25. package/build/cjs/backend/index.d.cts +2 -21
  26. package/build/cjs/backend/index.d.ts +2 -21
  27. package/build/cjs/backend/index.d.ts.map +1 -1
  28. package/build/cjs/backend/test/jest.setup.d.ts +2 -0
  29. package/build/cjs/backend/test/jest.setup.d.ts.map +1 -0
  30. package/build/cjs/backend/test/jestHelper.d.ts +5 -0
  31. package/build/cjs/backend/test/jestHelper.d.ts.map +1 -0
  32. package/build/components/step-card/README.md +17 -80
  33. package/build/components/step-card/_index.scss +13 -13
  34. package/build/components/step-card/step-card.yaml +6 -6
  35. package/build/components/step-card/template.njk +7 -3
  36. package/build/esm/backend/__tests__/cookie-banner.test.d.ts +2 -0
  37. package/build/esm/backend/__tests__/cookie-banner.test.d.ts.map +1 -0
  38. package/build/esm/backend/__tests__/footer.test.d.ts +2 -0
  39. package/build/esm/backend/__tests__/footer.test.d.ts.map +1 -0
  40. package/build/esm/backend/__tests__/header.test.d.ts +2 -0
  41. package/build/esm/backend/__tests__/header.test.d.ts.map +1 -0
  42. package/build/esm/backend/__tests__/index.spec.d.ts +2 -0
  43. package/build/esm/backend/__tests__/index.spec.d.ts.map +1 -0
  44. package/build/esm/backend/__tests__/language-select.test.d.ts +2 -0
  45. package/build/esm/backend/__tests__/language-select.test.d.ts.map +1 -0
  46. package/build/esm/backend/__tests__/logger.spec.d.ts +2 -0
  47. package/build/esm/backend/__tests__/logger.spec.d.ts.map +1 -0
  48. package/build/esm/backend/__tests__/mobile-base.test.d.ts +2 -0
  49. package/build/esm/backend/__tests__/mobile-base.test.d.ts.map +1 -0
  50. package/build/esm/backend/__tests__/phase-banner.test.d.ts +2 -0
  51. package/build/esm/backend/__tests__/phase-banner.test.d.ts.map +1 -0
  52. package/build/esm/backend/__tests__/progress-button.test.d.ts +2 -0
  53. package/build/esm/backend/__tests__/progress-button.test.d.ts.map +1 -0
  54. package/build/esm/backend/__tests__/skip-link.test.d.ts +2 -0
  55. package/build/esm/backend/__tests__/skip-link.test.d.ts.map +1 -0
  56. package/build/esm/backend/__tests__/spinner.test.d.ts +2 -0
  57. package/build/esm/backend/__tests__/spinner.test.d.ts.map +1 -0
  58. package/build/esm/backend/index.d.ts +2 -21
  59. package/build/esm/backend/index.d.ts.map +1 -1
  60. package/build/esm/backend/index.js +61 -33
  61. package/build/esm/backend/test/jest.setup.d.ts +2 -0
  62. package/build/esm/backend/test/jest.setup.d.ts.map +1 -0
  63. package/build/esm/backend/test/jestHelper.d.ts +5 -0
  64. package/build/esm/backend/test/jestHelper.d.ts.map +1 -0
  65. package/package.json +6 -6
package/build/all.css CHANGED
@@ -1 +1 @@
1
- @media(max-width: 640px){.govuk-header__navigation-item{border-left:none !important}.govuk-template--rebranded .govuk-header__navigation-list{padding-bottom:0px !important}.govuk-header__navigation-item{padding-top:8px !important}}.frontendUi_header_signOut-item{padding:5px 0px 5px 30px;border-left:1px solid #b1b4b6;margin-left:auto}.frontendUi_header_signOut-item--rebrand{border-left:none;padding:5px 0px 5px 0px;margin-left:auto;font-weight:700 !important}.frontendUi_header__signOut{display:flex;flex-wrap:wrap}.frontendUi-header__content{margin-left:auto}.govuk-header__navigation--signOut{padding:15px 0 15px !important}.govuk-template--rebranded .govuk-header__navigation{padding:15px 0 15px !important}@media(max-width: 640px){.govuk-header__navigation--signOut{padding:18px 0 8px !important}.govuk-template--rebranded .govuk-header__navigation{padding:18px 0 8px !important}}.govuk-template--rebranded .govuk-header__navigation-item a{font-weight:700 !important}.govuk-template--rebranded .govuk-header__navigation-item{padding-top:5px !important}@media(min-width: 20em)and (max-width: 48.0525em){.govuk-template--rebranded .govuk-header__navigation-list{padding-bottom:0px}}@media(max-width: 323px){.govuk-header__logo{padding-right:5px}.frontendUi_header_signOut-item{padding-left:0px}}@media(max-width: 261px){.frontendUi-header__content{margin-left:unset}.govuk-template--rebranded .govuk-header__navigation{padding:0px 0 8px !important}.govuk-header__logotype{max-width:100%;max-height:auto}.govuk-template--rebranded .govuk-header__logo{padding-top:5% !important;padding-bottom:5% !important}}.govuk-tag{font-size:16px;font-weight:bold;line-height:1;display:inline-block;padding-top:5px;padding-right:8px;padding-bottom:4px;padding-left:8px;outline:2px solid rgba(0,0,0,0);outline-offset:-2px;color:#fff !important;background-color:#1d70b8 !important;letter-spacing:1px !important;text-decoration:none !important;text-transform:uppercase !important}@media(max-width: 256px){.govuk-phase-banner__content{display:block}}.language-select{margin:15px 0 15px 0}.language-select__list{margin-top:1em;text-align:right}.language-select__list-item{display:inline-block}.language-select__list-item:first-child::after{content:"";display:inline-block;position:relative;top:.1875em;height:1em;border-right:.09375em solid #000}.language-select__list-item a,.language-select__list-item [aria-current]{padding:.3125em}@media screen and (max-width: 641px){.language-select__list{float:none;text-align:left;padding-bottom:10px;border-bottom:1px solid #b1b4b6}}.spinner{width:80px;height:80px;border-radius:50%;border-width:12px;border-style:solid;border-color:#dee0e2;border-top-color:#005ea5;margin-bottom:15px}@media(forced-colors: active){.spinner{forced-color-adjust:none;border-top-color:rgba(0,0,0,0) !important}}@media not (prefers-reduced-motion){.spinner{-webkit-animation:spin 2s linear infinite;animation:spin 2s linear infinite}}@media(prefers-reduced-motion){.spinner{transform:rotate(0.125turn)}}.spinner__finished{border-color:#005ea5;-webkit-animation:none;animation:none}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg)}}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.centre{margin-left:auto;margin-right:auto;text-align:center;display:block}@media(max-width: 256px){.govuk-footer__crown{max-width:100%;max-height:auto}}.govuk-footer{padding-top:25px;padding-bottom:15px;border-top:1px solid #b1b4b6;color:#0b0c0c;background:#f3f2f1}.govuk-template--rebranded .govuk-footer{border-top:10px solid #1d70b8;background:#f4f8fb}.govuk-footer__copyright-logo::before{background:#6e777a}.govuk-template--rebranded .govuk-footer__copyright-logo::before{background:currentcolor}.govuk-button--progress{position:relative}.govuk-button--progress-loading{background-color:#505a5f !important;color:#fff !important;pointer-events:none !important;padding-left:40px !important}@keyframes rotate{from{transform:rotate(0)}to{transform:rotate(360deg)}}.govuk-button--progress-loading::before{content:"";border:3px solid hsla(0,0%,100%,.35);border-top:3px solid #fff;border-radius:50%;width:20px;height:20px;animation:rotate 1s infinite linear;position:absolute;top:12% !important;left:8px}.govuk-progress-button--disabled{opacity:1 !important}@media(prefers-reduced-motion: reduce){.govuk-button--progress-loading{padding-left:10px !important}.govuk-button--progress-loading::before{display:none}}.step-card{border:1px solid #b1b4b6;gap:0;box-sizing:border-box;display:flex;flex-direction:column;align-items:flex-start;text-align:left;background:#fff;color:#0b0c0c}.step-card .govuk-list{margin-bottom:0 !important;width:100%}@media(max-width: 768px){.step-card{padding-left:.75rem;padding-right:.75rem;margin-left:.75rem;margin-right:.75rem}}@media(min-width: 769px){.step-card{padding-left:1.5rem;padding-right:1.5rem}}.step-item-container{display:flex;align-items:flex-start;border-bottom:1px solid #b1b4b6}.step-item-image{flex-shrink:0;margin-right:16px;display:flex;align-items:center;background-color:#e8f1f8;justify-content:space-around;width:70px;height:70px}.step-item-content{display:flex;align-items:flex-start;width:100%}.step-title-container{display:grid;grid-template-columns:max-content 1fr;column-gap:.25em;align-items:baseline}.step-number{grid-column:1;grid-row:1;margin-bottom:0}.step-title{grid-column:2;grid-row:1;margin-bottom:6px}.step-description{grid-column:2;grid-row:2;margin-top:0;margin-bottom:0}.step-description.govuk-list--bullet{grid-column:1/-1;padding-left:0;margin-left:1.2em;margin-top:0px}.step-card .govuk-list li:last-child .step-item-container{border-bottom:none;padding-bottom:0}@media(max-width: 300px){.step-item-container{flex-direction:column}.step-item-image{margin-right:0;margin-bottom:12px}.step-title-container{display:flex;flex-wrap:wrap;align-items:baseline}.step-description{width:100%}}
1
+ @media(max-width: 640px){.govuk-header__navigation-item{border-left:none !important}.govuk-template--rebranded .govuk-header__navigation-list{padding-bottom:0px !important}.govuk-header__navigation-item{padding-top:8px !important}}.frontendUi_header_signOut-item{padding:5px 0px 5px 30px;border-left:1px solid #b1b4b6;margin-left:auto}.frontendUi_header_signOut-item--rebrand{border-left:none;padding:5px 0px 5px 0px;margin-left:auto;font-weight:700 !important}.frontendUi_header__signOut{display:flex;flex-wrap:wrap}.frontendUi-header__content{margin-left:auto}.govuk-header__navigation--signOut{padding:15px 0 15px !important}.govuk-template--rebranded .govuk-header__navigation{padding:15px 0 15px !important}@media(max-width: 640px){.govuk-header__navigation--signOut{padding:18px 0 8px !important}.govuk-template--rebranded .govuk-header__navigation{padding:18px 0 8px !important}}.govuk-template--rebranded .govuk-header__navigation-item a{font-weight:700 !important}.govuk-template--rebranded .govuk-header__navigation-item{padding-top:5px !important}@media(min-width: 20em)and (max-width: 48.0525em){.govuk-template--rebranded .govuk-header__navigation-list{padding-bottom:0px}}@media(max-width: 323px){.govuk-header__logo{padding-right:5px}.frontendUi_header_signOut-item{padding-left:0px}}@media(max-width: 261px){.frontendUi-header__content{margin-left:unset}.govuk-template--rebranded .govuk-header__navigation{padding:0px 0 8px !important}.govuk-header__logotype{max-width:100%;max-height:auto}.govuk-template--rebranded .govuk-header__logo{padding-top:5% !important;padding-bottom:5% !important}}.govuk-tag{font-size:16px;font-weight:bold;line-height:1;display:inline-block;padding-top:5px;padding-right:8px;padding-bottom:4px;padding-left:8px;outline:2px solid rgba(0,0,0,0);outline-offset:-2px;color:#fff !important;background-color:#1d70b8 !important;letter-spacing:1px !important;text-decoration:none !important;text-transform:uppercase !important}@media(max-width: 256px){.govuk-phase-banner__content{display:block}}.language-select{margin:15px 0 15px 0}.language-select__list{margin-top:1em;text-align:right}.language-select__list-item{display:inline-block}.language-select__list-item:first-child::after{content:"";display:inline-block;position:relative;top:.1875em;height:1em;border-right:.09375em solid #000}.language-select__list-item a,.language-select__list-item [aria-current]{padding:.3125em}@media screen and (max-width: 641px){.language-select__list{float:none;text-align:left;padding-bottom:10px;border-bottom:1px solid #b1b4b6}}.spinner{width:80px;height:80px;border-radius:50%;border-width:12px;border-style:solid;border-color:#dee0e2;border-top-color:#005ea5;margin-bottom:15px}@media(forced-colors: active){.spinner{forced-color-adjust:none;border-top-color:rgba(0,0,0,0) !important}}@media not (prefers-reduced-motion){.spinner{-webkit-animation:spin 2s linear infinite;animation:spin 2s linear infinite}}@media(prefers-reduced-motion){.spinner{transform:rotate(0.125turn)}}.spinner__finished{border-color:#005ea5;-webkit-animation:none;animation:none}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg)}}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.centre{margin-left:auto;margin-right:auto;text-align:center;display:block}@media(max-width: 256px){.govuk-footer__crown{max-width:100%;max-height:auto}}.govuk-footer{padding-top:25px;padding-bottom:15px;border-top:1px solid #b1b4b6;color:#0b0c0c;background:#f3f2f1}.govuk-template--rebranded .govuk-footer{border-top:10px solid #1d70b8;background:#f4f8fb}.govuk-footer__copyright-logo::before{background:#6e777a}.govuk-template--rebranded .govuk-footer__copyright-logo::before{background:currentcolor}.govuk-button--progress{position:relative}.govuk-button--progress-loading{background-color:#505a5f !important;color:#fff !important;pointer-events:none !important;padding-left:40px !important}@keyframes rotate{from{transform:rotate(0)}to{transform:rotate(360deg)}}.govuk-button--progress-loading::before{content:"";border:3px solid hsla(0,0%,100%,.35);border-top:3px solid #fff;border-radius:50%;width:20px;height:20px;animation:rotate 1s infinite linear;position:absolute;top:12% !important;left:8px}.govuk-progress-button--disabled{opacity:1 !important}@media(prefers-reduced-motion: reduce){.govuk-button--progress-loading{padding-left:10px !important}.govuk-button--progress-loading::before{display:none}}.step-card{border:1px solid #b1b4b6;gap:0;box-sizing:border-box;display:flex;flex-direction:column;align-items:flex-start;text-align:left}.step-card .govuk-list{margin-bottom:0 !important;width:100%}.step-card h2{margin-bottom:5px}@media(max-width: 768px){.step-card{padding-left:15px;padding-right:15px}}@media(min-width: 769px){.step-card{padding-left:30px;padding-right:30px}}.step-item-container{display:flex;align-items:flex-start;border-bottom:1px solid #b1b4b6}.step-item-image{flex-shrink:0;margin-right:16px;display:flex;align-items:center;background-color:#e8f1f8;justify-content:space-around;width:70px;height:70px}.step-item-content{display:flex;align-items:flex-start;width:100%}.step-header-container{display:grid;grid-template-columns:max-content 1fr;column-gap:.25em;align-items:baseline}.step-number{grid-column:1;grid-row:1;margin-bottom:0}.step-header{grid-column:2;grid-row:1;margin-bottom:6px}.step-description{grid-column:2;grid-row:2;margin-top:0;margin-bottom:0}.step-description.govuk-list--bullet{grid-column:2;padding-left:0;margin-left:1.2em;margin-top:0}.step-card .govuk-list li:last-child .step-item-container{border-bottom:none;padding-bottom:0}@media(max-width: 300px){.step-item-container{flex-direction:column}.step-item-image{margin-right:0;margin-bottom:12px}.step-header-container{display:flex;flex-wrap:wrap;align-items:baseline}.step-description{width:100%}}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=cookie-banner.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cookie-banner.test.d.ts","sourceRoot":"","sources":["../../../../src/__tests__/cookie-banner.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=footer.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"footer.test.d.ts","sourceRoot":"","sources":["../../../../src/__tests__/footer.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=header.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"header.test.d.ts","sourceRoot":"","sources":["../../../../src/__tests__/header.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.spec.d.ts","sourceRoot":"","sources":["../../../../src/__tests__/index.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=language-select.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"language-select.test.d.ts","sourceRoot":"","sources":["../../../../src/__tests__/language-select.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=logger.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.spec.d.ts","sourceRoot":"","sources":["../../../../src/__tests__/logger.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ import "@testing-library/jest-dom";
2
+ //# sourceMappingURL=mobile-base.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mobile-base.test.d.ts","sourceRoot":"","sources":["../../../../src/__tests__/mobile-base.test.ts"],"names":[],"mappings":"AACA,OAAO,2BAA2B,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=phase-banner.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"phase-banner.test.d.ts","sourceRoot":"","sources":["../../../../src/__tests__/phase-banner.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=progress-button.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"progress-button.test.d.ts","sourceRoot":"","sources":["../../../../src/__tests__/progress-button.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=skip-link.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skip-link.test.d.ts","sourceRoot":"","sources":["../../../../src/__tests__/skip-link.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=spinner.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spinner.test.d.ts","sourceRoot":"","sources":["../../../../src/__tests__/spinner.test.ts"],"names":[],"mappings":""}
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var node_fs = require('node:fs');
3
+ var fs = require('node:fs');
4
4
  var path = require('node:path');
5
5
  var pino = require('pino');
6
6
  var lodash = require('lodash');
@@ -187,20 +187,20 @@ var translationEn = {
187
187
  stepCard: stepCard
188
188
  };
189
189
 
190
- let logger;
190
+ let logger$1;
191
191
  const setCustomLogger = (customLogger) => {
192
- logger = customLogger;
192
+ logger$1 = customLogger;
193
193
  };
194
194
  const getLogger = () => {
195
195
  var _a, _b;
196
- if (!logger) {
197
- logger = pino.pino({
198
- name: "@govuk-one-login/frontend-passthrough-headers",
196
+ if (!logger$1) {
197
+ logger$1 = pino.pino({
198
+ name: "@govuk-one-login/frontend-ui",
199
199
  level: (_b = (_a = process.env.LOG_LEVEL) !== null && _a !== void 0 ? _a : process.env.LOGS_LEVEL) !== null && _b !== void 0 ? _b : "warn",
200
200
  });
201
- return logger;
201
+ return logger$1;
202
202
  }
203
- return logger;
203
+ return logger$1;
204
204
  };
205
205
 
206
206
  const getGTM = (req, res, next) => {
@@ -318,16 +318,19 @@ function getHelmetConfig(additions = {}) {
318
318
  return lodash.merge(baseHelmetConfig, additions);
319
319
  }
320
320
 
321
+ const logger = getLogger();
321
322
  // Implementation
322
323
  function frontendUiMiddleware(req, res, next) {
323
324
  res.locals.translations = req.i18n.store.data[req.i18n.language];
324
- res.locals.allTranslations = req.i18n.store.data;
325
325
  res.locals.basePath = process.cwd();
326
326
  next();
327
327
  }
328
328
  const setFrontendUiTranslations = (instanceI18n) => {
329
329
  instanceI18n.addResourceBundle("en", "translation", translationEn, true, false);
330
330
  instanceI18n.addResourceBundle("cy", "translation", translationCy, true, false);
331
+ if (process.env.CHECK_TRANSLATIONS_ENABLED) {
332
+ validateTranslations(instanceI18n.getResourceBundle("en", "translation"), instanceI18n.getResourceBundle("cy", "translation"));
333
+ }
331
334
  };
332
335
  const frontendUiMiddlewareIdentityBypass = (req, res, next) => {
333
336
  const localTranslations = {
@@ -339,32 +342,57 @@ const frontendUiMiddlewareIdentityBypass = (req, res, next) => {
339
342
  res.locals.basePath = process.cwd();
340
343
  next();
341
344
  };
342
- function resolvePath(obj, path) {
343
- return path.split(".").reduce((acc, key) => (acc && typeof acc === "object" ? acc[key] : undefined), obj);
345
+ function isPlainObject(val) {
346
+ return typeof val === "object" && val !== null && !Array.isArray(val);
347
+ }
348
+ function validateArrayItems(enArr, cyArr, fullPath) {
349
+ const longerArr = enArr.length >= cyArr.length ? enArr : cyArr;
350
+ longerArr.forEach((_, i) => {
351
+ const enItem = enArr[i];
352
+ const cyItem = cyArr[i];
353
+ if (isPlainObject(enItem) && isPlainObject(cyItem)) {
354
+ validateTranslations(enItem, cyItem, `${fullPath}[${i}]`);
355
+ }
356
+ });
357
+ }
358
+ function validateTranslations(en, cy, path = "") {
359
+ const allKeys = new Set([...Object.keys(en), ...Object.keys(cy)]);
360
+ for (const key of allKeys) {
361
+ const fullPath = path ? `${path}.${key}` : key;
362
+ if (!(key in en)) {
363
+ logger.warn(`Translation key exists in cy but is missing in en: ${fullPath}`);
364
+ continue;
365
+ }
366
+ if (!(key in cy)) {
367
+ logger.warn(`Translation key exists in en but is missing in cy: ${fullPath}`);
368
+ continue;
369
+ }
370
+ if (Array.isArray(en[key]) || Array.isArray(cy[key])) {
371
+ const enArr = en[key];
372
+ const cyArr = cy[key];
373
+ if (enArr.length !== cyArr.length) {
374
+ logger.warn(`Array length mismatch at: ${fullPath} - en has ${enArr.length} items, cy has ${cyArr.length} items`);
375
+ }
376
+ validateArrayItems(enArr, cyArr, fullPath);
377
+ }
378
+ else if (isPlainObject(en[key]) && isPlainObject(cy[key])) {
379
+ validateTranslations(en[key], cy[key], fullPath);
380
+ }
381
+ }
344
382
  }
345
- function buildSteps(allTranslations, currentTranslations, steps) {
346
- if (!allTranslations || !steps || !currentTranslations)
347
- return [];
348
- return steps
349
- .slice(0, 4)
350
- .map(({ key, image }) => {
351
- const allLanguageData = Object.keys(allTranslations).map((lng) => resolvePath(allTranslations[lng], key));
352
- return {
353
- data: resolvePath(currentTranslations, key),
354
- allLanguageData,
355
- image: image !== null && image !== void 0 ? image : null,
356
- };
357
- })
358
- .filter(({ allLanguageData }) => allLanguageData.every((step) => { var _a, _b; return (step === null || step === void 0 ? void 0 : step.title) && ((step === null || step === void 0 ? void 0 : step.description) || ((_b = (_a = step === null || step === void 0 ? void 0 : step.bulletList) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) > 0); }));
383
+ function warnCharacterLimit(text, limit) {
384
+ if (text.length > limit) {
385
+ logger.warn(`Text content exceeds character limit of ${limit} characters: "${text.slice(0, 30)}..."`);
386
+ }
359
387
  }
360
388
  function addFrontendUiGlobals(nunjucksEnv) {
361
389
  nunjucksEnv.addGlobal("addLanguageParam", addLanguageParam);
362
390
  nunjucksEnv.addGlobal("contactUsUrl", contactUsUrl);
363
- nunjucksEnv.addGlobal("buildSteps", buildSteps);
391
+ nunjucksEnv.addGlobal("warnCharacterLimit", warnCharacterLimit);
364
392
  }
365
393
  function addLanguageParam(language, url) {
366
394
  if (!url) {
367
- console.warn("URL is undefined. The parameter cannot be added, and the toggle will not work.");
395
+ logger.warn("URL is undefined. The parameter cannot be added, and the toggle will not work.");
368
396
  return "#invalid-url-lang-toggle";
369
397
  }
370
398
  url.searchParams.set("lng", language);
@@ -399,23 +427,22 @@ const getTranslationObject = (locale, filepath) => {
399
427
  path.resolve(filepath !== null && filepath !== void 0 ? filepath : "", locale, "translation.json"),
400
428
  ];
401
429
  for (const filePath of possiblePaths) {
402
- if (node_fs.existsSync(filePath)) {
430
+ if (fs.existsSync(filePath)) {
403
431
  try {
404
- const fileContent = node_fs.readFileSync(filePath, "utf8");
432
+ const fileContent = fs.readFileSync(filePath, "utf8");
405
433
  return JSON.parse(fileContent);
406
434
  }
407
- catch (error) {
408
- console.error(`Error reading or parsing translation file at ${filePath}:`, error);
435
+ catch (_a) {
436
+ logger.warn(`Error reading or parsing translation file at ${filePath}:`);
409
437
  }
410
438
  }
411
439
  }
412
- console.warn(`No translation file found for locale: ${locale}`);
440
+ logger.warn(`No translation file found for locale: ${locale}`);
413
441
  return {}; // Return an empty object as a fallback
414
442
  };
415
443
 
416
444
  exports.addFrontendUiGlobals = addFrontendUiGlobals;
417
445
  exports.addLanguageParam = addLanguageParam;
418
- exports.buildSteps = buildSteps;
419
446
  exports.contactUsUrl = contactUsUrl;
420
447
  exports.frontendUiMiddleware = frontendUiMiddleware;
421
448
  exports.frontendUiMiddlewareIdentityBypass = frontendUiMiddlewareIdentityBypass;
@@ -424,7 +451,8 @@ exports.frontendUiTranslationEn = translationEn;
424
451
  exports.getHelmetConfig = getHelmetConfig;
425
452
  exports.getTranslationObject = getTranslationObject;
426
453
  exports.locals = locals;
427
- exports.resolvePath = resolvePath;
428
454
  exports.setBaseTranslations = setBaseTranslations;
429
455
  exports.setFrontendUiTranslations = setFrontendUiTranslations;
430
456
  exports.settings = settings;
457
+ exports.validateTranslations = validateTranslations;
458
+ exports.warnCharacterLimit = warnCharacterLimit;
@@ -15,9 +15,6 @@ interface ExpressRequest extends Request {
15
15
  interface ExpressResponse extends Response {
16
16
  locals: {
17
17
  translations: unknown;
18
- allTranslations: {
19
- [lng: string]: unknown;
20
- };
21
18
  basePath?: string;
22
19
  };
23
20
  }
@@ -27,9 +24,6 @@ interface PlainRequest {
27
24
  interface PlainResponse {
28
25
  locals: {
29
26
  translations: unknown;
30
- allTranslations: {
31
- [lng: string]: unknown;
32
- };
33
27
  basePath?: string;
34
28
  };
35
29
  }
@@ -37,21 +31,8 @@ export declare function frontendUiMiddleware(req: ExpressRequest, res: ExpressRe
37
31
  export declare function frontendUiMiddleware(req: PlainRequest, res: PlainResponse, next: NextFunction): void;
38
32
  export declare const setFrontendUiTranslations: (instanceI18n: typeof i18next) => void;
39
33
  export declare const frontendUiMiddlewareIdentityBypass: (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => void;
40
- export declare function resolvePath(obj: unknown, path: string): unknown;
41
- type StepData = {
42
- title?: unknown;
43
- description?: unknown;
44
- bulletList?: unknown[];
45
- };
46
- type StepInput = {
47
- key: string;
48
- image?: string | null;
49
- };
50
- export declare function buildSteps(allTranslations: Record<string, unknown>, currentTranslations: unknown, steps: StepInput[]): {
51
- data: StepData;
52
- allLanguageData: StepData[];
53
- image: string | null;
54
- }[];
34
+ export declare function validateTranslations(en: Record<string, unknown>, cy: Record<string, unknown>, path?: string): void;
35
+ export declare function warnCharacterLimit(text: string, limit: number): void;
55
36
  export declare function addFrontendUiGlobals(nunjucksEnv: {
56
37
  addGlobal: (name: string, value: unknown) => void;
57
38
  }): void;
@@ -15,9 +15,6 @@ interface ExpressRequest extends Request {
15
15
  interface ExpressResponse extends Response {
16
16
  locals: {
17
17
  translations: unknown;
18
- allTranslations: {
19
- [lng: string]: unknown;
20
- };
21
18
  basePath?: string;
22
19
  };
23
20
  }
@@ -27,9 +24,6 @@ interface PlainRequest {
27
24
  interface PlainResponse {
28
25
  locals: {
29
26
  translations: unknown;
30
- allTranslations: {
31
- [lng: string]: unknown;
32
- };
33
27
  basePath?: string;
34
28
  };
35
29
  }
@@ -37,21 +31,8 @@ export declare function frontendUiMiddleware(req: ExpressRequest, res: ExpressRe
37
31
  export declare function frontendUiMiddleware(req: PlainRequest, res: PlainResponse, next: NextFunction): void;
38
32
  export declare const setFrontendUiTranslations: (instanceI18n: typeof i18next) => void;
39
33
  export declare const frontendUiMiddlewareIdentityBypass: (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => void;
40
- export declare function resolvePath(obj: unknown, path: string): unknown;
41
- type StepData = {
42
- title?: unknown;
43
- description?: unknown;
44
- bulletList?: unknown[];
45
- };
46
- type StepInput = {
47
- key: string;
48
- image?: string | null;
49
- };
50
- export declare function buildSteps(allTranslations: Record<string, unknown>, currentTranslations: unknown, steps: StepInput[]): {
51
- data: StepData;
52
- allLanguageData: StepData[];
53
- image: string | null;
54
- }[];
34
+ export declare function validateTranslations(en: Record<string, unknown>, cy: Record<string, unknown>, path?: string): void;
35
+ export declare function warnCharacterLimit(text: string, limit: number): void;
55
36
  export declare function addFrontendUiGlobals(nunjucksEnv: {
56
37
  addGlobal: (name: string, value: unknown) => void;
57
38
  }): void;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC1D,OAAO,OAAO,MAAM,SAAS,CAAC;AAI9B,cAAc,OAAO,CAAC;AAGtB,UAAU,QAAQ;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE;QACL,IAAI,EAAE;YAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;SAAE,CAAC;KAClC,CAAC;CACH;AAED,UAAU,cAAe,SAAQ,OAAO;IACtC,IAAI,EAAE,QAAQ,CAAC;CAChB;AAED,UAAU,eAAgB,SAAQ,QAAQ;IACxC,MAAM,EAAE;QACN,YAAY,EAAE,OAAO,CAAC;QACtB,eAAe,EAAE;YAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;SAAE,CAAC;QAC5C,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAED,UAAU,YAAY;IACpB,IAAI,EAAE,QAAQ,CAAC;CAChB;AAED,UAAU,aAAa;IACrB,MAAM,EAAE;QACN,YAAY,EAAE,OAAO,CAAC;QACtB,eAAe,EAAE;YAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;SAAE,CAAC;QAC5C,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAGD,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,YAAY,GACjB,IAAI,CAAC;AAER,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,YAAY,EACjB,GAAG,EAAE,aAAa,EAClB,IAAI,EAAE,YAAY,GACjB,IAAI,CAAC;AAcR,eAAO,MAAM,yBAAyB,GAAI,cAAc,OAAO,OAAO,SAerE,CAAC;AAEF,eAAO,MAAM,kCAAkC,GAC7C,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,MAAM,YAAY,SAUnB,CAAC;AAEF,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAI/D;AAED,KAAK,QAAQ,GAAG;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,EAAE,CAAA;CAAE,CAAC;AACnF,KAAK,SAAS,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAExD,wBAAgB,UAAU,CACxB,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACxC,mBAAmB,EAAE,OAAO,EAC5B,KAAK,EAAE,SAAS,EAAE,GACjB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,eAAe,EAAE,QAAQ,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,EAAE,CAmBzE;AAED,wBAAgB,oBAAoB,CAAC,WAAW,EAAE;IAAE,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;CAAE,QAItG;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,UAU3D;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,iBAkBhE;AAED,eAAO,MAAM,mBAAmB,GAC9B,cAAc,OAAO,OAAO,EAC5B,WAAW,MAAM,SASlB,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,MAAM,EACd,WAAW,MAAM,KAChB,MAAM,CAAC,MAAM,EAAE,OAAO,CAuBxB,CAAC;AAEF,OAAO,EAAE,OAAO,IAAI,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AACpF,OAAO,EAAE,OAAO,IAAI,uBAAuB,EAAE,MAAM,gCAAgC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC1D,OAAO,OAAO,MAAM,SAAS,CAAC;AAK9B,cAAc,OAAO,CAAC;AAKtB,UAAU,QAAQ;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE;QACL,IAAI,EAAE;YAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;SAAE,CAAC;KAClC,CAAC;CACH;AAED,UAAU,cAAe,SAAQ,OAAO;IACtC,IAAI,EAAE,QAAQ,CAAC;CAChB;AAED,UAAU,eAAgB,SAAQ,QAAQ;IACxC,MAAM,EAAE;QACN,YAAY,EAAE,OAAO,CAAC;QACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAED,UAAU,YAAY;IACpB,IAAI,EAAE,QAAQ,CAAC;CAChB;AAED,UAAU,aAAa;IACrB,MAAM,EAAE;QACN,YAAY,EAAE,OAAO,CAAC;QACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAGD,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,YAAY,GACjB,IAAI,CAAC;AAER,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,YAAY,EACjB,GAAG,EAAE,aAAa,EAClB,IAAI,EAAE,YAAY,GACjB,IAAI,CAAC;AAaR,eAAO,MAAM,yBAAyB,GAAI,cAAc,OAAO,OAAO,SAsBrE,CAAC;AAEF,eAAO,MAAM,kCAAkC,GAC7C,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,MAAM,YAAY,SAUnB,CAAC;AAsBF,wBAAgB,oBAAoB,CAClC,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC3B,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC3B,IAAI,SAAK,GACR,IAAI,CAyBN;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAM7D;AAED,wBAAgB,oBAAoB,CAAC,WAAW,EAAE;IAChD,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACnD,QAIA;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,UAU3D;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,iBAkBhE;AAED,eAAO,MAAM,mBAAmB,GAC9B,cAAc,OAAO,OAAO,EAC5B,WAAW,MAAM,SASlB,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,MAAM,EACd,WAAW,MAAM,KAChB,MAAM,CAAC,MAAM,EAAE,OAAO,CAsBxB,CAAC;AAEF,OAAO,EAAE,OAAO,IAAI,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AACpF,OAAO,EAAE,OAAO,IAAI,uBAAuB,EAAE,MAAM,gCAAgC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=jest.setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jest.setup.d.ts","sourceRoot":"","sources":["../../../../src/test/jest.setup.ts"],"names":[],"mappings":""}
@@ -0,0 +1,5 @@
1
+ import nunjucks from "nunjucks";
2
+ export declare const nunjucksEnv: nunjucks.Environment;
3
+ export declare function render(macroFolder: string, macroName: string, params?: {}): Document;
4
+ export declare function renderTemplate(macroPath: string, params?: {}): Document;
5
+ //# sourceMappingURL=jestHelper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jestHelper.d.ts","sourceRoot":"","sources":["../../../../src/test/jestHelper.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,UAAU,CAAC;AAYhC,eAAO,MAAM,WAAW,sBAMvB,CAAC;AAQF,wBAAgB,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,KAAK,YASzE;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,KAAK,YAM5D"}
@@ -2,67 +2,6 @@
2
2
 
3
3
  This component displays a numbered list of steps, each with an image, title, and description or bullet list. It is intended to guide users through a process in a clear, structured format.
4
4
 
5
- - Renders up to 4 steps
6
- - Each step's content comes from your translation files, referenced by a dot-notation key
7
- - A step is only rendered if it is valid (has a `title` and either a `description` or `bulletList`) in **all** configured languages
8
- - Steps that are invalid in any language are silently omitted
9
-
10
- ## How it works
11
-
12
- Step content lives entirely in your translation files. The component uses `buildSteps` — a Nunjucks global provided by `@govuk-one-login/frontend-ui` — to resolve each step's content across all languages and filter out any steps that are incomplete in any language before passing them to the macro.
13
-
14
- `buildSteps` is called in the outer template scope (where Nunjucks globals are available), inline within the macro call. The macro itself just renders the pre-resolved steps.
15
-
16
- `allTranslations` and `translations` are set automatically on `res.locals` by `frontendUiMiddleware` and are available in all templates without any extra setup.
17
-
18
- ## Setup
19
-
20
- Register `buildSteps` as a Nunjucks global in your app's Nunjucks configuration:
21
-
22
- ```js
23
- const frontendUi = require("@govuk-one-login/frontend-ui");
24
-
25
- nunjucksEnv.addGlobal("buildSteps", frontendUi.buildSteps);
26
- ```
27
-
28
- Or if you are already calling `addFrontendUiGlobals`, this is included automatically:
29
-
30
- ```js
31
- frontendUi.addFrontendUiGlobals(nunjucksEnv);
32
- ```
33
-
34
- Ensure `frontendUiMiddleware` is registered in your Express app:
35
-
36
- ```js
37
- const { frontendUiMiddleware } = require("@govuk-one-login/frontend-ui");
38
- app.use(frontendUiMiddleware);
39
- ```
40
-
41
- ## Translation file structure
42
-
43
- Each step must have a `title` and either a `description` or a `bulletList`. The `key` you pass is the full dot-notation path from the root of the language object in your translation data.
44
-
45
- ```json
46
- {
47
- "pages": {
48
- "myPage": {
49
- "steps": [
50
- {
51
- "title": "Create an account",
52
- "description": "Sign up using your email address."
53
- },
54
- {
55
- "title": "Verify your identity",
56
- "bulletList": ["Provide your passport", "Answer security questions"]
57
- }
58
- ]
59
- }
60
- }
61
- }
62
- ```
63
-
64
- This structure must exist in **every** language's translation file. Any step missing `title` and `description`/`bulletList` in any language will be omitted from all languages.
65
-
66
5
  ## Usage
67
6
 
68
7
  ```njk
@@ -70,37 +9,35 @@ This structure must exist in **every** language's translation file. Any step mis
70
9
 
71
10
  {{ frontendUiStepCard({
72
11
  translations: translations.translation.stepCard,
73
- steps: buildSteps(allTranslations, translations, [
74
- { key: "translation.pages.myPage.steps.0", image: "/assets/images/step1.svg" },
75
- { key: "translation.pages.myPage.steps.1", image: "/assets/images/step2.svg" },
76
- { key: "translation.pages.myPage.steps.2", image: null }
77
- ])
12
+ steps: [
13
+ {
14
+ data: { title: "Create an account", description: "Sign up using your email address." },
15
+ image: "/assets/images/step1.svg"
16
+ },
17
+ {
18
+ data: { title: "Verify your identity", bulletList: ["Provide your passport", "Answer security questions"] },
19
+ image: "/assets/images/step2.svg"
20
+ }
21
+ ]
78
22
  }) }}
79
23
  ```
80
24
 
81
25
  ## Parameters
82
26
 
83
- ### `frontendUiStepCard`
84
-
85
27
  | Name | Type | Required | Description |
86
28
  |------|------|----------|-------------|
87
29
  | `translations` | object | Yes | Translations object for the component (e.g. `translations.translation.stepCard`). The template uses `translations.header` for the section heading. |
88
- | `steps` | array | Yes | Array of resolved step objects returned by `buildSteps`. |
89
-
90
- ### `buildSteps(allTranslations, translations, steps)`
91
-
92
- | Argument | Type | Description |
93
- |----------|------|-------------|
94
- | `allTranslations` | object | All language translation data, available in templates as `allTranslations` via `frontendUiMiddleware`. |
95
- | `translations` | object | Current language translation data, available in templates as `translations` via `frontendUiMiddleware`. |
96
- | `steps` | array | Array of step definitions, each with a `key` and optional `image`. |
30
+ | `steps` | array | Yes | Array of step objects, each with a `data` object and optional `image`. |
31
+ | `unlimitedSteps` | boolean | No | If true, skips the default 4 step limit and renders all steps provided. |
97
32
 
98
- ### Step definition
33
+ ### Step object
99
34
 
100
35
  | Name | Type | Required | Description |
101
36
  |------|------|----------|-------------|
102
- | `key` | string | Yes | Full dot-notation path to the step from the root of the language object (e.g. `translation.pages.myPage.steps.0`). |
103
- | `image` | string | No | Path to the step image. Falls back to an inline SVG placeholder if `null` or omitted. |
37
+ | `data.title` | string | Yes | The step title. |
38
+ | `data.description` | string | No | A short description. Either this or `data.bulletList` is required. |
39
+ | `data.bulletList` | string[] | No | A list of bullet points. Either this or `data.description` is required. |
40
+ | `image` | string | No | Path to the step image. Falls back to an inline SVG placeholder if omitted. |
104
41
 
105
42
  ## Styles
106
43
 
@@ -8,8 +8,6 @@
8
8
  flex-direction: column;
9
9
  align-items: flex-start;
10
10
  text-align: left;
11
- background: #fff;
12
- color: #0b0c0c;
13
11
 
14
12
  .govuk-list {
15
13
  margin-bottom: 0 !important;
@@ -17,16 +15,18 @@
17
15
  }
18
16
 
19
17
 
18
+ h2 {
19
+ margin-bottom: 5px;
20
+ }
21
+
20
22
  @media (max-width: 768px) {
21
- padding-left: 0.75rem;
22
- padding-right: 0.75rem;
23
- margin-left: 0.75rem;
24
- margin-right: 0.75rem;
23
+ padding-left: 15px;
24
+ padding-right: 15px;
25
25
  }
26
26
 
27
27
  @media (min-width: 769px) {
28
- padding-left: 1.5rem;
29
- padding-right: 1.5rem;
28
+ padding-left: 30px;
29
+ padding-right: 30px;
30
30
  }
31
31
  }
32
32
 
@@ -53,7 +53,7 @@
53
53
  width: 100%;
54
54
  }
55
55
 
56
- .step-title-container {
56
+ .step-header-container {
57
57
  display: grid;
58
58
  grid-template-columns: max-content 1fr;
59
59
  column-gap: 0.25em;
@@ -66,7 +66,7 @@
66
66
  margin-bottom: 0;
67
67
  }
68
68
 
69
- .step-title {
69
+ .step-header {
70
70
  grid-column: 2;
71
71
  grid-row: 1;
72
72
  margin-bottom: 6px;
@@ -80,10 +80,10 @@
80
80
  }
81
81
 
82
82
  .step-description.govuk-list--bullet {
83
- grid-column: 1 / -1;
83
+ grid-column: 2;
84
84
  padding-left: 0;
85
85
  margin-left: 1.2em;
86
- margin-top: 0px;
86
+ margin-top: 0;
87
87
  }
88
88
 
89
89
  .step-card .govuk-list li:last-child .step-item-container {
@@ -101,7 +101,7 @@
101
101
  margin-bottom: 12px;
102
102
  }
103
103
 
104
- .step-title-container {
104
+ .step-header-container {
105
105
  display: flex;
106
106
  flex-wrap: wrap;
107
107
  align-items: baseline;
@@ -3,10 +3,14 @@ params:
3
3
  type: object
4
4
  required: true
5
5
  description: translations object from middleware (translations.translation.stepCard)
6
+ - name: unlimitedSteps
7
+ type: boolean
8
+ required: false
9
+ description: if true, skips the default 4 step limit and renders all steps provided.
6
10
  - name: steps
7
11
  type: array
8
12
  required: true
9
- description: array of step objects combining translation data and image. Maximum of 4 steps will be rendered.
13
+ description: array of step objects, each with a data object and image. Steps are passed directly by the caller.
10
14
  params:
11
15
  - name: data
12
16
  type: object
@@ -15,8 +19,4 @@ params:
15
19
  - name: image
16
20
  type: string
17
21
  required: false
18
- description: path to the image for this step, or null for the default placeholder
19
- - name: stepsPath
20
- type: string
21
- required: false
22
- description: dot-notation path to the steps array within each language's translation data (e.g. "translation.pages.stepCard1.steps"). Requires configureFrontendUiNunjucks to be called in your nunjucks setup. When provided, a step is only rendered if it is valid in all languages.
22
+ description: path to the image for this step, or null for the default placeholder
@@ -1,20 +1,23 @@
1
1
  {% set stepCard = params.translations %}
2
2
  {% set steps = params.steps if params.steps else [] %}
3
3
 
4
- <div class="step-card govuk-!-padding-top-3 govuk-!-padding-bottom-6 govuk-!-margin-bottom-6">
4
+
5
+ <div class="step-card govuk-!-padding-top-6 govuk-!-padding-bottom-6 govuk-!-margin-bottom-6">
5
6
  <h2 class="govuk-heading-m">{{ stepCard.header }}</h2>
6
7
  <ol class="govuk-list" role="list">
7
8
  {% for step in steps %}
9
+ {% if params.unlimitedSteps or loop.index <= 4 %}
8
10
  <li class="step-item" role="listitem">
9
11
  <div class="step-item-container govuk-!-padding-top-6{% if not loop.last %} govuk-!-padding-bottom-6{% endif %}">
10
12
  <div class="step-item-image" aria-hidden="true">
11
13
  <img src="{{ step.image if step.image else 'data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2270%22 height=%2270%22 viewBox=%220 0 70 70%22%3E%3Crect width=%2270%22 height=%2270%22 fill=%22%23e8f1f8%22/%3E%3C/svg%3E' }}" alt="">
12
14
  </div>
13
15
  <div class="step-item-content">
14
- <div class="step-title-container">
16
+ <div class="step-header-container">
15
17
  <span class="step-number govuk-body govuk-!-font-weight-bold">{{loop.index}}.</span>
16
- <p class="govuk-body govuk-!-font-weight-bold step-title">{{step.data.title}}</p>
18
+ <p class="govuk-body govuk-!-font-weight-bold step-header">{{step.data.title}}</p>
17
19
  {% if step.data.description and not step.data.bulletList %}
20
+ {{ warnCharacterLimit(step.data.description, 40) }}
18
21
  <p class="govuk-body step-description">{{ step.data.description }}</p>
19
22
  {% endif %}
20
23
  {% if step.data.bulletList and step.data.bulletList.length %}
@@ -28,6 +31,7 @@
28
31
  </div>
29
32
  </div>
30
33
  </li>
34
+ {% endif %}
31
35
  {% endfor %}
32
36
  </ol>
33
37
  </div>
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=cookie-banner.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cookie-banner.test.d.ts","sourceRoot":"","sources":["../../../../src/__tests__/cookie-banner.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=footer.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"footer.test.d.ts","sourceRoot":"","sources":["../../../../src/__tests__/footer.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=header.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"header.test.d.ts","sourceRoot":"","sources":["../../../../src/__tests__/header.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.spec.d.ts","sourceRoot":"","sources":["../../../../src/__tests__/index.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=language-select.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"language-select.test.d.ts","sourceRoot":"","sources":["../../../../src/__tests__/language-select.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=logger.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.spec.d.ts","sourceRoot":"","sources":["../../../../src/__tests__/logger.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ import "@testing-library/jest-dom";
2
+ //# sourceMappingURL=mobile-base.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mobile-base.test.d.ts","sourceRoot":"","sources":["../../../../src/__tests__/mobile-base.test.ts"],"names":[],"mappings":"AACA,OAAO,2BAA2B,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=phase-banner.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"phase-banner.test.d.ts","sourceRoot":"","sources":["../../../../src/__tests__/phase-banner.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=progress-button.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"progress-button.test.d.ts","sourceRoot":"","sources":["../../../../src/__tests__/progress-button.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=skip-link.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skip-link.test.d.ts","sourceRoot":"","sources":["../../../../src/__tests__/skip-link.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=spinner.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spinner.test.d.ts","sourceRoot":"","sources":["../../../../src/__tests__/spinner.test.ts"],"names":[],"mappings":""}
@@ -15,9 +15,6 @@ interface ExpressRequest extends Request {
15
15
  interface ExpressResponse extends Response {
16
16
  locals: {
17
17
  translations: unknown;
18
- allTranslations: {
19
- [lng: string]: unknown;
20
- };
21
18
  basePath?: string;
22
19
  };
23
20
  }
@@ -27,9 +24,6 @@ interface PlainRequest {
27
24
  interface PlainResponse {
28
25
  locals: {
29
26
  translations: unknown;
30
- allTranslations: {
31
- [lng: string]: unknown;
32
- };
33
27
  basePath?: string;
34
28
  };
35
29
  }
@@ -37,21 +31,8 @@ export declare function frontendUiMiddleware(req: ExpressRequest, res: ExpressRe
37
31
  export declare function frontendUiMiddleware(req: PlainRequest, res: PlainResponse, next: NextFunction): void;
38
32
  export declare const setFrontendUiTranslations: (instanceI18n: typeof i18next) => void;
39
33
  export declare const frontendUiMiddlewareIdentityBypass: (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => void;
40
- export declare function resolvePath(obj: unknown, path: string): unknown;
41
- type StepData = {
42
- title?: unknown;
43
- description?: unknown;
44
- bulletList?: unknown[];
45
- };
46
- type StepInput = {
47
- key: string;
48
- image?: string | null;
49
- };
50
- export declare function buildSteps(allTranslations: Record<string, unknown>, currentTranslations: unknown, steps: StepInput[]): {
51
- data: StepData;
52
- allLanguageData: StepData[];
53
- image: string | null;
54
- }[];
34
+ export declare function validateTranslations(en: Record<string, unknown>, cy: Record<string, unknown>, path?: string): void;
35
+ export declare function warnCharacterLimit(text: string, limit: number): void;
55
36
  export declare function addFrontendUiGlobals(nunjucksEnv: {
56
37
  addGlobal: (name: string, value: unknown) => void;
57
38
  }): void;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC1D,OAAO,OAAO,MAAM,SAAS,CAAC;AAK9B,cAAc,OAAO,CAAC;AAKtB,UAAU,QAAQ;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE;QACL,IAAI,EAAE;YAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;SAAE,CAAC;KAClC,CAAC;CACH;AAED,UAAU,cAAe,SAAQ,OAAO;IACtC,IAAI,EAAE,QAAQ,CAAC;CAChB;AAED,UAAU,eAAgB,SAAQ,QAAQ;IACxC,MAAM,EAAE;QACN,YAAY,EAAE,OAAO,CAAC;QACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAED,UAAU,YAAY;IACpB,IAAI,EAAE,QAAQ,CAAC;CAChB;AAED,UAAU,aAAa;IACrB,MAAM,EAAE;QACN,YAAY,EAAE,OAAO,CAAC;QACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAGD,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,YAAY,GACjB,IAAI,CAAC;AAER,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,YAAY,EACjB,GAAG,EAAE,aAAa,EAClB,IAAI,EAAE,YAAY,GACjB,IAAI,CAAC;AAaR,eAAO,MAAM,yBAAyB,GAAI,cAAc,OAAO,OAAO,SAsBrE,CAAC;AAEF,eAAO,MAAM,kCAAkC,GAC7C,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,MAAM,YAAY,SAUnB,CAAC;AAsBF,wBAAgB,oBAAoB,CAClC,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC3B,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC3B,IAAI,SAAK,GACR,IAAI,CAyBN;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAM7D;AAED,wBAAgB,oBAAoB,CAAC,WAAW,EAAE;IAChD,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACnD,QAIA;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,UAU3D;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,iBAkBhE;AAED,eAAO,MAAM,mBAAmB,GAC9B,cAAc,OAAO,OAAO,EAC5B,WAAW,MAAM,SASlB,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,MAAM,EACd,WAAW,MAAM,KAChB,MAAM,CAAC,MAAM,EAAE,OAAO,CAsBxB,CAAC;AAEF,OAAO,EAAE,OAAO,IAAI,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AACpF,OAAO,EAAE,OAAO,IAAI,uBAAuB,EAAE,MAAM,gCAAgC,CAAC"}
@@ -1,4 +1,4 @@
1
- import { existsSync, readFileSync } from 'node:fs';
1
+ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { pino } from 'pino';
4
4
  import { merge } from 'lodash';
@@ -185,20 +185,20 @@ var translationEn = {
185
185
  stepCard: stepCard
186
186
  };
187
187
 
188
- let logger;
188
+ let logger$1;
189
189
  const setCustomLogger = (customLogger) => {
190
- logger = customLogger;
190
+ logger$1 = customLogger;
191
191
  };
192
192
  const getLogger = () => {
193
193
  var _a, _b;
194
- if (!logger) {
195
- logger = pino({
196
- name: "@govuk-one-login/frontend-passthrough-headers",
194
+ if (!logger$1) {
195
+ logger$1 = pino({
196
+ name: "@govuk-one-login/frontend-ui",
197
197
  level: (_b = (_a = process.env.LOG_LEVEL) !== null && _a !== void 0 ? _a : process.env.LOGS_LEVEL) !== null && _b !== void 0 ? _b : "warn",
198
198
  });
199
- return logger;
199
+ return logger$1;
200
200
  }
201
- return logger;
201
+ return logger$1;
202
202
  };
203
203
 
204
204
  const getGTM = (req, res, next) => {
@@ -316,16 +316,19 @@ function getHelmetConfig(additions = {}) {
316
316
  return merge(baseHelmetConfig, additions);
317
317
  }
318
318
 
319
+ const logger = getLogger();
319
320
  // Implementation
320
321
  function frontendUiMiddleware(req, res, next) {
321
322
  res.locals.translations = req.i18n.store.data[req.i18n.language];
322
- res.locals.allTranslations = req.i18n.store.data;
323
323
  res.locals.basePath = process.cwd();
324
324
  next();
325
325
  }
326
326
  const setFrontendUiTranslations = (instanceI18n) => {
327
327
  instanceI18n.addResourceBundle("en", "translation", translationEn, true, false);
328
328
  instanceI18n.addResourceBundle("cy", "translation", translationCy, true, false);
329
+ if (process.env.CHECK_TRANSLATIONS_ENABLED) {
330
+ validateTranslations(instanceI18n.getResourceBundle("en", "translation"), instanceI18n.getResourceBundle("cy", "translation"));
331
+ }
329
332
  };
330
333
  const frontendUiMiddlewareIdentityBypass = (req, res, next) => {
331
334
  const localTranslations = {
@@ -337,32 +340,57 @@ const frontendUiMiddlewareIdentityBypass = (req, res, next) => {
337
340
  res.locals.basePath = process.cwd();
338
341
  next();
339
342
  };
340
- function resolvePath(obj, path) {
341
- return path.split(".").reduce((acc, key) => (acc && typeof acc === "object" ? acc[key] : undefined), obj);
343
+ function isPlainObject(val) {
344
+ return typeof val === "object" && val !== null && !Array.isArray(val);
345
+ }
346
+ function validateArrayItems(enArr, cyArr, fullPath) {
347
+ const longerArr = enArr.length >= cyArr.length ? enArr : cyArr;
348
+ longerArr.forEach((_, i) => {
349
+ const enItem = enArr[i];
350
+ const cyItem = cyArr[i];
351
+ if (isPlainObject(enItem) && isPlainObject(cyItem)) {
352
+ validateTranslations(enItem, cyItem, `${fullPath}[${i}]`);
353
+ }
354
+ });
355
+ }
356
+ function validateTranslations(en, cy, path = "") {
357
+ const allKeys = new Set([...Object.keys(en), ...Object.keys(cy)]);
358
+ for (const key of allKeys) {
359
+ const fullPath = path ? `${path}.${key}` : key;
360
+ if (!(key in en)) {
361
+ logger.warn(`Translation key exists in cy but is missing in en: ${fullPath}`);
362
+ continue;
363
+ }
364
+ if (!(key in cy)) {
365
+ logger.warn(`Translation key exists in en but is missing in cy: ${fullPath}`);
366
+ continue;
367
+ }
368
+ if (Array.isArray(en[key]) || Array.isArray(cy[key])) {
369
+ const enArr = en[key];
370
+ const cyArr = cy[key];
371
+ if (enArr.length !== cyArr.length) {
372
+ logger.warn(`Array length mismatch at: ${fullPath} - en has ${enArr.length} items, cy has ${cyArr.length} items`);
373
+ }
374
+ validateArrayItems(enArr, cyArr, fullPath);
375
+ }
376
+ else if (isPlainObject(en[key]) && isPlainObject(cy[key])) {
377
+ validateTranslations(en[key], cy[key], fullPath);
378
+ }
379
+ }
342
380
  }
343
- function buildSteps(allTranslations, currentTranslations, steps) {
344
- if (!allTranslations || !steps || !currentTranslations)
345
- return [];
346
- return steps
347
- .slice(0, 4)
348
- .map(({ key, image }) => {
349
- const allLanguageData = Object.keys(allTranslations).map((lng) => resolvePath(allTranslations[lng], key));
350
- return {
351
- data: resolvePath(currentTranslations, key),
352
- allLanguageData,
353
- image: image !== null && image !== void 0 ? image : null,
354
- };
355
- })
356
- .filter(({ allLanguageData }) => allLanguageData.every((step) => { var _a, _b; return (step === null || step === void 0 ? void 0 : step.title) && ((step === null || step === void 0 ? void 0 : step.description) || ((_b = (_a = step === null || step === void 0 ? void 0 : step.bulletList) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) > 0); }));
381
+ function warnCharacterLimit(text, limit) {
382
+ if (text.length > limit) {
383
+ logger.warn(`Text content exceeds character limit of ${limit} characters: "${text.slice(0, 30)}..."`);
384
+ }
357
385
  }
358
386
  function addFrontendUiGlobals(nunjucksEnv) {
359
387
  nunjucksEnv.addGlobal("addLanguageParam", addLanguageParam);
360
388
  nunjucksEnv.addGlobal("contactUsUrl", contactUsUrl);
361
- nunjucksEnv.addGlobal("buildSteps", buildSteps);
389
+ nunjucksEnv.addGlobal("warnCharacterLimit", warnCharacterLimit);
362
390
  }
363
391
  function addLanguageParam(language, url) {
364
392
  if (!url) {
365
- console.warn("URL is undefined. The parameter cannot be added, and the toggle will not work.");
393
+ logger.warn("URL is undefined. The parameter cannot be added, and the toggle will not work.");
366
394
  return "#invalid-url-lang-toggle";
367
395
  }
368
396
  url.searchParams.set("lng", language);
@@ -397,18 +425,18 @@ const getTranslationObject = (locale, filepath) => {
397
425
  path.resolve(filepath !== null && filepath !== void 0 ? filepath : "", locale, "translation.json"),
398
426
  ];
399
427
  for (const filePath of possiblePaths) {
400
- if (existsSync(filePath)) {
428
+ if (fs.existsSync(filePath)) {
401
429
  try {
402
- const fileContent = readFileSync(filePath, "utf8");
430
+ const fileContent = fs.readFileSync(filePath, "utf8");
403
431
  return JSON.parse(fileContent);
404
432
  }
405
- catch (error) {
406
- console.error(`Error reading or parsing translation file at ${filePath}:`, error);
433
+ catch (_a) {
434
+ logger.warn(`Error reading or parsing translation file at ${filePath}:`);
407
435
  }
408
436
  }
409
437
  }
410
- console.warn(`No translation file found for locale: ${locale}`);
438
+ logger.warn(`No translation file found for locale: ${locale}`);
411
439
  return {}; // Return an empty object as a fallback
412
440
  };
413
441
 
414
- export { addFrontendUiGlobals, addLanguageParam, buildSteps, contactUsUrl, frontendUiMiddleware, frontendUiMiddlewareIdentityBypass, translationCy as frontendUiTranslationCy, translationEn as frontendUiTranslationEn, getHelmetConfig, getTranslationObject, locals, resolvePath, setBaseTranslations, setFrontendUiTranslations, settings };
442
+ export { addFrontendUiGlobals, addLanguageParam, contactUsUrl, frontendUiMiddleware, frontendUiMiddlewareIdentityBypass, translationCy as frontendUiTranslationCy, translationEn as frontendUiTranslationEn, getHelmetConfig, getTranslationObject, locals, setBaseTranslations, setFrontendUiTranslations, settings, validateTranslations, warnCharacterLimit };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=jest.setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jest.setup.d.ts","sourceRoot":"","sources":["../../../../src/test/jest.setup.ts"],"names":[],"mappings":""}
@@ -0,0 +1,5 @@
1
+ import nunjucks from "nunjucks";
2
+ export declare const nunjucksEnv: nunjucks.Environment;
3
+ export declare function render(macroFolder: string, macroName: string, params?: {}): Document;
4
+ export declare function renderTemplate(macroPath: string, params?: {}): Document;
5
+ //# sourceMappingURL=jestHelper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jestHelper.d.ts","sourceRoot":"","sources":["../../../../src/test/jestHelper.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,UAAU,CAAC;AAYhC,eAAO,MAAM,WAAW,sBAMvB,CAAC;AAQF,wBAAgB,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,KAAK,YASzE;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,KAAK,YAM5D"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@govuk-one-login/frontend-ui",
3
- "version": "5.1.2",
3
+ "version": "5.2.0",
4
4
  "description": "",
5
5
  "main": "build/cjs/backend/index.cjs",
6
6
  "module": "build/esm/backend/index.js",
@@ -11,7 +11,7 @@
11
11
  "scripts": {
12
12
  "build": "rollup -c",
13
13
  "dev": "rollup -c --watch",
14
- "test": "jest --coverage src",
14
+ "test": "vitest --coverage src",
15
15
  "test:visual": "playwright test browser-tests/functional-tests",
16
16
  "test:visual:update": "playwright test browser-tests/functional-tests --update-snapshots"
17
17
  },
@@ -50,6 +50,9 @@
50
50
  "peerDependenciesMeta": {
51
51
  "hmpo-components": {
52
52
  "optional": true
53
+ },
54
+ "@govuk-one-login/frontend-analytics": {
55
+ "optional": true
53
56
  }
54
57
  },
55
58
  "exports": {
@@ -62,8 +65,5 @@
62
65
  "require": "./build/cjs/frontend/index.cjs"
63
66
  }
64
67
  },
65
- "types": "./build/esm/backend/index.d.ts",
66
- "optionalDependencies": {
67
- "@govuk-one-login/frontend-analytics": "^4.0.7"
68
- }
68
+ "types": "./build/esm/backend/index.d.ts"
69
69
  }