- Implemented SCSS styles for compact board in departures. - Created styles for feature discovery modal and its elements. - Added navigation styles for desktop and mobile views. - Developed FeatureDiscovery component with guided steps for user onboarding. - Introduced freshness utility to manage data freshness based on timestamps. - Added tests for freshness utility to ensure correct behavior across various scenarios.
116 lines
7.6 KiB
TypeScript
116 lines
7.6 KiB
TypeScript
import { test, expect } from './fixtures';
|
||
|
||
test('theme settings are reachable from navigation and preserve timetable and favorites', async ({ page }, info) => {
|
||
await page.emulateMedia({ colorScheme: 'light' });
|
||
await page.goto('/?demo=1&city=1&stop=101&view=day&date=2026-10-07&route=1');
|
||
await expect(page.locator('.hourDeparture')).toHaveCount(8);
|
||
await page.getByRole('button', { name: 'Добавить остановку в избранное', exact: true }).click();
|
||
await page.getByRole('button', { name: 'Закрыть уведомление', exact: true }).click();
|
||
const nav = page.getByRole('navigation', { name: info.project.name === 'mobile' ? 'Навигация на телефоне' : 'Основная навигация' });
|
||
await nav.getByRole('link', { name: 'Настройки', exact: true }).click();
|
||
await expect(page.getByRole('heading', { name: 'Настройки', exact: true })).toBeVisible();
|
||
await expect(nav.getByRole('link', { name: 'Настройки', exact: true })).toHaveAttribute('aria-current', 'page');
|
||
await expect(page.getByRole('radio', { name: /^Как на устройстве/ })).toBeChecked();
|
||
await page.getByRole('radio', { name: /^Тёмная/ }).check();
|
||
await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark');
|
||
await expect(page.locator('body')).toHaveCSS('background-color', 'rgb(20, 24, 32)');
|
||
await expect(page.locator('meta[name="theme-color"]')).toHaveAttribute('content', '#202735');
|
||
await page.reload();
|
||
await expect(page.getByRole('radio', { name: /^Тёмная/ })).toBeChecked();
|
||
await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark');
|
||
await page.screenshot({ path: `test-results/settings-dark-${info.project.name}.png`, fullPage: true, animations: 'disabled' });
|
||
for (const width of [320, 760, 761, 820, 1050, 1440]) {
|
||
await page.setViewportSize({ width, height: 900 });
|
||
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true);
|
||
}
|
||
await page.getByRole('link', { name: 'К расписанию', exact: true }).click();
|
||
await expect(page).toHaveURL(/city=1&stop=101&view=day&date=2026-10-07&route=1/);
|
||
await expect(page.locator('.hourDeparture')).toHaveCount(8);
|
||
await expect(page.getByRole('button', { name: 'Убрать остановку из избранного', exact: true })).toBeVisible();
|
||
await expect(page.locator('.boardCard')).toHaveCSS('background-color', 'rgb(28, 35, 48)');
|
||
await expect(page.locator('.citySelect')).toHaveCSS('background-color', 'rgb(28, 35, 48)');
|
||
await page.screenshot({ path: `test-results/schedule-dark-${info.project.name}.png`, fullPage: true, animations: 'disabled' });
|
||
await page.getByRole('navigation', { name: 'Основная навигация' }).getByRole('link', { name: /Избранное/ }).click();
|
||
await expect(page.locator('.favoriteCard')).toHaveCount(1);
|
||
await expect(page.locator('.favoriteRoute').first()).toHaveCSS('background-color', 'rgb(28, 35, 48)');
|
||
});
|
||
|
||
test('system appearance follows live changes while explicit themes override it and support keyboard controls', async ({ page }) => {
|
||
await page.emulateMedia({ colorScheme: 'dark' });
|
||
let requests = 0;
|
||
await page.route('**/api/data?**', route => { requests++; return route.abort(); });
|
||
await page.goto('/settings');
|
||
await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark');
|
||
await expect(page.locator('.themeStatus')).toContainText('тёмная тема устройства');
|
||
await page.emulateMedia({ colorScheme: 'light' });
|
||
await expect(page.locator('html')).toHaveAttribute('data-theme', 'light');
|
||
await expect(page.locator('.themeStatus')).toContainText('светлая тема устройства');
|
||
const light = page.getByRole('radio', { name: /^Светлая/ });
|
||
await light.check();
|
||
await page.emulateMedia({ colorScheme: 'dark' });
|
||
await expect(page.locator('html')).toHaveAttribute('data-theme', 'light');
|
||
await light.focus(); await page.keyboard.press('ArrowRight');
|
||
await expect(page.getByRole('radio', { name: /^Тёмная/ })).toBeChecked();
|
||
await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark');
|
||
await page.keyboard.press('ArrowRight');
|
||
await expect(page.getByRole('radio', { name: /^Как на устройстве/ })).toBeChecked();
|
||
await page.emulateMedia({ colorScheme: 'light' });
|
||
await expect(page.locator('html')).toHaveAttribute('data-theme', 'light');
|
||
expect(requests).toBe(0);
|
||
});
|
||
|
||
test('theme changes synchronize between tabs and settings remain available after an offline reload', async ({ page, context }) => {
|
||
await page.goto('/settings');
|
||
await page.evaluate(async () => {
|
||
await navigator.serviceWorker.ready;
|
||
if (!navigator.serviceWorker.controller) await new Promise<void>(resolve => navigator.serviceWorker.addEventListener('controllerchange', () => resolve(), { once: true }));
|
||
});
|
||
const other = await context.newPage();
|
||
await other.goto('/settings');
|
||
await other.getByRole('radio', { name: /^Тёмная/ }).check();
|
||
await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark');
|
||
await expect(page.getByRole('radio', { name: /^Тёмная/ })).toBeChecked();
|
||
await context.setOffline(true);
|
||
await page.reload();
|
||
await expect(page.getByRole('heading', { name: 'Настройки', exact: true })).toBeVisible();
|
||
await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark');
|
||
await page.getByRole('radio', { name: /^Светлая/ }).check();
|
||
await expect(other.locator('html')).toHaveAttribute('data-theme', 'light');
|
||
await page.reload();
|
||
await expect(page.getByRole('radio', { name: /^Светлая/ })).toBeChecked();
|
||
await other.close();
|
||
});
|
||
|
||
test('the saved theme is applied before the application bundle executes', async ({ page }) => {
|
||
await page.emulateMedia({ colorScheme: 'light' });
|
||
await page.addInitScript(() => localStorage.setItem('ridebus:theme', 'dark'));
|
||
const bundle = /\/assets\/index-[^/]+\.js$/;
|
||
await page.route(bundle, route => route.fulfill({ status: 200, contentType: 'application/javascript', body: 'export {};' }));
|
||
await page.goto('/settings');
|
||
await expect(page.locator('#root')).toBeEmpty();
|
||
await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark');
|
||
await expect(page.locator('html')).toHaveCSS('color-scheme', 'dark');
|
||
await expect(page.locator('body')).toHaveCSS('background-color', 'rgb(20, 24, 32)');
|
||
await expect(page.locator('meta[name="theme-color"]')).toHaveAttribute('content', '#202735');
|
||
await page.unroute(bundle);
|
||
await page.reload();
|
||
await expect(page.getByRole('radio', { name: /^Тёмная/ })).toBeChecked();
|
||
});
|
||
|
||
test('blocked preference storage does not prevent changing theme or viewing settings', async ({ page }) => {
|
||
const errors: string[] = []; page.on('pageerror', error => errors.push(error.message));
|
||
await page.emulateMedia({ colorScheme: 'light' });
|
||
await page.addInitScript(() => {
|
||
Storage.prototype.getItem = () => { throw new DOMException('Blocked', 'SecurityError'); };
|
||
Storage.prototype.setItem = () => { throw new DOMException('Blocked', 'SecurityError'); };
|
||
});
|
||
await page.goto('/settings');
|
||
await expect(page.locator('html')).toHaveAttribute('data-theme', 'light');
|
||
await page.getByRole('radio', { name: /^Тёмная/ }).check();
|
||
await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark');
|
||
await expect(page.locator('.themeStatus')).toContainText('браузер не разрешил сохранить выбор');
|
||
await page.reload();
|
||
await expect(page.getByRole('radio', { name: /^Как на устройстве/ })).toBeChecked();
|
||
expect(errors).toEqual([]);
|
||
});
|