import React from 'react'; import { afterEach, describe, expect, it, vi } from 'vitest'; import { cleanup, fireEvent, render, screen } from '@testing-library/react'; import { FounderTopBar } from '../components/founder-portal/founder-top-bar'; import { FIXTURE_FOUNDER_CLUB } from '../lib/club-fixtures'; import { getPortalPermissions } from '../lib/club-data'; vi.mock('next/navigation', () => ({ useSearchParams: () => new URLSearchParams('preview=demo') })); vi.mock('../lib/firebase-client', () => ({ firestore: {}, firebaseAuth: {} })); afterEach(cleanup); const props = { club: FIXTURE_FOUNDER_CLUB, permissions: getPortalPermissions('owner') }; describe('Founder notification bell', () => { it('opens and closes club updates and preserves demo preview on destination links', () => { render(); const bell = screen.getByRole('button', { name: 'Notifications' }); expect(bell.getAttribute('aria-expanded')).toBe('false'); fireEvent.click(bell); expect(screen.getByRole('region', { name: 'Club updates' })).toBeDefined(); expect(bell.getAttribute('aria-expanded')).toBe('true'); expect(screen.getByRole('link', { name: /2 pending applications/ }).getAttribute('href')).toBe(`/clubs/${props.club.slug}/manage/applications?preview=demo`); expect(screen.getByRole('link', { name: /1 upcoming ride/ }).getAttribute('href')).toBe(`/clubs/${props.club.slug}/manage/rides?preview=demo`); expect(screen.getByRole('link', { name: /Club Wall/ }).getAttribute('href')).toBe(`/clubs/${props.club.slug}/manage/wall?preview=demo`); fireEvent.click(bell); expect(screen.queryByRole('region', { name: 'Club updates' })).toBeNull(); }); it('does not fabricate an unread indicator or empty-inbox claim when counts are unavailable', () => { render(); expect(screen.queryByLabelText(/pending applications/)).toBeNull(); fireEvent.click(screen.getByRole('button', { name: 'Notifications' })); expect(screen.getByRole('link', { name: /Membership applications/ })).toBeDefined(); expect(screen.getByText('Open a section to review current club activity.')).toBeDefined(); }); it('closes on Escape and restores keyboard focus to the bell', () => { render(); const bell = screen.getByRole('button', { name: 'Notifications' }); fireEvent.click(bell); screen.getByRole('button', { name: 'Close notifications' }).focus(); fireEvent.keyDown(document, { key: 'Escape' }); expect(screen.queryByRole('region', { name: 'Club updates' })).toBeNull(); expect(document.activeElement).toBe(bell); }); it('dismisses when clicking outside, following a link, or switching to the rider menu', () => { render(); const bell = screen.getByRole('button', { name: 'Notifications' }); fireEvent.click(bell); fireEvent.mouseDown(document.body); expect(screen.queryByRole('region', { name: 'Club updates' })).toBeNull(); fireEvent.click(bell); const wallLink = screen.getByRole('link', { name: /Club Wall/ }); wallLink.addEventListener('click', (event) => event.preventDefault()); fireEvent.click(wallLink); expect(screen.queryByRole('region', { name: 'Club updates' })).toBeNull(); fireEvent.click(bell); fireEvent.click(screen.getByRole('button', { name: 'Open rider menu' })); expect(screen.queryByRole('region', { name: 'Club updates' })).toBeNull(); expect(screen.getByRole('menu')).toBeDefined(); }); it('closes when club context changes', () => { const view = render(); fireEvent.click(screen.getByRole('button', { name: 'Notifications' })); view.rerender(); expect(screen.queryByRole('region', { name: 'Club updates' })).toBeNull(); }); it('does not expose management destinations or pending counts to ordinary members', () => { render(); expect(screen.queryByLabelText(/pending applications/)).toBeNull(); fireEvent.click(screen.getByRole('button', { name: 'Notifications' })); expect(screen.getByText('Club management updates are available to Owners and Admins.')).toBeDefined(); const panel = screen.getByRole('region', { name: 'Club updates' }); expect(panel.querySelector('a')).toBeNull(); }); });