Closes #344 ## What was implemented ### Commit 1 — `feat(nav): add cursor-pointer and tooltip to notification bell` - Extracted `bellLabel` as `$derived` in `NotificationBell.svelte` — eliminates the duplicated inline ternary and keeps tooltip/label in sync reactively - Added `title={bellLabel}` to the bell `<button>` — native tooltip mirrors `aria-label` in both zero and non-zero unread states - Added `cursor-pointer` to the bell button's class list - Added global `button { cursor: pointer; }` rule in `@layer base` of `layout.css` — prevents future regressions (global scope per Decision Queue) - Added 3 component tests in `NotificationBell.svelte.spec.ts`: cursor-pointer class present, title equals aria-label when unread=0, title equals aria-label when unread=3 ### Commit 2 — `fix(nav): replace hardcoded ThemeToggle title with Paraglide i18n keys` - Added `theme_toggle_to_light` / `theme_toggle_to_dark` keys to `de/en/es` messages - Extracted `themeLabel` as `$derived` in `ThemeToggle.svelte` and bound both `aria-label` and `title` to it - Fixes the pre-existing hardcoded English strings (`'light mode'` / `'dark mode'`) per Decision Queue resolution Touch target size was descoped per the Decision Queue. ## Decision Queue resolutions (from issue #344) - **cursor-pointer scope**: global via `@layer base` ✅ - **ThemeToggle scope**: fixed in this issue ✅ - **Touch target**: descoped ✅ ## Test results All 5 `NotificationBell` tests pass. Co-authored-by: Marcel <marcel@familienarchiv> Reviewed-on: http://heim-nas:3005/marcel/familienarchiv/pulls/351
This commit was merged in pull request #351.
This commit is contained in:
@@ -55,6 +55,34 @@ async function openDropdownAndClickFirstNotification() {
|
||||
notifButton.click();
|
||||
}
|
||||
|
||||
describe('NotificationBell — cursor and tooltip', () => {
|
||||
it('bell button has cursor-pointer class', async () => {
|
||||
render(NotificationBell);
|
||||
const btn = document.querySelector<HTMLButtonElement>('button[aria-haspopup="true"]')!;
|
||||
expect(btn.classList.contains('cursor-pointer')).toBe(true);
|
||||
});
|
||||
|
||||
it('bell button title equals aria-label when unreadCount is 0', async () => {
|
||||
mockNotificationList.value = [];
|
||||
render(NotificationBell);
|
||||
const btn = document.querySelector<HTMLButtonElement>('button[aria-haspopup="true"]')!;
|
||||
expect(btn.getAttribute('title')).toBe('Benachrichtigungen');
|
||||
expect(btn.getAttribute('aria-label')).toBe(btn.getAttribute('title'));
|
||||
});
|
||||
|
||||
it('bell button title equals aria-label when unreadCount is 3', async () => {
|
||||
mockNotificationList.value = [
|
||||
makeNotification({ id: 'n1' }),
|
||||
makeNotification({ id: 'n2' }),
|
||||
makeNotification({ id: 'n3' })
|
||||
];
|
||||
render(NotificationBell);
|
||||
const btn = document.querySelector<HTMLButtonElement>('button[aria-haspopup="true"]')!;
|
||||
expect(btn.getAttribute('title')).toBe('3 ungelesene Benachrichtigungen');
|
||||
expect(btn.getAttribute('aria-label')).toBe(btn.getAttribute('title'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('NotificationBell', () => {
|
||||
it('handleMarkRead navigates to URL including annotationId when notification has annotationId', async () => {
|
||||
mockNotificationList.value = [makeNotification({ annotationId: 'annot-1' })];
|
||||
|
||||
Reference in New Issue
Block a user