Skip to content

Feature/892 - Adopt MOJ Frontend mojFilter component - #920

Merged
alao-daniel merged 6 commits into
masterfrom
feature-892
Aug 19, 2026
Merged

alao-daniel merged 6 commits into
masterfrom
feature-892

Conversation

@alao-daniel

@alao-daniel alao-daniel commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Jira link

#892

Change description

Adopts MOJ Frontend mojFilter component
closes #892

Checklist

  • commit messages are meaningful and follow good commit message guidelines
  • README and other documentation has been updated / added (if needed)
  • tests have been updated / new tests has been added (if needed)
  • Does this PR introduce a breaking change

Summary by CodeRabbit

  • New Features

    • Introduced a consistent filter experience across court, SJP, location, audit-log and user-management pages.
    • Added responsive filter panels, selected-filter tags, clear/remove actions and accessible show/hide controls.
    • Added Welsh translations for filter controls.
    • Long email addresses and other unbroken values now wrap correctly.
    • Improved selection-counter and filter-layout styling.
  • Bug Fixes

    • Improved mobile filter behaviour and responsive layouts.
  • Tests

    • Updated unit, template and end-to-end coverage for filtering and accessibility.

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5c362dd4-3d30-4f92-b6dc-8167c39c0d7d

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2e738822-abf6-48a3-bc58-a409503c2c44

📥 Commits

Reviewing files that changed from the base of the PR and between 665ec1b and 7d399ce.

📒 Files selected for processing (2)
  • apps/web/src/assets/js/filter-toggle.test.ts
  • apps/web/src/assets/js/filter-toggle.ts

📝 Walkthrough

Walkthrough

The change migrates six bespoke filter panels to the MOJ Frontend mojFilter component. It adds shared Nunjucks, SCSS, JavaScript, build, localisation, accessibility, unit-test, template-test, and E2E-test integration.

Changes

MOJ filter migration

