Files
ridebus-pwa/e2e/navigation.spec.ts
Zakhar Timoshenko 6fe039cefa feat: add StopsMap and WalkingDirections components for enhanced map functionality
- Implemented StopsMap component to display bus stops on a map using MapLibre GL.
- Integrated walking directions feature in WalkingDirections component to calculate routes from user location to selected bus stop.
- Added geo utility functions for coordinate validation and distance calculation.
- Created route grouping and stop filtering logic in routeGroups module.
- Developed custom hooks for managing stop routes with React Query.
- Added tests for routing and stop functionalities to ensure reliability.
2026-10-08 00:18:10 +03:00

102 lines
6.5 KiB
TypeScript

import { test, expect, type Page } from './fixtures';
async function offlineReady(page: Page) {
await page.evaluate(async () => {
await navigator.serviceWorker.ready;
if (!navigator.serviceWorker.controller) await new Promise<void>(resolve => navigator.serviceWorker.addEventListener('controllerchange', () => resolve(), { once: true }));
});
}
test('four sections fit phones and tablets and the home picker stays compact', async ({ page }) => {
await page.goto('/?demo=1&city=1&stop=101');
await expect(page.locator('.departureRow')).toHaveCount(8);
await expect(page.locator('.compactPicker .stopItem')).toHaveCount(0);
for (const width of [320, 390, 760, 761, 820, 1050, 1120, 1440]) {
await page.setViewportSize({ width, height: 900 });
const nav = page.getByRole('navigation', { name: width <= 760 ? 'Навигация на телефоне' : 'Основная навигация' });
await expect(nav.getByRole('link')).toHaveCount(4);
for (const name of ['Главная', 'Остановки', 'Избранное', 'Настройки']) await expect(nav.getByRole('link', { name: new RegExp(`^${name}`) })).toBeVisible();
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true);
expect((await page.locator('.compactPicker').boundingBox())!.height).toBeLessThan(300);
}
await page.setViewportSize({ width: 390, height: 844 });
const nav = page.getByRole('navigation', { name: 'Навигация на телефоне' });
for (const [name, path] of [['Остановки', '/stops'], ['Избранное', '/favorites'], ['Настройки', '/settings'], ['Главная', '/']]) {
await nav.getByRole('link', { name: new RegExp(`^${name}`) }).click();
await expect.poll(() => new URL(page.url()).pathname).toBe(path);
await expect(nav.getByRole('link', { name: new RegExp(`^${name}`) })).toHaveAttribute('aria-current', 'page');
}
});
test('choosing a stop from the home dialog closes it and scrolls to departures on mobile', async ({ page }) => {
await page.setViewportSize({ width: 390, height: 844 });
await page.addInitScript(() => {
const original = HTMLElement.prototype.scrollIntoView;
(window as any).stopScrolls = [];
HTMLElement.prototype.scrollIntoView = function(options) {
if (this.id === 'departures') (window as any).stopScrolls.push(options);
original.call(this, options);
};
});
await page.goto('/?demo=1&city=1');
const trigger = page.getByRole('button', { name: 'Выбрать остановку', exact: true });
await trigger.click();
const dialog = page.getByRole('dialog', { name: 'Выбрать остановку', exact: true });
await expect(dialog).toBeVisible();
// Opening the chooser does not focus search or summon the phone keyboard.
await expect(dialog.getByRole('searchbox')).not.toBeFocused();
await dialog.getByRole('searchbox').fill('Площадь');
await dialog.locator('.stopItem').click();
await expect(dialog).toHaveCount(0);
await expect(page.locator('#board-title')).toHaveText('Площадь Свободы');
await expect(page.locator('#departures')).toBeFocused();
await expect.poll(() => page.evaluate(() => (window as any).stopScrolls.at(-1)?.behavior)).toBe('smooth');
await expect.poll(() => page.locator('#board-title').evaluate(element => element.getBoundingClientRect().top)).toBeLessThan(180);
await expect(trigger).toContainText('Площадь Свободы');
await page.emulateMedia({ reducedMotion: 'reduce' });
await trigger.click();
await dialog.getByRole('searchbox').fill('Площадь');
await dialog.locator('.stopItem').click();
await expect.poll(() => page.evaluate(() => (window as any).stopScrolls.length)).toBe(2);
await expect.poll(() => page.evaluate(() => (window as any).stopScrolls.at(-1)?.behavior)).toBe('auto');
});
test('catalog opens a stop card and its schedule on the home page while retaining the date', async ({ page }) => {
await page.setViewportSize({ width: 320, height: 780 });
await page.goto('/stops?demo=1&city=1&view=day&date=2026-10-08');
await page.getByRole('searchbox').fill('Площадь');
await page.locator('.stopItem').click();
await expect(page.locator('.stopPassport')).toBeFocused();
await expect(page.locator('#departures')).toHaveCount(0);
await page.getByRole('button', { name: 'Открыть расписание', exact: true }).click();
await expect(page.locator('#departures')).toBeFocused();
await expect(page).toHaveURL(/\/\?.*stop=101.*date=2026-10-08/);
await expect(page.getByLabel('Дата поездки', { exact: true })).toHaveValue('2026-10-08');
await expect(page.locator('.hourDeparture')).toHaveCount(32);
await expect.poll(() => page.locator('#board-title').evaluate(element => element.getBoundingClientRect().top)).toBeLessThan(180);
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true);
});
test('disconnecting now shows a single status and older departures receive a warning later', async ({ page, context }) => {
await page.clock.install({ time: new Date('2026-10-07T09:00:00Z') });
await page.goto('/?demo=1&city=1&stop=101');
await expect(page.locator('.departureRow')).toHaveCount(8);
await page.getByRole('button', { name: 'Добавить остановку в избранное', exact: true }).click();
await page.getByRole('button', { name: 'Сохранить поездку', exact: true }).click();
await page.getByRole('dialog').getByRole('button', { name: 'Сохранить поездку', exact: true }).click();
await expect(page.locator('.quickTripCard')).toHaveCount(1);
await offlineReady(page);
await context.setOffline(true);
await expect(page.locator('.offlineBanner')).toHaveCount(1);
await expect(page.locator('.savedNotice, .tripSaved, .alertSaved')).toHaveCount(0);
await expect(page.locator('main')).not.toContainText('устарев');
await page.reload();
await expect(page.locator('.departureRow')).toHaveCount(8);
await expect(page.locator('.savedNotice, .tripSaved, .alertSaved')).toHaveCount(0);
await page.clock.fastForward('00:31:00');
await expect(page.locator('.savedNotice')).toContainText('Расписание давно не обновлялось');
await expect(page.locator('.tripSaved')).toContainText('Расписание давно не обновлялось');
await page.getByRole('navigation').getByRole('link', { name: /Избранное/ }).click();
await expect(page.locator('.favoriteStatus')).toContainText('Расписание давно не обновлялось');
});