Files
TREK/client/tests/unit/mobile/settings/MSettingsGeneral.test.tsx
T

160 lines
7.2 KiB
TypeScript

// FE-MOB-SET-001 onwards
import { describe, it, expect, vi, beforeEach } from 'vitest';
import userEvent from '@testing-library/user-event';
import { render, screen } from '../../../helpers/render';
import { resetAllStores, seedStore } from '../../../helpers/store';
import { buildSettings } from '../../../helpers/factories';
import { useSettingsStore } from '../../../../src/store/settingsStore';
import { ToastContainer } from '../../../../src/components/shared/Toast';
import MSettingsGeneral from '../../../../src/mobile/screens/settings/MSettingsGeneral';
describe('MSettingsGeneral', () => {
beforeEach(() => {
resetAllStores();
seedStore(useSettingsStore, {
settings: buildSettings({ language: 'en', temperature_unit: 'fahrenheit', default_currency: '' }),
});
});
it('FE-MOB-SET-001: renders the Language & region and Travel & map cards with the current values', () => {
render(<MSettingsGeneral />);
expect(screen.getByText('Language & region')).toBeInTheDocument();
expect(screen.getByText('Travel & map')).toBeInTheDocument();
expect(screen.getByText('English')).toBeInTheDocument();
expect(screen.getByText('Trip currency')).toBeInTheDocument();
expect(screen.getByRole('button', { name: '°F Fahrenheit' })).toHaveAttribute('aria-pressed', 'true');
});
it('FE-MOB-SET-002: unit segments persist the preference via updateSetting', async () => {
const user = userEvent.setup();
const updateSetting = vi.fn().mockResolvedValue(undefined);
seedStore(useSettingsStore, {
settings: buildSettings({ language: 'en', temperature_unit: 'fahrenheit' }),
updateSetting,
});
render(<MSettingsGeneral />);
await user.click(screen.getByRole('button', { name: '°C Celsius' }));
expect(updateSetting).toHaveBeenCalledWith('temperature_unit', 'celsius');
});
it('FE-MOB-SET-003: the language picker sheet lists the real locales and saves the choice', async () => {
const user = userEvent.setup();
const updateSetting = vi.fn().mockResolvedValue(undefined);
seedStore(useSettingsStore, {
settings: buildSettings({ language: 'en' }),
updateSetting,
});
render(<MSettingsGeneral />);
await user.click(screen.getByRole('button', { name: /English/ }));
await user.click(await screen.findByRole('button', { name: 'Deutsch' }));
expect(updateSetting).toHaveBeenCalledWith('language', 'de');
});
it('FE-MOB-SET-004: a chosen display currency is shown with its symbol', () => {
seedStore(useSettingsStore, { settings: buildSettings({ language: 'en', default_currency: 'EUR' }) });
render(<MSettingsGeneral />);
expect(screen.getByRole('button', { name: 'EUR — €' })).toBeInTheDocument();
});
it('FE-MOB-SET-005: the currency sheet saves a pick and can fall back to the trip currency', async () => {
const user = userEvent.setup();
const updateSetting = vi.fn().mockResolvedValue(undefined);
seedStore(useSettingsStore, {
settings: buildSettings({ language: 'en', default_currency: '' }),
updateSetting,
});
render(<MSettingsGeneral />);
await user.click(screen.getByRole('button', { name: 'Trip currency' }));
await user.click(await screen.findByRole('button', { name: 'CHF — CHF' }));
expect(updateSetting).toHaveBeenCalledWith('default_currency', 'CHF');
});
it('FE-MOB-SET-006: distance and time-format segments persist their preference', async () => {
const user = userEvent.setup();
const updateSetting = vi.fn().mockResolvedValue(undefined);
seedStore(useSettingsStore, {
settings: buildSettings({ language: 'en', distance_unit: 'metric', time_format: '24h' }),
updateSetting,
});
render(<MSettingsGeneral />);
expect(screen.getByRole('button', { name: 'km Metric' })).toHaveAttribute('aria-pressed', 'true');
await user.click(screen.getByRole('button', { name: 'mi Imperial' }));
expect(updateSetting).toHaveBeenCalledWith('distance_unit', 'imperial');
await user.click(screen.getByRole('button', { name: '12h' }));
expect(updateSetting).toHaveBeenCalledWith('time_format', '12h');
});
it('FE-MOB-SET-007: unset unit preferences fall back to the metric/24h defaults', () => {
seedStore(useSettingsStore, {
settings: buildSettings({ language: 'en', temperature_unit: undefined, distance_unit: undefined, time_format: undefined }),
});
render(<MSettingsGeneral />);
expect(screen.getByRole('button', { name: '°C Celsius' })).toHaveAttribute('aria-pressed', 'true');
expect(screen.getByRole('button', { name: 'km Metric' })).toHaveAttribute('aria-pressed', 'true');
expect(screen.getByRole('button', { name: '24h' })).toHaveAttribute('aria-pressed', 'true');
});
it('FE-MOB-SET-008: the travel toggles reflect their opt-in / opt-out defaults', () => {
render(<MSettingsGeneral />);
expect(screen.getByRole('switch', { name: 'Booking route labels' })).toHaveAttribute('aria-checked', 'false');
expect(screen.getByRole('switch', { name: 'Explore places on the map' })).toHaveAttribute('aria-checked', 'true');
expect(screen.getByRole('switch', { name: 'Blur Booking Codes' })).toHaveAttribute('aria-checked', 'false');
expect(screen.getByRole('switch', { name: 'Optimize route from accommodation' })).toHaveAttribute('aria-checked', 'true');
});
it('FE-MOB-SET-009: flipping a travel toggle persists the new boolean', async () => {
const user = userEvent.setup();
const updateSetting = vi.fn().mockResolvedValue(undefined);
seedStore(useSettingsStore, {
settings: buildSettings({ language: 'en', map_booking_labels: false, map_poi_pill_enabled: true }),
updateSetting,
});
render(<MSettingsGeneral />);
await user.click(screen.getByRole('switch', { name: 'Booking route labels' }));
expect(updateSetting).toHaveBeenCalledWith('map_booking_labels', true);
await user.click(screen.getByRole('switch', { name: 'Explore places on the map' }));
expect(updateSetting).toHaveBeenCalledWith('map_poi_pill_enabled', false);
await user.click(screen.getByRole('switch', { name: 'Blur Booking Codes' }));
expect(updateSetting).toHaveBeenCalledWith('blur_booking_codes', true);
await user.click(screen.getByRole('switch', { name: 'Optimize route from accommodation' }));
expect(updateSetting).toHaveBeenCalledWith('optimize_from_accommodation', false);
});
it('FE-MOB-SET-010: a rejected save shows the server message as a toast', async () => {
const user = userEvent.setup();
seedStore(useSettingsStore, {
settings: buildSettings({ language: 'en' }),
updateSetting: vi.fn().mockRejectedValue(new Error('Error saving setting')),
});
render(<><ToastContainer /><MSettingsGeneral /></>);
await user.click(screen.getByRole('button', { name: '°F Fahrenheit' }));
await screen.findByText('Error saving setting');
});
it('FE-MOB-SET-011: a non-Error rejection falls back to the generic message', async () => {
const user = userEvent.setup();
seedStore(useSettingsStore, {
settings: buildSettings({ language: 'en' }),
updateSetting: vi.fn().mockRejectedValue('boom'),
});
render(<><ToastContainer /><MSettingsGeneral /></>);
await user.click(screen.getByRole('button', { name: '°F Fahrenheit' }));
await screen.findByText('Error');
});
});