diff --git a/packages/reference-implementation/e2e/cypress/e2e/api/vc_render/render_credential.cy.ts b/packages/reference-implementation/e2e/cypress/e2e/api/vc_render/render_credential.cy.ts index 8fb6d5d6a..ccb0d048d 100644 --- a/packages/reference-implementation/e2e/cypress/e2e/api/vc_render/render_credential.cy.ts +++ b/packages/reference-implementation/e2e/cypress/e2e/api/vc_render/render_credential.cy.ts @@ -5,7 +5,7 @@ const renderPage = new RenderPage(); describe('Verify page credential rendering', () => { function verifyErrorDisplayed(errorText: string) { cy.contains(errorText, { timeout: 10000 }).should('be.visible'); - cy.get('button').contains('JSON').should('not.exist'); + cy.contains('button', 'JSON').should('not.exist'); } describe('successful verification', () => { diff --git a/packages/reference-implementation/src/app/(protected)/layout.test.tsx b/packages/reference-implementation/src/app/(protected)/layout.test.tsx index 87e3cd0f5..abfa7b99b 100644 --- a/packages/reference-implementation/src/app/(protected)/layout.test.tsx +++ b/packages/reference-implementation/src/app/(protected)/layout.test.tsx @@ -1,41 +1,36 @@ import { render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; import ProtectedLayout from './layout'; const mockPush = jest.fn(); -const mockPathname = jest.fn(() => '/configuration/dids'); jest.mock('next/navigation', () => ({ useRouter: () => ({ push: mockPush, }), - usePathname: () => mockPathname(), + usePathname: () => '/dashboard', })); +jest.mock('lucide-react', () => ({ + LogOut: () => LogOut, +})); + +const mockUseAuth = jest.fn(); + jest.mock('@/contexts/auth', () => ({ AuthProvider: ({ children }: { children: React.ReactNode }) => <>{children}, - useAuth: () => ({ - user: { name: 'Test User', email: 'test@example.com', roles: [] }, - isLoading: false, - isAuthenticated: true, - logout: jest.fn(), - }), + useAuth: () => mockUseAuth(), +})); + +jest.mock('@/contexts/did/DidContext', () => ({ + DidProvider: ({ children }: { children: React.ReactNode }) => <>{children}, })); +// Mocked so the absence assertions trip if the layout renders the sidebars again. +// The mocks render their test ids unconditionally (the real Sidebar swaps to a +// skeleton without its test id while loading) and avoid the real components' +// dependencies on '@reference-implementation/components', mocked below. See #715. jest.mock('@/components/sidebar', () => ({ - Sidebar: ({ onNavClick, selectedNavId }: { onNavClick: (id: string) => void; selectedNavId?: string }) => ( -
- - - -
- ), + Sidebar: () =>
, MobileSidebar: () =>
, })); @@ -43,84 +38,64 @@ jest.mock('@reference-implementation/components', () => ({ Loader: () =>
, })); -jest.mock('lucide-react', () => ({ - LogOut: () => LogOut, -})); - -describe('ProtectedLayout navigation', () => { +describe('ProtectedLayout', () => { beforeEach(() => { jest.clearAllMocks(); + mockUseAuth.mockReturnValue({ + user: { name: 'Test User', email: 'test@example.com', roles: [] }, + isLoading: false, + isAuthenticated: true, + logout: jest.fn(), + }); }); - it('calls router.push when clicking a mapped nav item', async () => { - const user = userEvent.setup(); + it('renders children without the navigation sidebars', () => { render( -
Content
+
Content
, ); - await user.click(screen.getByTestId('nav-dids')); - - expect(mockPush).toHaveBeenCalledWith('/configuration/dids'); + expect(screen.getByTestId('content')).toBeInTheDocument(); + expect(screen.queryByTestId('sidebar')).toBeNull(); + expect(screen.queryByTestId('mobile-sidebar')).toBeNull(); }); - it('does not call router.push when clicking an unmapped nav item', async () => { - const user = userEvent.setup(); - render( - -
Content
-
, - ); - - await user.click(screen.getByTestId('nav-credentials')); + it('shows the loader while authentication is pending', () => { + mockUseAuth.mockReturnValue({ + user: null, + isLoading: true, + isAuthenticated: false, + logout: jest.fn(), + }); - expect(mockPush).not.toHaveBeenCalled(); - }); - - it('does not call router.push for external nav items', async () => { - const user = userEvent.setup(); render( -
Content
+
Content
, ); - await user.click(screen.getByTestId('nav-resources')); - + expect(screen.getByTestId('loader')).toBeInTheDocument(); + expect(screen.queryByTestId('content')).toBeNull(); expect(mockPush).not.toHaveBeenCalled(); }); - it('derives selectedNavId from the current pathname', () => { - mockPathname.mockReturnValue('/configuration/dids'); - render( - -
Content
-
, - ); - - expect(screen.getByTestId('sidebar')).toHaveAttribute('data-selected-nav-id', 'dids'); - }); - - it('sets selectedNavId to undefined for unrecognised paths', () => { - mockPathname.mockReturnValue('/dashboard'); - render( - -
Content
-
, - ); - - expect(screen.getByTestId('sidebar')).not.toHaveAttribute('data-selected-nav-id', 'dids'); - }); + it('redirects to sign-in when unauthenticated', () => { + mockUseAuth.mockReturnValue({ + user: null, + isLoading: false, + isAuthenticated: false, + logout: jest.fn(), + }); - it('matches sub-paths to the correct nav item', () => { - mockPathname.mockReturnValue('/configuration/dids/create'); render( -
Content
+
Content
, ); - expect(screen.getByTestId('sidebar')).toHaveAttribute('data-selected-nav-id', 'dids'); + expect(mockPush).toHaveBeenCalledWith('/api/auth/signin'); + expect(screen.getByTestId('loader')).toBeInTheDocument(); + expect(screen.queryByTestId('content')).toBeNull(); }); }); diff --git a/packages/reference-implementation/src/app/(protected)/layout.tsx b/packages/reference-implementation/src/app/(protected)/layout.tsx index adbb4648e..e7a73b170 100644 --- a/packages/reference-implementation/src/app/(protected)/layout.tsx +++ b/packages/reference-implementation/src/app/(protected)/layout.tsx @@ -8,6 +8,9 @@ import { DidProvider } from '@/contexts/did/DidContext'; import { Sidebar, MobileSidebar } from '@/components/sidebar'; import { LogOut } from 'lucide-react'; +// Navigation is hidden until the pages it links to exist; flip to true to reinstate (#715). +const SHOW_NAVIGATION = false; + interface ProtectedLayoutProps { children: React.ReactNode; } @@ -144,17 +147,21 @@ function ProtectedContent({ children }: { children: React.ReactNode }) { return (
- {/* Mobile Sidebar/Navbar - hidden on desktop */} -
- -
- - {/* Desktop Sidebar - hidden on mobile */} -
- -
- -
+ {SHOW_NAVIGATION && ( + <> + {/* Mobile Sidebar/Navbar - hidden on desktop */} +
+ +
+ + {/* Desktop Sidebar - hidden on mobile */} +
+ +
+ + )} + +
{isLoading ? ( diff --git a/packages/reference-implementation/src/app/(public)/layout.test.tsx b/packages/reference-implementation/src/app/(public)/layout.test.tsx new file mode 100644 index 000000000..5b56c6dc7 --- /dev/null +++ b/packages/reference-implementation/src/app/(public)/layout.test.tsx @@ -0,0 +1,36 @@ +import { render, screen } from '@testing-library/react'; +import PublicLayout from './layout'; + +// Mocked with a root test id so the absence assertion trips if the layout +// renders the Header again (the real Header has no test id on its root). See #715. +jest.mock('@/components/Header/Header', () => ({ + __esModule: true, + default: () =>
, +})); + +jest.mock('@reference-implementation/components', () => ({ + Footer: () =>
, +})); + +describe('PublicLayout', () => { + it('renders children without the header', () => { + render( + +
Content
+
, + ); + + expect(screen.getByTestId('content')).toBeInTheDocument(); + expect(screen.queryByTestId('header')).toBeNull(); + }); + + it('renders the footer', () => { + render( + +
Content
+
, + ); + + expect(screen.getByTestId('footer')).toBeInTheDocument(); + }); +}); diff --git a/packages/reference-implementation/src/app/(public)/layout.tsx b/packages/reference-implementation/src/app/(public)/layout.tsx index f7e4efd7c..85727824d 100644 --- a/packages/reference-implementation/src/app/(public)/layout.tsx +++ b/packages/reference-implementation/src/app/(public)/layout.tsx @@ -5,15 +5,18 @@ import { Container } from '@mui/material'; import Header from '../../components/Header/Header'; +// Navigation is hidden until the pages it links to exist; flip to true to reinstate (#715). +const SHOW_NAVIGATION = false; + export default function PublicLayout({ children }: { children: React.ReactNode }) { return ( -
+ {SHOW_NAVIGATION &&
} {children}