diff --git a/web/e2e/tests/secondary-sidebar-isolation.spec.ts b/web/e2e/tests/secondary-sidebar-isolation.spec.ts new file mode 100644 index 0000000..12dabdd --- /dev/null +++ b/web/e2e/tests/secondary-sidebar-isolation.spec.ts @@ -0,0 +1,77 @@ +import { test, expect } from '@playwright/test'; +import { seedLoreWithFolder, deleteLore, type SeededLore } from '../fixtures/api'; + +/** + * Regression : la secondary sidebar fuyait entre sections. + * + * Bug initial (2026-05-19) : on est sur /lore/:id (la sidebar affiche l'arbre + * du Lore), on clique sur "Campagne" dans la sidebar principale → on arrive + * sur /campaigns, MAIS la sidebar secondaire continuait d'afficher l'arbre + * du Lore precedent. + * + * Cause : les composants top-level (campaigns.component, lore.component, + * game-systems.component, settings.component) ne nettoyaient pas la sidebar + * heritee d'une section precedente. Fix : appel a layoutService.hide() dans + * leur ngOnInit. + */ +test.describe('Secondary sidebar — isolation entre sections', () => { + let seededLore: SeededLore; + + test.beforeEach(async ({ request }) => { + seededLore = await seedLoreWithFolder(request); + }); + + test.afterEach(async ({ request }) => { + if (seededLore?.id) await deleteLore(request, seededLore.id); + }); + + test('Lore detail → /campaigns : la sidebar secondaire disparait', async ({ page }) => { + // 1. Sur le detail d'un Lore, la sidebar secondaire est affichee avec + // le nom du Lore comme titre. + await page.goto(`/lore/${seededLore.id}`); + await expect(page.locator('app-secondary-sidebar')).toBeVisible(); + await expect(page.locator('app-secondary-sidebar')).toContainText(seededLore.name); + + // 2. Navigation vers la liste des campagnes (top-level). + await page.goto('/campaigns'); + await expect(page.getByRole('heading', { name: /Vos Campagnes/i })).toBeVisible(); + + // 3. La sidebar secondaire ne doit PAS persister (sinon elle afficherait + // encore l'arbre du Lore precedent). Le *ngIf au niveau d'AppComponent + // la retire completement du DOM quand layoutService est en etat hidden. + await expect(page.locator('app-secondary-sidebar')).toHaveCount(0); + }); + + test('Lore detail → /game-systems : la sidebar secondaire disparait', async ({ page }) => { + await page.goto(`/lore/${seededLore.id}`); + await expect(page.locator('app-secondary-sidebar')).toBeVisible(); + + await page.goto('/game-systems'); + await expect(page.getByRole('heading', { name: /Systèmes de JDR/i })).toBeVisible(); + await expect(page.locator('app-secondary-sidebar')).toHaveCount(0); + }); + + test('Lore detail → /settings : la sidebar secondaire disparait', async ({ page }) => { + await page.goto(`/lore/${seededLore.id}`); + await expect(page.locator('app-secondary-sidebar')).toBeVisible(); + + await page.goto('/settings'); + // Settings n'a pas de h1 forcement evident, on se base sur l'URL + l'absence + // de sidebar secondaire (objet du test). + await expect(page).toHaveURL(/\/settings$/); + await expect(page.locator('app-secondary-sidebar')).toHaveCount(0); + }); + + test('Lore detail → /lore (liste racine) : la sidebar secondaire disparait', async ({ page }) => { + // Sur le detail, la sidebar est visible + await page.goto(`/lore/${seededLore.id}`); + await expect(page.locator('app-secondary-sidebar')).toBeVisible(); + + // Retour a la liste racine du Lore + await page.goto('/lore'); + await expect(page.getByRole('heading', { name: /Vos Univers/i })).toBeVisible(); + + // La sidebar ne doit plus apparaitre sur la liste racine. + await expect(page.locator('app-secondary-sidebar')).toHaveCount(0); + }); +}); diff --git a/web/src/app/campaigns/campaigns.component.ts b/web/src/app/campaigns/campaigns.component.ts index 5017df5..3b8fce2 100644 --- a/web/src/app/campaigns/campaigns.component.ts +++ b/web/src/app/campaigns/campaigns.component.ts @@ -3,6 +3,7 @@ import { CommonModule } from '@angular/common'; import { Router } from '@angular/router'; import { LucideAngularModule, Map, Plus } from 'lucide-angular'; import { CampaignService } from '../services/campaign.service'; +import { LayoutService } from '../services/layout.service'; import { Campaign } from '../services/campaign.model'; import { CampaignCreateComponent, CampaignCreatePayload } from './campaign/campaign-create/campaign-create.component'; @@ -22,10 +23,15 @@ export class CampaignsComponent implements OnInit { constructor( private router: Router, - private campaignService: CampaignService + private campaignService: CampaignService, + private layoutService: LayoutService ) {} ngOnInit(): void { + // Liste racine de la section Campagnes : aucune sidebar secondaire ne + // doit subsister (ex: si on arrive depuis une page Lore qui en affichait + // une, elle persisterait sans ce hide() — cf. bug rapporte 2026-05-19). + this.layoutService.hide(); this.loadCampaigns(); } diff --git a/web/src/app/game-systems/game-systems.component.ts b/web/src/app/game-systems/game-systems.component.ts index e2d1d01..42d0bd7 100644 --- a/web/src/app/game-systems/game-systems.component.ts +++ b/web/src/app/game-systems/game-systems.component.ts @@ -3,6 +3,7 @@ import { CommonModule } from '@angular/common'; import { Router } from '@angular/router'; import { LucideAngularModule, Dices, Plus, Pencil, Trash2 } from 'lucide-angular'; import { GameSystemService } from '../services/game-system.service'; +import { LayoutService } from '../services/layout.service'; import { GameSystem } from '../services/game-system.model'; import { ConfirmDialogService } from '../shared/confirm-dialog/confirm-dialog.service'; @@ -24,10 +25,14 @@ export class GameSystemsComponent implements OnInit { constructor( private router: Router, private gameSystemService: GameSystemService, - private confirmDialog: ConfirmDialogService + private confirmDialog: ConfirmDialogService, + private layoutService: LayoutService ) {} ngOnInit(): void { + // Page racine : on s'assure de ne pas heriter de la sidebar d'une + // section precedente (cf. fix CampaignsComponent / LoreComponent). + this.layoutService.hide(); this.load(); } diff --git a/web/src/app/lore/lore.component.ts b/web/src/app/lore/lore.component.ts index 1f227af..7292bd5 100644 --- a/web/src/app/lore/lore.component.ts +++ b/web/src/app/lore/lore.component.ts @@ -3,6 +3,7 @@ import { CommonModule } from '@angular/common'; import { Router } from '@angular/router'; import { LucideAngularModule, BookOpen, Folder, Plus } from 'lucide-angular'; import { LoreService } from '../services/lore.service'; +import { LayoutService } from '../services/layout.service'; import { Lore } from '../services/lore.model'; import { LoreCreateComponent } from './lore-create/lore-create.component'; @@ -26,10 +27,15 @@ export class LoreComponent implements OnInit { constructor( private loreService: LoreService, + private layoutService: LayoutService, private router: Router ) {} ngOnInit(): void { + // Liste racine de la section Lore : aucune sidebar secondaire ne doit + // subsister (sinon elle persiste depuis la section precedente — bug + // symetrique a celui de CampaignsComponent). + this.layoutService.hide(); this.loadLores(); } diff --git a/web/src/app/settings/settings.component.ts b/web/src/app/settings/settings.component.ts index 0d754f0..88207ee 100644 --- a/web/src/app/settings/settings.component.ts +++ b/web/src/app/settings/settings.component.ts @@ -7,6 +7,7 @@ import { LucideAngularModule, ArrowLeft, RefreshCw, Save, Check, AlertCircle, Do import { SettingsService, AppSettings, AppSettingsUpdate, OneMinModelGroup, OllamaPullEvent } from '../services/settings.service'; import { UpdatesService, UpdateStatus } from '../services/updates.service'; import { ConfigService } from '../services/config.service'; +import { LayoutService } from '../services/layout.service'; import { LicenseService, LicenseStatusDTO, BetaStatusDTO, ChannelStatusDTO, ChannelName } from '../services/license.service'; import { ConfirmDialogService } from '../shared/confirm-dialog/confirm-dialog.service'; @@ -133,10 +134,14 @@ export class SettingsComponent implements OnInit, OnDestroy { private updatesService: UpdatesService, public config: ConfigService, private licenseService: LicenseService, - private confirmDialog: ConfirmDialogService + private confirmDialog: ConfirmDialogService, + private layoutService: LayoutService ) {} ngOnInit(): void { + // Page racine : on s'assure de ne pas heriter de la sidebar d'une + // section precedente (cf. fix CampaignsComponent / LoreComponent). + this.layoutService.hide(); this.loadSettings(); if (this.config.updateCheckEnabled) { this.checkUpdates();