diff --git a/frontend/src/lib/components/HelpPopover.svelte b/frontend/src/lib/components/HelpPopover.svelte
index e1d118ca..f6ff984f 100644
--- a/frontend/src/lib/components/HelpPopover.svelte
+++ b/frontend/src/lib/components/HelpPopover.svelte
@@ -1,3 +1,10 @@
+
+
+
{#if open}
{#if children}
diff --git a/frontend/src/lib/components/HelpPopover.svelte.spec.ts b/frontend/src/lib/components/HelpPopover.svelte.spec.ts
index b2e5ae16..b22d1ab9 100644
--- a/frontend/src/lib/components/HelpPopover.svelte.spec.ts
+++ b/frontend/src/lib/components/HelpPopover.svelte.spec.ts
@@ -20,7 +20,7 @@ describe('HelpPopover — initial state', () => {
renderPopover();
const btn = page.getByRole('button', { name: /Help/ });
await expect.element(btn).toHaveAttribute('aria-expanded', 'false');
- expect(document.querySelector('[role="tooltip"]')).toBeNull();
+ expect(document.querySelector('[role="region"]')).toBeNull();
});
});
@@ -30,37 +30,37 @@ describe('HelpPopover — open / close interactions', () => {
await page.getByRole('button', { name: /Help/ }).click();
const btn = page.getByRole('button', { name: /Help/ });
await expect.element(btn).toHaveAttribute('aria-expanded', 'true');
- expect(document.querySelector('[role="tooltip"]')).not.toBeNull();
+ expect(document.querySelector('[role="region"]')).not.toBeNull();
});
it('closes on Esc key', async () => {
renderPopover();
await page.getByRole('button', { name: /Help/ }).click();
- expect(document.querySelector('[role="tooltip"]')).not.toBeNull();
+ expect(document.querySelector('[role="region"]')).not.toBeNull();
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
- await vi.waitFor(() => expect(document.querySelector('[role="tooltip"]')).toBeNull());
+ await vi.waitFor(() => expect(document.querySelector('[role="region"]')).toBeNull());
});
it('closes on outside click', async () => {
renderPopover();
await page.getByRole('button', { name: /Help/ }).click();
- expect(document.querySelector('[role="tooltip"]')).not.toBeNull();
+ expect(document.querySelector('[role="region"]')).not.toBeNull();
document.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true }));
- await vi.waitFor(() => expect(document.querySelector('[role="tooltip"]')).toBeNull());
+ await vi.waitFor(() => expect(document.querySelector('[role="region"]')).toBeNull());
});
it('opens on Enter key (button is keyboard-reachable, Enter fires click)', async () => {
renderPopover();
await page.getByRole('button', { name: /Help/ }).click();
- expect(document.querySelector('[role="tooltip"]')).not.toBeNull();
+ expect(document.querySelector('[role="region"]')).not.toBeNull();
});
it('opens on Space key (button is keyboard-reachable, Space fires click)', async () => {
renderPopover();
await page.getByRole('button', { name: /Help/ }).click();
- expect(document.querySelector('[role="tooltip"]')).not.toBeNull();
+ expect(document.querySelector('[role="region"]')).not.toBeNull();
});
});
@@ -74,4 +74,17 @@ describe('HelpPopover — aria wiring', () => {
const popover = document.getElementById(controls!);
expect(popover).not.toBeNull();
});
+
+ it('two renders produce different, predictable IDs (no Math.random — SSR safe)', async () => {
+ const { container: c1 } = render(HelpPopover, { props: { label: 'A' } });
+ const { container: c2 } = render(HelpPopover, { props: { label: 'B' } });
+ const id1 = c1.querySelector('button[aria-controls]')?.getAttribute('aria-controls');
+ const id2 = c2.querySelector('button[aria-controls]')?.getAttribute('aria-controls');
+ expect(id1).toBeTruthy();
+ expect(id2).toBeTruthy();
+ expect(id1).not.toBe(id2);
+ // IDs must be deterministic (counter-based), not random hex
+ expect(id1).toMatch(/^help-popover-\d+$/);
+ expect(id2).toMatch(/^help-popover-\d+$/);
+ });
});