feat(settings): add always-show-booking-routes account setting

New map_always_show_routes account setting, defaulting to off, i18n'd
across all 22 supported locales. Lives in Display > Travel & map,
directly under Booking route labels — its closest sibling — using
that section's immediate-save On/Off pattern rather than a separate
toggle+Save flow, since it's a booking-display preference, not a map
render-config option.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Dieter Blomme
2026-07-07 14:24:46 +02:00
committed by Maurice
parent c43bb62f08
commit 4c13b4e40f
25 changed files with 106 additions and 3 deletions
@@ -1,5 +1,5 @@
// FE-COMP-DISPLAY-001 to FE-COMP-DISPLAY-027
import { render, screen } from '../../../tests/helpers/render';
import { render, screen, within } from '../../../tests/helpers/render';
import userEvent from '@testing-library/user-event';
import { http, HttpResponse } from 'msw';
import { server } from '../../../tests/helpers/msw/server';
@@ -127,11 +127,38 @@ describe('DisplaySettingsTab', () => {
it('FE-COMP-DISPLAY-025: blur booking codes On button is active when blur_booking_codes is true', () => {
seedStore(useSettingsStore, { settings: buildSettings({ blur_booking_codes: true }) });
render(<DisplaySettingsTab />);
const onButtons = screen.getAllByText(/^On$/i);
const blurOnBtn = onButtons[1].closest('button')!;
const block = screen.getByText(/blur booking codes/i).closest('div')!;
const blurOnBtn = within(block).getByText(/^On$/i).closest('button')!;
expect(blurOnBtn.style.border).toContain('var(--text-primary)');
});
it('FE-COMP-DISPLAY-030: shows Always show booking routes next to Booking route labels', () => {
render(<DisplaySettingsTab />);
const bookingLabels = screen.getByText(/booking route labels/i);
const alwaysShow = screen.getByText(/always show booking routes/i);
expect(alwaysShow).toBeInTheDocument();
// Adjacent siblings within the Travel & Map section: alwaysShow's block
// immediately follows bookingLabels' block.
expect(bookingLabels.closest('div')!.nextElementSibling).toBe(alwaysShow.closest('div'));
});
it('FE-COMP-DISPLAY-031: always-show-routes Off button is active by default (unset)', () => {
render(<DisplaySettingsTab />);
const block = screen.getByText(/always show booking routes/i).closest('div')!;
const offBtn = within(block).getByText(/^Off$/i).closest('button')!;
expect(offBtn.style.border).toContain('var(--text-primary)');
});
it('FE-COMP-DISPLAY-032: clicking On for always-show-routes calls updateSetting with map_always_show_routes true', async () => {
const user = userEvent.setup();
const updateSetting = vi.fn().mockResolvedValue(undefined);
seedStore(useSettingsStore, { settings: buildSettings(), updateSetting });
render(<DisplaySettingsTab />);
const block = screen.getByText(/always show booking routes/i).closest('div')!;
await user.click(within(block).getByText(/^On$/i));
expect(updateSetting).toHaveBeenCalledWith('map_always_show_routes', true);
});
it('FE-COMP-DISPLAY-026: updateSetting failure shows toast error', async () => {
const user = userEvent.setup();
const updateSetting = vi.fn().mockRejectedValue(new Error('Server error'));
@@ -265,6 +265,37 @@ export default function DisplaySettingsTab(): React.ReactElement {
<p className="text-xs mt-1 text-content-faint">{t('settings.bookingLabelsHint')}</p>
</div>
{/* Always show booking routes */}
<div>
<label className="block text-sm font-medium mb-2 text-content-secondary">{t('settings.alwaysShowRoutes')}</label>
<div className="flex gap-3">
{[
{ value: true, label: t('settings.on') || 'On' },
{ value: false, label: t('settings.off') || 'Off' },
].map(opt => (
<button
key={String(opt.value)}
onClick={async () => {
try { await updateSetting('map_always_show_routes', opt.value) }
catch (e: unknown) { toast.error(e instanceof Error ? e.message : t('common.error')) }
}}
style={{
display: 'flex', alignItems: 'center', gap: 8,
padding: '10px 20px', borderRadius: 10, cursor: 'pointer',
fontFamily: 'inherit', fontSize: 'calc(14px * var(--fs-scale-body, 1))', fontWeight: 500,
border: (settings.map_always_show_routes === true) === opt.value ? '2px solid var(--text-primary)' : '2px solid var(--border-primary)',
background: (settings.map_always_show_routes === true) === opt.value ? 'var(--bg-hover)' : 'var(--bg-card)',
color: 'var(--text-primary)',
transition: 'all 0.15s',
}}
>
{opt.label}
</button>
))}
</div>
<p className="text-xs mt-1 text-content-faint">{t('settings.alwaysShowRoutesHint')}</p>
</div>
{/* Explore places on the map (POI category pill) */}
<div>
<label className="block text-sm font-medium mb-2 text-content-secondary">{t('settings.mapPoiPill')}</label>