diff --git a/apps/portal-shell/src/app/app.html b/apps/portal-shell/src/app/app.html index bd16644..0f124a2 100644 --- a/apps/portal-shell/src/app/app.html +++ b/apps/portal-shell/src/app/app.html @@ -6,3 +6,4 @@ + diff --git a/apps/portal-shell/src/app/app.spec.ts b/apps/portal-shell/src/app/app.spec.ts index 1e3cd49..b3ee85f 100644 --- a/apps/portal-shell/src/app/app.spec.ts +++ b/apps/portal-shell/src/app/app.spec.ts @@ -10,7 +10,7 @@ describe('App', () => { }).compileComponents(); }); - it('renders the layout shell — skip link, header, sidebar, main', async () => { + it('renders the layout shell — skip link, header, sidebar, main, footer', async () => { const fixture = TestBed.createComponent(App); await fixture.whenStable(); const root = fixture.nativeElement as HTMLElement; @@ -18,5 +18,6 @@ describe('App', () => { expect(root.querySelector('app-header')).not.toBeNull(); expect(root.querySelector('app-sidebar')).not.toBeNull(); expect(root.querySelector('main#main-content')).not.toBeNull(); + expect(root.querySelector('app-footer')).not.toBeNull(); }); }); diff --git a/apps/portal-shell/src/app/app.ts b/apps/portal-shell/src/app/app.ts index 5f82fdd..e6d1be8 100644 --- a/apps/portal-shell/src/app/app.ts +++ b/apps/portal-shell/src/app/app.ts @@ -2,10 +2,11 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { RouterOutlet } from '@angular/router'; import { Header } from './components/header/header'; import { Sidebar } from './components/sidebar/sidebar'; +import { Footer } from './components/footer/footer'; @Component({ selector: 'app-root', - imports: [RouterOutlet, Header, Sidebar], + imports: [RouterOutlet, Header, Sidebar, Footer], templateUrl: './app.html', styleUrl: './app.scss', changeDetection: ChangeDetectionStrategy.OnPush, diff --git a/apps/portal-shell/src/app/components/footer/footer.html b/apps/portal-shell/src/app/components/footer/footer.html new file mode 100644 index 0000000..e845a8d --- /dev/null +++ b/apps/portal-shell/src/app/components/footer/footer.html @@ -0,0 +1,26 @@ + diff --git a/apps/portal-shell/src/app/components/footer/footer.spec.ts b/apps/portal-shell/src/app/components/footer/footer.spec.ts new file mode 100644 index 0000000..1080fe5 --- /dev/null +++ b/apps/portal-shell/src/app/components/footer/footer.spec.ts @@ -0,0 +1,35 @@ +import { TestBed } from '@angular/core/testing'; +import { provideRouter } from '@angular/router'; +import { Footer } from './footer'; + +describe('Footer', () => { + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [Footer], + providers: [provideRouter([])], + }).compileComponents(); + }); + + it('exposes a footer landmark', async () => { + const fixture = TestBed.createComponent(Footer); + await fixture.whenStable(); + const root = fixture.nativeElement as HTMLElement; + expect(root.querySelector('footer[aria-label="Page footer"]')).not.toBeNull(); + }); + + it('shows the APF copyright with the current year', async () => { + const fixture = TestBed.createComponent(Footer); + await fixture.whenStable(); + const text = (fixture.nativeElement as HTMLElement).textContent ?? ''; + expect(text).toContain('APF France handicap'); + expect(text).toContain(String(new Date().getFullYear())); + }); + + it('renders both accessibility statement links (EN + FR)', async () => { + const fixture = TestBed.createComponent(Footer); + await fixture.whenStable(); + const root = fixture.nativeElement as HTMLElement; + expect(root.querySelector('a[href="/accessibility"]')).not.toBeNull(); + expect(root.querySelector('a[href="/accessibilite"]')).not.toBeNull(); + }); +}); diff --git a/apps/portal-shell/src/app/components/footer/footer.ts b/apps/portal-shell/src/app/components/footer/footer.ts new file mode 100644 index 0000000..5c06715 --- /dev/null +++ b/apps/portal-shell/src/app/components/footer/footer.ts @@ -0,0 +1,25 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { RouterLink } from '@angular/router'; + +/** + * Thin full-width footer pinned to the bottom of the shell. + * + * v1 ships: + * - APF France handicap copyright with the current year. + * - Accessibility statement links in both languages (RGAA-mandated; + * ADR-0016). Both stay visible until the language selector lands, + * after which we drop the one matching the current locale. + * + * The footer is reserved for cross-cutting page furniture — once we + * have them, the language toggle and a dev-only version badge land + * here. Real product navigation stays in the sidebar. + */ +@Component({ + selector: 'app-footer', + imports: [RouterLink], + templateUrl: './footer.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class Footer { + protected readonly year = new Date().getFullYear(); +} diff --git a/apps/portal-shell/src/app/components/header/header.html b/apps/portal-shell/src/app/components/header/header.html index aaba46d..9ef243d 100644 --- a/apps/portal-shell/src/app/components/header/header.html +++ b/apps/portal-shell/src/app/components/header/header.html @@ -1,5 +1,5 @@
- - @if (!collapsed()) {

{ expect(text).toContain('Communication'); }); - it('renders the FR + EN accessibility links', async () => { + it('does not render the accessibility links (now lives in the footer)', async () => { const fixture = TestBed.createComponent(Sidebar); await fixture.whenStable(); const root = fixture.nativeElement as HTMLElement; - expect(root.querySelector('a[href="/accessibility"]')).not.toBeNull(); - expect(root.querySelector('a[href="/accessibilite"]')).not.toBeNull(); + expect(root.querySelector('a[href="/accessibility"]')).toBeNull(); + expect(root.querySelector('a[href="/accessibilite"]')).toBeNull(); }); it('starts expanded and collapses on toggle, persisting to localStorage', async () => {