Layer / File(s) Summary
Shared integration and runtime behaviour
apps/web/package.json, apps/web/src/assets/..., apps/web/vite.build.ts, libs/...
Adds MOJ Frontend integration, shared filter initialisation, responsive overrides, build assets, template paths, and updated legacy filter behaviour.
Public and SJP templates
apps/web/src/pages/(list-types)/*, apps/web/src/pages/(public)/courts-tribunals-list/*, apps/web/src/pages/(verified)/location-name-search/*
Replaces bespoke filter markup with MOJ layouts, checkbox options, selected-filter categories, removal links, and localised data attributes.
System administration templates
apps/web/src/pages/(system-admin)/*
Migrates audit-log, user-search, and jurisdiction-data filters to MOJ layouts with translated controls and selected-filter categories.
Validation and migration records
apps/web/src/assets/js/*.test.ts, e2e-tests/tests/*, docs/tickets/892/*
Updates unit, template, and E2E assertions for MOJ markup and records the implementation plan, acceptance checks, and review findings.
Long email wrapping
apps/web/src/pages/(system-admin)/find-users/index.ts, apps/web/src/pages/(system-admin)/find-users/index.test.ts, apps/web/src/assets/css/web.scss
Adds the app-wrap-anywhere class to user email cells and verifies the rendered cell structure.

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The provided review summary identifies an unrelated flat-file-service.ts import change outside the filter migration scope in [#892]. Remove the unrelated flat-file-service.ts import change, or link it to a separate issue before merging.
Docstring Coverage ⚠️ Warning Docstring coverage is 71.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: adopting the MOJ Frontend mojFilter component.
Linked Issues check ✅ Passed The PR implements the dependency, filter migration, shared toggle initialisation, localisation, accessibility, and test updates required by [#892].
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature-892

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🧹 Nitpick comments (9)
apps/web/src/assets/js/filter-toggle.test.ts (1)

6-6: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Rename the module-level constant.

Rename filterToggleButtonSpy to FILTER_TOGGLE_BUTTON_SPY and update its references. This follows the required naming rule for constants.

As per coding guidelines, use SCREAMING_SNAKE_CASE for constants.

Source: Coding guidelines

apps/web/src/pages/(list-types)/sjp-press-list/sjp-press-list.njk.test.ts (1)

187-197: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the duplicated start-hidden assertions.

The "Filter toggle" block and the "Filter panel" block assert the same data-start-hidden values for the default case and for showFilter: true. Keep one set. This reduces maintenance when the attribute contract changes.

Also applies to: 223-236

apps/web/src/pages/(list-types)/sjp-public-list/sjp-public-list.njk (1)

76-128: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the shared SJP filter options markup.

Lines 76-128 duplicate apps/web/src/pages/(list-types)/sjp-press-list/sjp-press-list.njk lines 87-139 almost exactly: the hidden artefactId input, the search input, and the postcode and prosecutor checkbox groups. Move the markup into a shared Nunjucks macro and call it from both templates. This keeps the two SJP filter panels in step.

As per coding guidelines: "Prefer simple functional code, immutable data with const, minimal duplication".

Source: Coding guidelines

apps/web/src/pages/(list-types)/sjp-public-list/sjp-public-list.njk.test.ts (2)

531-541: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Merge the duplicated Welsh data-attribute assertions.

Line 534 and line 554 both assert data-show-text equals cy.common.showFilters. Keep the assertion in one test.

Also applies to: 551-557


502-508: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Align the script-token assertion with the press-list test.

This test checks for "filter-toggle". The equivalent press-list test checks for "filterToggle". Use the same tokens in both tests so the guard covers the same strings.

apps/web/src/pages/(public)/courts-tribunals-list/index.njk (1)

22-33: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Give the sub-jurisdiction group a real label.

The sub-jurisdiction checkboxes sit inside the jurisdiction fieldset. A span with heading classes provides their only label. Screen readers do not associate that span with the group. Use a nested fieldset with a legend, or pass fieldset: { legend: { text: item.subJurisdictionLabel } } to govukCheckboxes.

apps/web/src/pages/(verified)/location-name-search/index.njk (1)

26-61: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the shared jurisdiction filter markup.

Lines 26-61 duplicate apps/web/src/pages/(public)/courts-tribunals-list/index.njk lines 12-47 exactly. Move the markup into a shared Nunjucks macro and call it from both templates.

As per coding guidelines: "Prefer simple functional code, immutable data with const, minimal duplication".

Source: Coding guidelines

e2e-tests/tests/courts-tribunals-list.spec.ts (2)

383-389: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Fold the structural check into the filter journey test.

This test only asserts that the panel and its inputs render. It replaced a behaviour test. The E2E guideline requires one test per complete user journey and forbids separate tests for individual checks. Move these assertions into the existing filter journey test.

As per coding guidelines: "Use Playwright E2E tests with one test per complete user journey... Do not create separate E2E tests for individual validations, translations, or accessibility checks".

Source: Coding guidelines


303-303: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Prefer a role selector for the filter tags.

The tests locate tags by the .moj-filter__tag class. The tag is a link with a visually hidden "Remove this filter" label. Use page.getByRole("link", { name: /Remove this filter/ }) so the test breaks when the accessible name is lost, and so it does not depend on MOJ class names.

As per coding guidelines: "use role or label selectors before test IDs".

Also applies to: 353-368, 448-448

Source: Coding guidelines


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: db3554f0-6412-4183-b245-c920ce80cdfe

📥 Commits

Reviewing files that changed from the base of the PR and between 64a344d and 4a89407.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (45)
  • apps/web/package.json
  • apps/web/src/assets/css/dashboard.scss
  • apps/web/src/assets/css/filter-overrides.scss
  • apps/web/src/assets/css/filter-panel.scss
  • apps/web/src/assets/css/list-types/sjp-filters.scss
  • apps/web/src/assets/css/user-management.scss
  • apps/web/src/assets/css/verified-pages.scss
  • apps/web/src/assets/css/web.scss
  • apps/web/src/assets/js/filter-panel.test.ts
  • apps/web/src/assets/js/filter-panel.ts
  • apps/web/src/assets/js/filter-toggle.test.ts
  • apps/web/src/assets/js/filter-toggle.ts
  • apps/web/src/assets/js/web.test.ts
  • apps/web/src/assets/js/web.ts
  • apps/web/src/pages/(list-types)/sjp-press-list/sjp-press-list.njk
  • apps/web/src/pages/(list-types)/sjp-press-list/sjp-press-list.njk.test.ts
  • apps/web/src/pages/(list-types)/sjp-public-list/sjp-public-list.njk
  • apps/web/src/pages/(list-types)/sjp-public-list/sjp-public-list.njk.test.ts
  • apps/web/src/pages/(public)/courts-tribunals-list/index.njk
  • apps/web/src/pages/(public)/courts-tribunals-list/index.njk.test.ts
  • apps/web/src/pages/(system-admin)/audit-log-list/cy.ts
  • apps/web/src/pages/(system-admin)/audit-log-list/en.ts
  • apps/web/src/pages/(system-admin)/audit-log-list/index.njk
  • apps/web/src/pages/(system-admin)/audit-log-list/index.ts
  • apps/web/src/pages/(system-admin)/find-users/cy.ts
  • apps/web/src/pages/(system-admin)/find-users/en.ts
  • apps/web/src/pages/(system-admin)/find-users/index.njk
  • apps/web/src/pages/(system-admin)/find-users/index.njk.test.ts
  • apps/web/src/pages/(system-admin)/jurisdiction-data-list/cy.ts
  • apps/web/src/pages/(system-admin)/jurisdiction-data-list/en.ts
  • apps/web/src/pages/(system-admin)/jurisdiction-data-list/index.njk
  • apps/web/src/pages/(system-admin)/jurisdiction-data-list/index.njk.test.ts
  • apps/web/src/pages/(verified)/location-name-search/index.njk
  • apps/web/vite.build.ts
  • docs/tickets/892/plan.md
  • docs/tickets/892/review.md
  • docs/tickets/892/tasks.md
  • docs/tickets/892/ticket.md
  • e2e-tests/tests/courts-tribunals-list.spec.ts
  • e2e-tests/tests/sjp-press-list.spec.ts
  • e2e-tests/tests/system-admin/user-management.spec.ts
  • e2e-tests/tests/verified-user/sjp-public-list.spec.ts
  • libs/public-pages/src/flat-file/flat-file-service.ts
  • libs/test-support/src/nunjucks-test-helper.ts
  • libs/web-core/src/middleware/govuk-frontend/configure-govuk.ts
💤 Files with no reviewable changes (2)
  • apps/web/src/assets/css/user-management.scss
  • libs/public-pages/src/flat-file/flat-file-service.ts

// object. Only these two content helpers remain: they style cells inside the
// results table, not the filter panel.
.audit-log-layout__word-break {
word-break: break-word;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Replace the deprecated word-break value.

Line 47 fails Stylelint because break-word is deprecated for word-break. Use overflow-wrap: anywhere to retain long-value wrapping.

Proposed fix
 .audit-log-layout__word-break {
-  word-break: break-word;
+  overflow-wrap: anywhere;
 }
🧰 Tools
🪛 Stylelint (17.14.0)

[error] 47-47: Deprecated keyword "break-word" for property "word-break" (declaration-property-value-keyword-no-deprecated)

(declaration-property-value-keyword-no-deprecated)

Source: Linters/SAST tools

Comment on lines +1 to +6
// Custom overrides on top of the MOJ Frontend filter component + filter-layout
// object (adopted in #892). These bring the migrated panels back towards the
// bespoke look the pages had before the migration, in the same spirit as
// hmcts/pip-frontend's main.scss filter overrides. Loaded from web.scss AFTER
// the MOJ @use lines and govuk index so these rules win on source order.
//

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Resolve the Stylelint errors.

Line 6 is an empty comment. Line 101 needs an empty line before the comment. Remove the empty comment and add the required separation.

Also applies to: 90-105

🧰 Tools
🪛 Stylelint (17.14.0)

[error] 6-6: Unexpected empty comment (scss/comment-no-empty)

(scss/comment-no-empty)

Source: Linters/SAST tools

Comment on lines +9 to +19
// The filter toggle (FilterToggleButton) shows/hides the panel by toggling
// `.moj-js-hidden`, which lives in MOJ's hidden utility — without this @use the
// class has no rule and the panel can never hide (breaks the SJP show/hide).
@use "@ministryofjustice/frontend/moj/utilities/hidden";
@use "govuk-frontend/dist/govuk/index";
// The MOJ vendor layer above configures the shared govuk "base" module with
// $govuk-include-default-font-face: false, which suppresses GOV.UK's GDS Transport
// @font-face for the whole stylesheet (govuk helpers/_typography.scss gates on that flag).
// The gds-transport mixin is re-included below (after all @use rules) to re-emit them.
@use "govuk-frontend/dist/govuk/helpers/font-faces--internal" as govuk-font-faces;
@use "./filter-overrides.scss";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Fix the Stylelint errors before merge.

Line 9 and Line 14 require an empty line before the comments. Line 19 must omit the .scss extension from the Sass @use path.

Proposed fix
 `@use` "`@ministryofjustice/frontend/moj/objects/filter-layout`";
+
 // The filter toggle (FilterToggleButton) shows/hides the panel by toggling
@@
 `@use` "govuk-frontend/dist/govuk/index";
+
 // The MOJ vendor layer above configures the shared govuk "base" module with
@@
-@use "./filter-overrides.scss";
+@use "./filter-overrides";
🧰 Tools
🪛 Stylelint (17.14.0)

[error] 9-9: Expected empty line before comment (scss/double-slash-comment-empty-line-before)

(scss/double-slash-comment-empty-line-before)


[error] 14-14: Expected empty line before comment (scss/double-slash-comment-empty-line-before)

(scss/double-slash-comment-empty-line-before)


[error] 19-19: Unexpected extension ".scss" in @use (scss/load-partial-extension)

(scss/load-partial-extension)

Source: Linters/SAST tools

Comment on lines +141 to +161
{% set selectedCategories = [] %}
{% if filters.postcodes and filters.postcodes.length > 0 %}
{% set postcodeTags = [] %}
{% for postcode in filters.postcodes %}
{% set removeHref = "?artefactId=" + list.artefactId %}
{% for pc in filters.postcodes %}{% if pc != postcode %}{% set removeHref = removeHref + "&postcode=" + pc %}{% endif %}{% endfor %}
{% if filters.prosecutors %}{% for p in filters.prosecutors %}{% set removeHref = removeHref + "&prosecutor=" + p %}{% endfor %}{% endif %}
{% set postcodeTags = (postcodeTags.push({ text: postcode, href: removeHref }), postcodeTags) %}
{% endfor %}
{% set selectedCategories = (selectedCategories.push({ heading: { text: postcodeFilterHeading }, items: postcodeTags }), selectedCategories) %}
{% endif %}
{% if filters.prosecutors and filters.prosecutors.length > 0 %}
{% set prosecutorTags = [] %}
{% for prosecutor in filters.prosecutors %}
{% set removeHref = "?artefactId=" + list.artefactId %}
{% if filters.postcodes %}{% for pc in filters.postcodes %}{% set removeHref = removeHref + "&postcode=" + pc %}{% endfor %}{% endif %}
{% for p in filters.prosecutors %}{% if p != prosecutor %}{% set removeHref = removeHref + "&prosecutor=" + p %}{% endif %}{% endfor %}
{% set prosecutorTags = (prosecutorTags.push({ text: prosecutor, href: removeHref }), prosecutorTags) %}
{% endfor %}
{% set selectedCategories = (selectedCategories.push({ heading: { text: prosecutorFilterHeading }, items: prosecutorTags }), selectedCategories) %}
{% endif %}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Unencoded filter values in the MOJ selected-filter removal URLs. Both SJP templates build removeHref by concatenating raw postcode and prosecutor values into a query string. A value that contains &, + or # breaks the query string and applies the wrong filter set after removal.

  • apps/web/src/pages/(list-types)/sjp-press-list/sjp-press-list.njk#L141-L161: apply | urlencode to each pc and p value in both removeHref loops.
  • apps/web/src/pages/(list-types)/sjp-public-list/sjp-public-list.njk#L130-L150: apply | urlencode to each pc and p value in both removeHref loops.
📍 Affects 2 files
  • apps/web/src/pages/(list-types)/sjp-press-list/sjp-press-list.njk#L141-L161 (this comment)
  • apps/web/src/pages/(list-types)/sjp-public-list/sjp-public-list.njk#L130-L150

Comment on lines 286 to 293
it("should render filter tags with an accessible remove link", () => {
const { $ } = renderList({ filters: { postcodes: ["SW1"], prosecutors: [] } });

const removeLink = $('a[aria-label="Remove SW1 filter"]');
expect(removeLink).toHaveLength(1);
expect(removeLink.hasClass("filter-tag-remove")).toBe(true);
const tag = $(".moj-filter__tag");
expect(tag).toHaveLength(1);
expect(tag.text()).toContain("SW1");
expect(tag.find(".govuk-visually-hidden").text()).toContain("Remove this filter");
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

MOJ Frontend 10 mojFilter selected filters tag "Remove this filter" hidden text override

💡 Result:

In the MOJ Frontend Filter component, the text "Remove this filter" is implemented as hidden text for screen readers within each filter tag [1][2]. To override or customize this text, you typically modify the HTML template in your application where the moj-filter-tags are generated. The standard implementation uses a span element with the govuk-visually-hidden class inside the anchor tag [1][2]: Remove this filter Filter name Because the MOJ Design System documentation provides this as a template pattern rather than a rigid, immutable component property, you have direct control over the markup in your Nunjucks template [1][2]. You can change the content inside the govuk-visually-hidden span to whatever text is appropriate for your service's context [1]. If you are using the Nunjucks macro to render the component, note that the component structure is designed to be flexible; you can often override the rendering of the selected filters section by providing your own HTML partials or modifying the data passed to the filter component [1][3]. If the component does not natively support a specific override for this string via a macro parameter, updating the template directly is the standard approach within the Ministry of Justice design patterns [1][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- candidate files ---'
git ls-files | rg 'sjp-press-list|filter|moj-frontend|govuk-frontend' | head -120
printf '%s\n' '--- test context ---'
sed -n '250,315p' 'apps/web/src/pages/(list-types)/sjp-press-list/sjp-press-list.njk.test.ts'
printf '%s\n' '--- page references ---'
rg -n --glob '*.njk' --glob '*.ts' --glob '*.json' 'moj-filter|Remove this filter|filterTag|filter tag|filters' apps/web/src apps/web/package.json package.json

Repository: hmcts/cath-service

Length of output: 50374


🏁 Script executed:

#!/bin/bash
set -e
PAGE='apps/web/src/pages/(list-types)/sjp-press-list/sjp-press-list.njk'
TEST='apps/web/src/pages/(list-types)/sjp-press-list/sjp-press-list.njk.test.ts'
printf '%s\n' '--- template filter markup ---'
rg -n -C 8 'moj-filter__tag|govuk-visually-hidden|selectedFilters|clearFilters|showFilters|hideFilters|filter' "$PAGE"
printf '%s\n' '--- locale and render setup ---'
sed -n '1,125p' "$TEST"
printf '%s\n' '--- Welsh tests ---'
rg -n -C 8 'Welsh|Welsh|lng=cy|cy\.|locale|language' "$TEST"
printf '%s\n' '--- dependency declarations ---'
rg -n -C 3 'moj-frontend|govuk-frontend|nunjucks' apps/web/package.json package.json pnpm-lock.yaml yarn.lock package-lock.json 2>/dev/null || true

Repository: hmcts/cath-service

Length of output: 36388


🏁 Script executed:

#!/bin/bash
set -e
PAGE='apps/web/src/pages/(list-types)/sjp-press-list/sjp-press-list.njk'
TEST='apps/web/src/pages/(list-types)/sjp-press-list/sjp-press-list.njk.test.ts'
printf '%s\n' '--- template filter markup ---'
rg -n -C 8 'moj-filter__tag|govuk-visually-hidden|selectedFilters|clearFilters|showFilters|hideFilters|filter' "$PAGE"
printf '%s\n' '--- locale and render setup ---'
sed -n '1,125p' "$TEST"
printf '%s\n' '--- Welsh tests ---'
rg -n -C 8 'Welsh|lng=cy|cy\.|locale|language' "$TEST"
printf '%s\n' '--- dependency declarations ---'
rg -n -C 3 'moj-frontend|govuk-frontend|nunjucks' apps/web/package.json package.json pnpm-lock.yaml yarn.lock package-lock.json 2>/dev/null || true

Repository: hmcts/cath-service

Length of output: 36388


🌐 Web query:

github ministryofjustice frontend v10.0.1 moj components filter macro.njk "Remove this filter"

💡 Result:

In the Ministry of Justice (MOJ) Frontend library, the Filter component is used via the mojFilter Nunjucks macro [1]. If you are looking to remove an individual filter or selected filter item from the interface, this is typically handled by adjusting the configuration passed to the macro, specifically within the selectedFilters object [1]. The selectedFilters parameter in the mojFilter macro allows you to define categories and items that are currently active [1]. To remove a filter, you should: 1. Locate the selectedFilters section in your Nunjucks template [1]. 2. Identify the categories array within that object [1]. 3. Find the specific items object corresponding to the filter you wish to remove [1]. 4. Remove that item entry from the array [1]. Each item in the selectedFilters configuration includes an href property, which represents the link to remove that specific filter (often by redirecting the user to a URL that excludes that filter parameter) [1]. Removing the item from your Nunjucks configuration will stop it from rendering in the "Selected filters" section of the component [1]. If you are attempting to remove or hide the entire "Selected filters" section programmatically or via CSS, note that this section is a standard part of the component's structure [1]. For further customization or to address specific behavioral issues (such as modifying accessibility attributes like aria-haspopup), users often rely on custom JavaScript or CSS to override the component's default behavior, as the macro configuration itself is intended to support standard patterns [2][3]. For definitive guidance on the parameters available in your specific version (v10.0.1), you can inspect the Nunjucks macro options on the official MOJ Design System component page [1][4].

Citations:


🏁 Script executed:

#!/bin/bash
set -e
BASE='https://raw.githubusercontent.com/ministryofjustice/moj-frontend/v10.0.1'
for FILE in \
  'src/moj/components/filter/macro.njk' \
  'src/moj/components/filter/_macro.njk' \
  'src/moj/components/filter/template.njk' \
  'src/moj/components/filter/filter.njk'
do
  URL="$BASE/$FILE"
  BODY=$(curl -fsSL "$URL" 2>/dev/null || true)
  if [ -n "$BODY" ]; then
    printf '%s\n' "--- $URL ---"
    printf '%s\n' "$BODY" | rg -n -C 5 'Remove this filter|visually-hidden|selectedFilters|categories|items|remove' || printf '%s\n' "$BODY"
  fi
done
printf '%s\n' '--- tagged tree candidates ---'
curl -fsSL 'https://api.github.com/repos/ministryofjustice/moj-frontend/git/trees/v10.0.1?recursive=1' |
  jq -r '.tree[]?.path' |
  rg 'filter|Filter' |
  head -80

Repository: hmcts/cath-service

Length of output: 1850


🏁 Script executed:

#!/bin/bash
python3 - <<'PY'
from pathlib import Path
from urllib.request import urlopen

page = Path("apps/web/src/pages/(list-types)/sjp-press-list/sjp-press-list.njk").read_text()
macro = urlopen(
    "https://raw.githubusercontent.com/ministryofjustice/moj-frontend/v10.0.1/src/moj/components/filter/template.njk",
    timeout=10,
).read().decode()

assert "postcodeTags.push({ text: postcode, href: removeHref })" in page
assert "prosecutorTags.push({ text: prosecutor, href: removeHref })" in page
assert '<span class="govuk-visually-hidden">Remove this filter</span>' in macro
assert "item.remove" not in macro
assert "item.removeText" not in macro
assert "item.visuallyHiddenText" not in macro

print("selected filter items provide only text and href")
print("MOJ Frontend 10.0.1 supplies a fixed English removal label")
print("no item-level removal-label override is supported")
PY

Repository: hmcts/cath-service

Length of output: 2442


🏁 Script executed:

#!/bin/bash
set -e
python3 - <<'PY'
from pathlib import Path
import subprocess

page = Path("apps/web/src/pages/(list-types)/sjp-press-list/sjp-press-list.njk").read_text()
macro = subprocess.check_output(
    [
        "curl", "-fsSL",
        "https://raw.githubusercontent.com/ministryofjustice/moj-frontend/v10.0.1/src/moj/components/filter/template.njk",
    ],
    text=True,
)

assert "postcodeTags = (postcodeTags.push({ text: postcode, href: removeHref }), postcodeTags)" in page
assert "prosecutorTags = (prosecutorTags.push({ text: prosecutor, href: removeHref }), prosecutorTags)" in page
assert '<span class="govuk-visually-hidden">Remove this filter</span>' in macro
assert "item.removeText" not in macro
assert "item.visuallyHiddenText" not in macro

print("selected filter items provide only text and href")
print("MOJ Frontend 10.0.1 supplies a fixed English removal label")
print("no item-level removal-label override is supported")
PY

Repository: hmcts/cath-service

Length of output: 314


Localise the selected-filter removal label

mojFilter hard-codes Remove this filter, so Welsh selected-filter tags expose English screen-reader text. Render a localised removal label through an override or custom tag markup, and add a Welsh test with an active filter.

Source: Coding guidelines

Comment on lines +57 to +63
{% if selectedSubJurisdictions.length > 0 %}
{% set subJurisdictionTags = [] %}
{% for i in range(0, selectedSubJurisdictions.length) %}
{% set subJurisdictionTags = (subJurisdictionTags.push({ text: selectedSubJurisdictionsDisplay[i], href: subJurisdictionRemoveUrls[i] }), subJurisdictionTags) %}
{% endfor %}
{% set selectedCategories = (selectedCategories.push({ heading: { text: filterHeading }, items: subJurisdictionTags }), selectedCategories) %}
{% endif %}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Sub-jurisdiction tag group is labelled "Filter". Both templates set the sub-jurisdiction selected-filter category heading to filterHeading. The rendered group heading then reads "Filter" next to "Jurisdiction" and "Region".

  • apps/web/src/pages/(public)/courts-tribunals-list/index.njk#L57-L63: replace filterHeading with a dedicated sub-jurisdiction heading from en.ts and cy.ts.
  • apps/web/src/pages/(verified)/location-name-search/index.njk#L71-L77: replace filterHeading with the same dedicated sub-jurisdiction heading.
📍 Affects 2 files
  • apps/web/src/pages/(public)/courts-tribunals-list/index.njk#L57-L63 (this comment)
  • apps/web/src/pages/(verified)/location-name-search/index.njk#L71-L77

Comment on lines +105 to +107
<div class="govuk-form-group">
<label class="govuk-label govuk-!-font-weight-bold">{{ actionsLabel }}</label>
<p class="govuk-body-s">No actions available yet</p>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Localise the empty-actions message.

Welsh users see No actions available yet when no actions exist. Add this message to both locale objects, pass it from index.ts, and render that value here.

Proposed change
-      <p class="govuk-body-s">No actions available yet</p>
+      <p class="govuk-body-s">{{ noActionsAvailableText }}</p>

As per coding guidelines, “Every page must provide English and Welsh content and select the current locale through the i18n middleware.”

Source: Coding guidelines

Comment thread docs/tickets/892/plan.md
Comment on lines +116 to +138
### JavaScript init (FilterToggleButton wiring, .js-enabled class)
In `web.ts`, after `initAll()`:
```ts
import { FilterToggleButton } from "@ministryofjustice/frontend";
// inside the DOMContentLoaded / else branches:
const filter = document.querySelector<HTMLElement>(".moj-filter");
if (filter) {
new FilterToggleButton({
bigModeMediaQuery: "(min-width: 48.0625em)",
startHidden: filter.dataset.startHidden === "true",
toggleButton: { showText: ..., hideText: ..., classes: "govuk-button--secondary" },
closeButton: { text: ... },
// toggleButtonContainer / closeButtonContainer selectors as required by v10 API
});
}
```
- The `.js-enabled` body class is already present (GOV.UK `template.njk:35`) — no work.
- Show/hide/close button text must come from the page's locale data, passed via
`data-*` attributes on the container (NOT interpolated into JS — see §3), then read
in `web.ts`. Confirm the exact v10 `FilterToggleButton` option names against the
installed package before finalising (the ticket lists `toggleButtonContainer` /
`closeButtonContainer` / `bigModeMediaQuery` / `startHidden` / `toggleButton.showText`
/ `hideText`).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== repo files =="
git ls-files | rg '(^docs/tickets/892/plan\.md$|filter-toggle\.ts$|web\.ts$|package\.json$|package-lock\.json$|yarn\.lock$|pnpm-lock\.yaml$)' || true

echo "== docs excerpt =="
if [ -f docs/tickets/892/plan.md ]; then
  nl -ba docs/tickets/892/plan.md | sed -n '100,150p;218,240p'
fi

echo "== package manifests =="
for f in package.json; do [ -f "$f" ] && nl -ba "$f" | sed -n '1,220p'; done
for f in pkg.json package.json; do [ -f "$f" ] && nl -ba "$f" | sed -n '1,220p'; done

echo "== search FilterToggleButton usages =="
rg -n "FilterToggleButton|initFilterToggle|toggle logic|web\.ts" .

Repository: hmcts/cath-service

Length of output: 3509


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== docs excerpt =="
sed -n '110,145p;220,236p' docs/tickets/892/plan.md || true

echo "== filter-toggle.ts =="
sed -n '1,220p' apps/web/src/assets/js/filter-toggle.ts || true

echo "== web.ts relevant imports/usages =="
grep -n "import\\|FilterToggleButton\\|filter-toggle\\|initFilterToggle\\|DOMContentLoaded" apps/web/src/assets/js/web.ts || true

echo "== package pins for `@ministryofjustice/frontend` =="
python3 - <<'PY'
import json, pathlib
for p in pathlib.Path('.').rglob('package.json'):
    try:
        data=json.loads(p.read_text())
    except Exception:
        continue
    deps={}
    for key in ('dependencies','devDependencies','peerDependencies','optionalDependencies'):
        deps.update(data.get(key,{}) or {})
    if '`@ministryofjustice/frontend`' in deps:
        print(p, deps['`@ministryofjustice/frontend`'])
PY

echo "== yarn lock frontend entries =="
grep -A30 '`@ministryofjustice/frontend`@' yarn.lock | head -80 || true

echo "== local package source/types if installed =="
find apps/web -path '*node_modules/@ministryofjustice/frontend*' -type f | head -50 || true
find . -path '*node_modules/@ministryofjustice/frontend*' -type f | head -50 || true

Repository: hmcts/cath-service

Length of output: 10301


Fix the FilterToggleButton wiring example.

The FilterToggleButton constructor takes (filter, options), and initFilterToggle() from filter-toggle.ts owns that construction. Update the plan so web.ts only calls initFilterToggle() after initAll(), and remove new FilterToggleButton(...) from this section.

🧰 Tools
🪛 LanguageTool

[style] ~136-~136: Would you like to use the Oxford spelling “finalizing”? The spelling ‘finalising’ is also correct.
Context: ... against the installed package before finalising (the ticket lists `toggleButtonContaine...

(OXFORD_SPELLING_Z_NOT_S)

Comment on lines +28 to +30
2. **Two acceptance criteria unverifiable at runtime in this sandbox (AC8, AC9)**
- AC8 (axe passes on all six + keyboard reach to panel and close button) and AC9 (`yarn test:e2e` green) require a browser and Redis, neither available here. The E2E specs have been correctly updated to MOJ selectors (`.moj-filter`, `.moj-filter__tag`, tag-as-remove-link) and the code paths look right, but "axe passes" and "e2e green" are runtime claims that have not been executed.
- **Recommendation**: Run `yarn test:e2e` (and the axe checks embedded in the journey specs) in a CI/local environment with Redis + browsers before merge, and confirm green. Until then these two ACs stay `- [~]`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files 'docs/tickets/892/*' || true

echo "== review relevant =="
if [ -f docs/tickets/892/review.md ]; then
  nl -ba docs/tickets/892/review.md | sed -n '1,140p'
fi

echo "== tasks relevant =="
if [ -f docs/tickets/892/tasks.md ]; then
  nl -ba docs/tickets/892/tasks.md | sed -n '1,120p'
fi

echo "== references =="
rg -n "AC8|AC9|test:e2e|AxeBuilder|Next Steps|APPROVED|unverified|not runnable|green|passed" docs/tickets/892 || true

Repository: hmcts/cath-service

Length of output: 335


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== deterministic evidence status probe =="
python3 - <<'PY'
from pathlib import Path
import re

docs = {
    "review": Path("docs/tickets/892/review.md"),
    "tasks": Path("docs/tickets/892/tasks.md"),
}
for name, path in docs.items():
    if not path.exists():
        print(f"{name}: MISSING")
        continue
    text = path.read_text()
    print(f"\n--- {name} ---")
    for needle in [
        "Two acceptance criteria un",
        "Next Steps",
        "Overall Assessment",
        "APPROVED",
        "Axe passes on all six",
        "yarn test:e2e",
        "3673 passed",
        "not executable",
        "not runnable",
        "run green",
        "8 passed",
        "Tally:",
        "verification",
    ]:
        if needle in text:
            print(f"FOUND {needle:!r}")
    # count checkbox states where likely enough
    for m in re.finditer(r'\[[\s*\-xX~o]\].*(Axe passes|yarn test:e2e)', text):
        print("CHECKBOX", m.group(0)[:160].replace("\n", " "))
PY

Repository: hmcts/cath-service

Length of output: 325


Align the runtime verification evidence across the ticket docs.

The records claim AC8/AC9 were unrunnable or unverified, then later record those checks as run green. If AC8 and AC9 passed in CI/local, remove the partial/unverified status consistently and cite the exact commands/results. If they did not run, keep them partial and do not mark the PR as approved on that evidence.

🧰 Tools
🪛 LanguageTool

[typographical] ~30-~30: It seems that a comma is missing after this introductory phrase.
Context: ...owsers before merge, and confirm green. Until then these two ACs stay - [~]. ## 💡 SUGG...

(SINCE_THEN_COMMA)

📍 Affects 2 files
  • docs/tickets/892/review.md#L28-L30 (this comment)
  • docs/tickets/892/review.md#L55-L59
  • docs/tickets/892/review.md#L70-L89
  • docs/tickets/892/tasks.md#L41-L47

@@ -0,0 +1,124 @@
# #892: Adopt MOJ Frontend mojFilter component across the six hand-rolled filter panels

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Standardise the panel count across the ticket records.

The documents call the scope “six” while they enumerate seven pages, including jurisdiction-data-list. This can cause one migrated page to be omitted from acceptance checks.

  • docs/tickets/892/ticket.md#L1-L1: update the title and related “six” references, or explicitly define jurisdiction-data-list as an additional in-scope page.
  • docs/tickets/892/plan.md#L1-L15: align the strategy with the seven page entries in Lines [159-192].
  • docs/tickets/892/tasks.md#L17-L24: replace “six pages” with the final scope.
  • docs/tickets/892/review.md#L7-L7: align the summary and acceptance tally with the final scope.
📍 Affects 4 files
  • docs/tickets/892/ticket.md#L1-L1 (this comment)
  • docs/tickets/892/plan.md#L1-L15
  • docs/tickets/892/tasks.md#L17-L24
  • docs/tickets/892/review.md#L7-L7

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Preview Deployment Successful 🚀

Your preview environment is ready:

The environment will be automatically cleaned up when this PR is closed.

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

🎭 Playwright E2E Test Results

82 tests   52 ✅  6m 2s ⏱️
31 suites  30 💤
 1 files     0 ❌

Results for commit 8d5a454.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Preview Deployment Successful 🚀

Your preview environment is ready:

The environment will be automatically cleaned up when this PR is closed.

@github-actions

Copy link
Copy Markdown
Contributor

Preview Deployment Successful 🚀

Your preview environment is ready:

The environment will be automatically cleaned up when this PR is closed.

@github-actions

Copy link
Copy Markdown
Contributor

Preview Deployment Successful 🚀

Your preview environment is ready:

The environment will be automatically cleaned up when this PR is closed.

@sonarqubecloud

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown
Contributor

Preview Deployment Successful 🚀

Your preview environment is ready:

The environment will be automatically cleaned up when this PR is closed.

@alao-daniel
alao-daniel merged commit 3c7178c into master Aug 19, 2026
25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Adopt MOJ Frontend mojFilter component across the six hand-rolled filter panels

2 participants