diff --git a/src/App.tsx b/src/App.tsx
index 086f5bb30..e984cf6ae 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -76,6 +76,7 @@ const ComplianceReviewScreen = lazy(() => import('./screens/compliance-review.sc
const ComplianceCallQueuesScreen = lazy(() => import('./screens/compliance-call-queues.screen'));
const ComplianceCallQueueScreen = lazy(() => import('./screens/compliance-call-queue.screen'));
const ComplianceCallQueueDetailScreen = lazy(() => import('./screens/compliance-call-queue-detail.screen'));
+const ComplianceNotFoundScreen = lazy(() => import('./screens/compliance-not-found.screen'));
const SupportDashboardOverviewScreen = lazy(() => import('./screens/support-dashboard-overview.screen'));
const SupportDashboardScreen = lazy(() => import('./screens/support-dashboard.screen'));
const SupportDashboardIssueScreen = lazy(() => import('./screens/support-dashboard-issue.screen'));
@@ -462,6 +463,12 @@ export const Routes = [
path: 'compliance/call-queues/:queue/:userDataId',
element: withSuspense(),
},
+ // Must stay last among the compliance routes: React Router ranks static segments above a splat,
+ // so this only matches paths none of the screens above claim.
+ {
+ path: 'compliance/*',
+ element: withSuspense(),
+ },
{
path: 'sitemap',
element: withSuspense(),
diff --git a/src/__tests__/compliance-chargeback-list.screen.test.tsx b/src/__tests__/compliance-chargeback-list.screen.test.tsx
index 16435eebc..86ba778d7 100644
--- a/src/__tests__/compliance-chargeback-list.screen.test.tsx
+++ b/src/__tests__/compliance-chargeback-list.screen.test.tsx
@@ -5,6 +5,7 @@
let mockIsLoggedIn = true;
const mockGetPendingChargebacks = jest.fn();
const mockNavigate = jest.fn();
+const mockUseComplianceGuard = jest.fn();
jest.mock('@dfx.swiss/react', () => ({
useSessionContext: () => ({ isLoggedIn: mockIsLoggedIn }),
@@ -23,7 +24,7 @@ jest.mock('src/components/error-hint', () => ({
}));
jest.mock('src/hooks/guard.hook', () => ({
- useComplianceGuard: () => undefined,
+ useComplianceGuard: (...args: unknown[]) => mockUseComplianceGuard(...args),
}));
jest.mock('src/hooks/layout-config.hook', () => ({
@@ -104,6 +105,18 @@ describe('ComplianceChargebackListScreen', () => {
mockGetPendingChargebacks.mockResolvedValue(asTransport([]));
});
+ // The role guard is all that stands between an unauthorised role and this screen, and it is mocked
+ // out in every other test here — so without this assertion the screen could lose the call and the
+ // whole suite would stay green (issue #1306 suspected exactly that). Asserting the argument list is
+ // empty covers the quieter mutation too: the hook's signature is
+ // `useComplianceGuard(redirectPath = '/', isActive = true)`, so `useComplianceGuard(undefined, false)`
+ // would keep the call and still leave the guard inert.
+ it('calls the compliance guard on render, with the guard left active', () => {
+ render();
+
+ expect(mockUseComplianceGuard).toHaveBeenCalledWith();
+ });
+
it('does not fetch when not logged in', () => {
mockIsLoggedIn = false;
render();
diff --git a/src/__tests__/compliance-not-found.screen.test.tsx b/src/__tests__/compliance-not-found.screen.test.tsx
new file mode 100644
index 000000000..041d456f0
--- /dev/null
+++ b/src/__tests__/compliance-not-found.screen.test.tsx
@@ -0,0 +1,47 @@
+// Unit tests for ComplianceNotFoundScreen: the guarded catch-all for unknown /compliance paths.
+
+const mockUseComplianceGuard = jest.fn();
+
+jest.mock('@dfx.swiss/react-components', () => ({
+ StyledVerticalStack: ({ children }: { children: React.ReactNode }) =>
{children}
,
+}));
+
+jest.mock('react-router-dom', () => ({
+ useLocation: () => ({ pathname: '/compliance/does-not-exist' }),
+}));
+
+jest.mock('src/contexts/settings.context', () => ({
+ useSettingsContext: () => ({ translate: (_ns: string, key: string) => key }),
+}));
+
+jest.mock('src/hooks/guard.hook', () => ({
+ useComplianceGuard: (...args: unknown[]) => mockUseComplianceGuard(...args),
+}));
+
+jest.mock('src/hooks/layout-config.hook', () => ({
+ useLayoutOptions: () => undefined,
+}));
+
+import { render, screen } from '@testing-library/react';
+import ComplianceNotFoundScreen from 'src/screens/compliance-not-found.screen';
+
+describe('ComplianceNotFoundScreen', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ // The whole point of this screen: an unknown compliance path must still run the role guard, so an
+ // unauthorised role is sent away instead of parked on a /compliance URL (issue #1306).
+ it('calls the compliance guard on render, with the guard left active', () => {
+ render();
+
+ expect(mockUseComplianceGuard).toHaveBeenCalledWith();
+ });
+
+ it('names the missing page and shows the path that was requested', () => {
+ render();
+
+ expect(screen.getByText('This compliance page does not exist')).toBeInTheDocument();
+ expect(screen.getByText('/compliance/does-not-exist')).toBeInTheDocument();
+ });
+});
diff --git a/src/screens/compliance-not-found.screen.tsx b/src/screens/compliance-not-found.screen.tsx
new file mode 100644
index 000000000..3c5c83943
--- /dev/null
+++ b/src/screens/compliance-not-found.screen.tsx
@@ -0,0 +1,24 @@
+import { StyledVerticalStack } from '@dfx.swiss/react-components';
+import { useLocation } from 'react-router-dom';
+import { useSettingsContext } from 'src/contexts/settings.context';
+import { useComplianceGuard } from 'src/hooks/guard.hook';
+import { useLayoutOptions } from 'src/hooks/layout-config.hook';
+
+// Catch-all for every unknown path below /compliance. Without it such a URL matches no route at all,
+// so the router falls back to its errorElement — a screen that runs outside every guard and leaves the
+// address bar untouched, which parks an unauthorised role on a /compliance URL (issue #1306). Guarding
+// the catch-all makes the role check independent of whether a given compliance screen exists.
+export default function ComplianceNotFoundScreen(): JSX.Element {
+ useComplianceGuard();
+ useLayoutOptions({ title: 'Not found', backButton: true });
+
+ const { translate } = useSettingsContext();
+ const { pathname } = useLocation();
+
+ return (
+
+ {translate('screens/compliance', 'This compliance page does not exist')}
+ {pathname}
+
+ );
+}