Task/prod 952 implement typescript in codebase#237
Task/prod 952 implement typescript in codebase#237Zahrahasannhs wants to merge 2 commits intomainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR migrates the cookie-consent codebase to TypeScript, updates build/test tooling to handle .ts sources, and tightens null-safety across DOM interactions.
Changes:
- Switch webpack entrypoint and Babel pipeline to compile TypeScript sources.
- Add TypeScript configuration and ambient type declarations for non-TS imports/globals.
- Update source files and unit tests to TypeScript with stricter typing/null checks.
Reviewed changes
Copilot reviewed 19 out of 24 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| webpack.config.js | Build entry switched to main.ts; Babel configured to transpile TS. |
| tsconfig.json | Introduces strict TypeScript compiler settings and includes/excludes. |
| src/types.d.ts | Adds ambient typings for html/scss imports and global test/runtime vars. |
| src/settings.ts | Adds TS type annotations (incl. nullable link handling). |
| src/settings.test.ts | Updates tests for TS module/test patterns. |
| src/main.ts | Adds TS types and changes global API attachment/event binding approach. |
| src/main.test.ts | Updates tests to align with globalThis API exposure. |
| src/enable.ts | Adds TS types and refactors DOM enablement logic. |
| src/enable.test.ts | Updates tests for TS and null-safety adjustments. |
| src/cookies.ts | Adds TS types and refactors some string/domain handling. |
| src/cookies.test.ts | Updates tests for new TS function signatures. |
| src/cookieconsent.ts | Adds TS types and null-safety around cookie parsing/event handling. |
| src/cookieconsent.test.ts | Updates tests for TS typing and session cookie behavior changes. |
| src/banner.ts | Adds TS types and null-safe DOM element access. |
| src/banner.test.ts | Updates tests for TS typing and safer DOM access. |
| src/banner.html | Updates template requires to reference settings.ts. |
| src/analyticsCookieMatcher.ts | Adds TS types and refactors pattern escaping implementation. |
| src/analyticsCookieMatcher.test.ts | Fixes test import path for colocated TS source. |
| src/snapshots/banner.test.ts.snap | Adds Jest snapshot output for banner test. |
| README.md | Documents TypeScript usage and adds typecheck command. |
| package.json | Updates entrypoint, adds typecheck script and TS-related dev deps. |
| package-lock.json | Locks new TS/Jest type dependencies and transitive updates. |
| jest.config.js | Updates Jest config for TS tests and Babel transform. |
| babel.config.js | Updates Babel config to include TypeScript preset. |
Comments suppressed due to low confidence (7)
babel.config.js:6
babel.config.jsnow usesexport default, but the repository is CommonJS (package.jsonhas no"type": "module") and Babel loadsbabel.config.jsviarequire. This will cause a syntax error when running webpack/jest. Switch back tomodule.exports = babel(or rename tobabel.config.mjsand migrate the rest of the build tooling to ESM consistently).
const babel = (api) => {
const isTest = api.env('test');
let targets = {};
let plugins = [];
src/analyticsCookieMatcher.ts:27
String.prototype.replaceAllis used here at runtime. This library already ships@babel/polyfill(core-js@2) which does not polyfillreplaceAll, so older browsers will throw at runtime. Preferreplacewith a global regex (as before) or ensure the runtime polyfill strategy is updated to includereplaceAll.
src/enable.ts:15enableScriptnow relies onElement.before()andElement.remove(), which are not supported in some older browsers and are not polyfilled by@babel/polyfill(core-js@2). To avoid runtime failures, use the previous DOM-parent approach (parentNode.insertBefore/removeChild) or add an explicit polyfill requirement.
src/main.ts:60globalThisis used for the public browser API attachment and event listener registration. If this script is expected to run in older browsers,globalThisis not available by default and is not provided by@babel/polyfill(core-js@2), leading to a runtime ReferenceError. Preferwindowfor browser-only globals or update/polyfillglobalThisexplicitly.
src/cookies.ts:126globalThis.locationis used here, butglobalThisis not available in some older browsers and isn’t polyfilled by@babel/polyfill(core-js@2). If legacy browser support is required, usewindow.location(or a small helper that falls back towindowwhenglobalThisis missing).
src/cookieconsent.ts:259handleSharedConsentLinkClickcastsevent.targettoHTMLElementand calls.closest('a'). In real click events,event.targetcan be aTextnode or another non-Element, which would makeclosestundefined and throw. Guard withif (!(event.target instanceof Element)) return;before callingclosest.
src/enable.test.ts:19- This test now wraps all assertions in
if (script) { ... }. If the DOM query unexpectedly returnsnull, the test will pass without asserting anything (false positive). Prefer assertingscriptis not null (or using a non-null assertion) so the test fails when the expected script isn’t present.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
2e5f2fb to
2490d3d
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 20 out of 30 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (8)
babel.config.js:26
babel.config.jsnow usesexport default(ESM) but the file extension is.jsandpackage.jsondoes not declare"type": "module". Babel will typically loadbabel.config.jsas CommonJS, so this will throw a syntax error when the build/tests try to read the config. Usemodule.exports = ...or convert the project/config file to ESM explicitly (e.g.,.mjs+ ESM package configuration).
const babel = (api) => {
const isTest = api.env('test');
let targets = {};
let plugins = [];
if (isTest) {
// Add node to babel targets when running tests
targets = {
...targets,
node: 'current',
};
// Add rewire for jest unit tests
plugins = [...plugins, 'babel-plugin-rewire'];
}
const config = {
plugins,
presets: [['@babel/preset-env', { targets }], '@babel/preset-typescript'],
};
return config;
};
export default babel;
src/utils/enable.ts:14
- Switching from
parent.insertBefore/removeChildtoscript.before()andscript.remove()can break compatibility with older browsers (notably IE11) that don't implement these DOM APIs. If IE support is still required (there are other IE-related comments in the codebase), consider reverting to the previous DOM operations or adding an explicit polyfill requirement.
src/utils/enable.ts:41 - Replacing
indexOf(...) === -1withallowedCategories.includes(...)changes the runtime requirements (Array.prototype.includes is not supported in IE11 without a polyfill). If legacy browser support is expected, keepindexOfor ensure the runtime polyfill set coversincludes.
src/utils/enable.test.ts:12 document.querySelector('script')can returnnull, butenableScriptexpects anHTMLScriptElement. WithstrictTypeScript checking, this should be a type error (and is also a potential runtime error if the DOM setup changes). Add a non-null assertion or an explicit null check before callingenableScript.
src/utils/enable.test.ts:19- This test now conditionally asserts only if
scriptis truthy. IfenableScriptfails to insert a new<script>, the test will silently pass with zero assertions. Prefer asserting thatscriptis not null (or using a non-null assertion) so the test fails when the DOM isn’t in the expected state.
src/services/cookieconsent.ts:263 event.targetis typed asEventTarget | nulland is not guaranteed to be anElement. Casting toHTMLElementand callingclosest()can throw at runtime if the click target is not an element (e.g., text node). Guard withevent.target instanceof Element(or similar) before callingclosest.
src/utils/analyticsCookieMatcher.ts:28- Using
String.prototype.replaceAllhere increases runtime requirements. The project still depends on@babel/polyfill(core-js@2), which does not polyfillreplaceAll, so this can throw in older browsers. Consider usingreplace(/.../g, ...)as before or updating the polyfill strategy (e.g., core-js@3) if modern-only browser support is intended.
package.json:56 jestis pinned to^29.7.0but@types/jestis^30.0.0. This mismatch can cause type incompatibilities in test code (especially with strict TS settings). Align the@types/jestmajor version with the installed Jest major, or upgrade Jest to match the types.
"@eslint/js": "^9.39.4",
"@lhci/cli": "^0.15.1",
"@types/jest": "^30.0.0",
"@types/node": "^25.5.2",
"babel-core": "^7.0.0-bridge.0",
"babel-jest": "^29.7.0",
"babel-loader": "^9.1.3",
"babel-plugin-rewire": "^1.2.0",
"css-loader": "^7.0.0",
"eslint": "^9.0.0",
"eslint-config-nhsuk": "^1.0.0",
"html-loader": "^5.1.0",
"http-server": "^14.1.1",
"husky": "^9.0.11",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
2490d3d to
f323f3d
Compare
| "watch": "webpack --watch", | ||
| "prepare": "husky" | ||
| "prepare": "husky", | ||
| "typecheck": "tsc --noEmit" |
There was a problem hiding this comment.
npm run typecheck fails for some files. Also you can include tsc during build and pre commit check.
There was a problem hiding this comment.
fixed typecheck errors. Added tsc during build and pre-commit checks
README.md
Outdated
| The release also must contain changes to the package version number to match the new tag. | ||
|
|
||
| If the release contains a change that will require the banner to be redisplayed to users, then the `COOKIE_VERSION` variable in cookieconsent.js must be increased by 1. | ||
| If the release contains a change that will require the banner to be redisplayed to users, then the `COOKIE_VERSION` variable in cookieconsent.ts must be increased by 1. |
There was a problem hiding this comment.
There is no cookieconsent.ts file in this change.
There was a problem hiding this comment.
readme updated; you're right file name was updated
f323f3d to
796ecfb
Compare
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
796ecfb to
4f981e7
Compare
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 34 out of 45 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (1)
babel.config.js:26
babel.config.jsis now written as an ES module (export default), but the project is not configured for ESM and Babel will treatbabel.config.jsas CommonJS by default. This will cause Babel/Jest/webpack to fail to load the config. Usemodule.exports = (api) => ({ ... })or rename tobabel.config.mjsand update module type accordingly.
const babel = (api) => {
const isTest = api.env('test');
let targets = {};
let plugins = [];
if (isTest) {
// Add node to babel targets when running tests
targets = {
...targets,
node: 'current',
};
// Add rewire for jest unit tests
plugins = [...plugins, 'babel-plugin-rewire'];
}
const config = {
plugins,
presets: [['@babel/preset-env', { targets }], '@babel/preset-typescript'],
};
return config;
};
export default babel;
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /** | ||
| * Consumes the `nhsa.sc` shared consent query parameter from the URL. | ||
| * | ||
| * - If the parameter is absent, nothing is done. | ||
| * - If the parameter is invalid (not 1 or 0), it is removed without modifying cookies. | ||
| * - If valid: | ||
| * - If the user has not previously consented, a new session cookie is created. | ||
| * - If the user has previously consented, but the value has changed, their | ||
| * `statistics` consent is updated and written as a session cookie. | ||
| * |
There was a problem hiding this comment.
The docstring for consumeSharedConsentQuery says that if the user has previously consented and the nhsa.sc value has changed, their statistics consent will be updated. The current implementation only calls setConsent when !isCookieConsentGiven(), so previously-consented users will never be updated. Please either implement the documented behavior or amend the comment so it matches what the code actually does.
| export function registerSharedConsentLinkHandler( | ||
| getConsentSetting: GetConsentSettingFn, | ||
| ): void { | ||
| const handler = createSharedConsentLinkClickHandler(getConsentSetting); | ||
| currentHandler = handler; | ||
| document.addEventListener('click', handler); | ||
| } |
There was a problem hiding this comment.
registerSharedConsentLinkHandler overwrites currentHandler but never removes an already-registered listener before adding a new one. If this is called multiple times (e.g. once on load and again after accepting consent), you'll end up with duplicate click handlers and repeated query-param mutation. Consider calling unregisterSharedConsentLinkHandler() at the start of this function (or no-op if currentHandler is already set).
There was a problem hiding this comment.
this is is important, can you make sure we dispose the handler as that would cause issues if we dont
4f981e7 to
db4188a
Compare
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
db4188a to
a7abcae
Compare
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
a7abcae to
785ea79
Compare
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
785ea79 to
e9f00ec
Compare
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
|



Developer checklist for this pull request
Description
Implemented typescript in code base with null safety and build config as well as resolved sonarQube warnings. Also restructured the project to create better hierarchy and align with modern TypeScript practices.