Files

110 lines
4.2 KiB
TypeScript

import React from 'react';
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { render, screen, fireEvent, act } from '../../../helpers/render';
import MUserMenu from '../../../../src/mobile/screens/dashboard/MUserMenu';
import { useAuthStore } from '../../../../src/store/authStore';
import { useSettingsStore } from '../../../../src/store/settingsStore';
// FE-MOB-MENU-001 onwards
function seedUser(role: 'user' | 'admin') {
useAuthStore.setState({
isAuthenticated: true,
user: { id: 1, username: 'Maurice', email: 'maurice@trek.app', role, avatar_url: '' } as never,
});
}
describe('MUserMenu', () => {
beforeEach(() => {
seedUser('user');
useSettingsStore.setState(s => ({ settings: { ...s.settings, dark_mode: 'dark' } }));
});
it('FE-MOB-MENU-001: shows profile header and hides the admin entry for regular users', () => {
render(<MUserMenu open onClose={() => {}} />);
expect(screen.getByText('Maurice')).toBeInTheDocument();
expect(screen.getByText('maurice@trek.app')).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Settings' })).toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'Admin Settings' })).not.toBeInTheDocument();
});
it('FE-MOB-MENU-002: shows the admin entry and badge for admins', () => {
seedUser('admin');
render(<MUserMenu open onClose={() => {}} />);
expect(screen.getByRole('button', { name: 'Admin Settings' })).toBeInTheDocument();
expect(screen.getByText('Admin')).toBeInTheDocument();
});
it('FE-MOB-MENU-003: the theme row cycles dark → light → auto', () => {
const updateSetting = vi.fn(async () => {});
useSettingsStore.setState({ updateSetting });
render(<MUserMenu open onClose={() => {}} />);
fireEvent.click(screen.getByRole('button', { name: /Color Mode/ }));
expect(updateSetting).toHaveBeenCalledWith('dark_mode', 'light');
act(() => {
useSettingsStore.setState(s => ({ settings: { ...s.settings, dark_mode: 'light' } }));
});
fireEvent.click(screen.getByRole('button', { name: /Color Mode/ }));
expect(updateSetting).toHaveBeenCalledWith('dark_mode', 'auto');
});
it('FE-MOB-MENU-004: auto cycles back to dark', () => {
const updateSetting = vi.fn(async () => {});
useSettingsStore.setState({ updateSetting });
useSettingsStore.setState(s => ({ settings: { ...s.settings, dark_mode: 'auto' } }));
render(<MUserMenu open onClose={() => {}} />);
fireEvent.click(screen.getByRole('button', { name: /Color Mode/ }));
expect(updateSetting).toHaveBeenCalledWith('dark_mode', 'dark');
});
it('FE-MOB-MENU-005: a legacy boolean dark_mode still resolves to dark', () => {
const updateSetting = vi.fn(async () => {});
useSettingsStore.setState({ updateSetting });
useSettingsStore.setState(s => ({ settings: { ...s.settings, dark_mode: true } }));
render(<MUserMenu open onClose={() => {}} />);
fireEvent.click(screen.getByRole('button', { name: /Color Mode/ }));
expect(updateSetting).toHaveBeenCalledWith('dark_mode', 'light');
});
it('FE-MOB-MENU-006: opening settings closes the menu first', () => {
const onClose = vi.fn();
render(<MUserMenu open onClose={onClose} />);
fireEvent.click(screen.getByRole('button', { name: 'Settings' }));
expect(onClose).toHaveBeenCalledTimes(1);
});
it('FE-MOB-MENU-007: signing out closes the menu and clears the session', async () => {
const onClose = vi.fn();
const logout = vi.fn(async () => {});
useAuthStore.setState({ logout });
render(<MUserMenu open onClose={onClose} />);
await act(async () => {
fireEvent.click(screen.getByRole('button', { name: 'Logout' }));
});
expect(onClose).toHaveBeenCalledTimes(1);
expect(logout).toHaveBeenCalledTimes(1);
});
it('FE-MOB-MENU-008: an uploaded avatar replaces the initial', () => {
useAuthStore.setState({
isAuthenticated: true,
user: { id: 1, username: 'Maurice', email: 'm@trek.app', role: 'user', avatar_url: '/uploads/avatars/m.jpg' } as never,
});
const { container } = render(<MUserMenu open onClose={() => {}} />);
expect(container.querySelector('img')).toHaveAttribute('src', '/uploads/avatars/m.jpg');
});
});