74 lines
4.5 KiB
TypeScript
74 lines
4.5 KiB
TypeScript
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(<FounderTopBar {...props} pendingApplicationsCount={2} upcomingRidesCount={1} />);
|
|
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(<FounderTopBar {...props} />);
|
|
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(<FounderTopBar {...props} />);
|
|
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(<FounderTopBar {...props} />);
|
|
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(<FounderTopBar {...props} />);
|
|
fireEvent.click(screen.getByRole('button', { name: 'Notifications' }));
|
|
view.rerender(<FounderTopBar {...props} club={{ ...props.club, id: 'other-club' }} />);
|
|
expect(screen.queryByRole('region', { name: 'Club updates' })).toBeNull();
|
|
});
|
|
it('does not expose management destinations or pending counts to ordinary members', () => {
|
|
render(<FounderTopBar {...props} permissions={getPortalPermissions('member')} pendingApplicationsCount={4} upcomingRidesCount={2} />);
|
|
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();
|
|
});
|
|
});
|