Files
topola-viewer/tests/search.spec.ts
2026-06-30 23:49:53 +02:00

222 lines
7.0 KiB
TypeScript

import {expect, test} from '@playwright/test';
import {setupGedcomRoute} from './helpers';
test.describe('Search functionality and global shortcut', () => {
test.beforeEach(async ({context}) => {
await setupGedcomRoute(context);
});
test('Search works and shortcut "/" focuses and selects all text', async ({
page,
}) => {
await page.goto('/#/view?url=https://example.org/family.ged');
await expect(page.locator('#content')).not.toContainText('Chike');
const searchInput = page.getByPlaceholder('Search for people', {
exact: false,
});
await expect(searchInput).toBeVisible();
// Fill some text, blur, and press '/'
await searchInput.fill('Test Query');
await searchInput.blur();
await expect(searchInput).not.toBeFocused();
await page.keyboard.press('/');
await expect(searchInput).toBeFocused();
// Assert that all text is selected
const isTextSelected = await searchInput.evaluate(
(el: HTMLInputElement) => {
return el.selectionStart === 0 && el.selectionEnd === el.value.length;
},
);
expect(isTextSelected).toBe(true);
// Perform search
await searchInput.fill('chik');
await expect(page.locator('.results')).toContainText('Chike');
await searchInput.press('Enter');
await expect(page.locator('#content')).toContainText('Chike');
});
test('Viewport switching: Search query is preserved when resizing between desktop and mobile', async ({
page,
}) => {
await page.setViewportSize({width: 1200, height: 800});
await page.goto('/#/view?url=https://example.org/family.ged');
const desktopInput = page.getByPlaceholder("Search for people (press '/')");
await desktopInput.fill('chik');
await expect(page.locator('.results')).toContainText('Chike');
// Resize to mobile viewport
await page.setViewportSize({width: 500, height: 800});
const mobileInput = page.getByPlaceholder('Search for people', {
exact: true,
});
await expect(mobileInput).toBeVisible();
await expect(mobileInput).toHaveValue('chik');
await expect(page.locator('.results')).toContainText('Chike');
});
test('Collision safety: Typing "/" inside an active modal input types "/" and does not trigger shortcut', async ({
page,
}) => {
await page.goto('/#/view?url=https://example.org/family.ged');
await page.getByText('Open', {exact: true}).click();
await page.getByText('Load from URL').click();
const modalInput = page.getByPlaceholder('https://');
await expect(modalInput).toBeVisible();
await expect(modalInput).toBeFocused();
await page.keyboard.type('http://test/data');
await expect(modalInput).toHaveValue('http://test/data');
const searchInput = page.getByPlaceholder('Search for people', {
exact: false,
});
await expect(searchInput).not.toBeFocused();
});
test('Modal safety: Pressing "/" while a modal is open does not steal focus even if modal input is blurred', async ({
page,
}) => {
await page.goto('/#/view?url=https://example.org/family.ged');
await page.getByText('Open', {exact: true}).click();
await page.getByText('Load from URL').click();
const modalInput = page.getByPlaceholder('https://');
await expect(modalInput).toBeVisible();
await modalInput.blur();
await page.keyboard.press('/');
const searchInput = page.getByPlaceholder('Search for people', {
exact: false,
});
await expect(searchInput).not.toBeFocused();
});
test('Form control protection: Pressing "/" while focused on a button does not steal focus', async ({
page,
}) => {
await page.goto('/#/view?url=https://example.org/family.ged');
await page.getByText('Open', {exact: true}).click();
await page.getByText('Load from URL').click();
const cancelButton = page.getByRole('button', {name: 'Cancel'});
await cancelButton.focus();
await expect(cancelButton).toBeFocused();
await page.keyboard.press('/');
await expect(cancelButton).toBeFocused();
const searchInput = page.getByPlaceholder('Search for people', {
exact: false,
});
await expect(searchInput).not.toBeFocused();
});
test('Modifier exclusion: Modifier combinations do not trigger the shortcut', async ({
page,
}) => {
await page.goto('/#/view?url=https://example.org/family.ged');
const searchInput = page.getByPlaceholder('Search for people', {
exact: false,
});
await expect(searchInput).not.toBeFocused();
await page.keyboard.press('Control+/');
await expect(searchInput).not.toBeFocused();
await page.keyboard.press('Alt+/');
await expect(searchInput).not.toBeFocused();
await page.keyboard.press('Meta+/');
await expect(searchInput).not.toBeFocused();
});
test('Landing page safety: Pressing "/" on landing page has no effect and doesn\'t throw errors', async ({
page,
}) => {
const errors: string[] = [];
page.on('pageerror', (err) => {
errors.push(err.message);
});
page.on('console', (msg) => {
if (msg.type() === 'error' && !msg.text().includes('Warning:')) {
errors.push(msg.text());
}
});
await page.goto('/');
await page.waitForLoadState('domcontentloaded');
await page.keyboard.press('/');
expect(errors).toEqual([]);
});
test('Question mark safety: Pressing "?" (Shift+/) does not trigger search shortcut', async ({
page,
}) => {
await page.goto('/#/view?url=https://example.org/family.ged');
const searchInput = page.getByPlaceholder('Search for people', {
exact: false,
});
await expect(searchInput).not.toBeFocused();
await page.keyboard.press('?');
await expect(searchInput).not.toBeFocused();
});
test('Dropdown menu interaction: Pressing "/" while dropdown is open focuses search bar', async ({
page,
}) => {
await page.goto('/#/view?url=https://example.org/family.ged');
await page.getByText('View', {exact: true}).click();
await expect(page.getByText('Hourglass chart')).toBeVisible();
await page.keyboard.press('/');
const searchInput = page.getByPlaceholder('Search for people', {
exact: false,
});
await expect(searchInput).toBeFocused();
});
test('Combobox wrapper interaction: Clicking inside search bar wrapper and pressing "/" focuses input', async ({
page,
}) => {
await page.goto('/#/view?url=https://example.org/family.ged');
const searchInput = page.getByPlaceholder('Search for people', {
exact: false,
});
await page.locator('.ui.search').first().click();
await searchInput.blur();
await page.keyboard.press('/');
await expect(searchInput).toBeFocused();
});
test('AltGr compatibility: Control+Alt+/ triggers the shortcut', async ({
page,
}) => {
await page.goto('/#/view?url=https://example.org/family.ged');
const searchInput = page.getByPlaceholder('Search for people', {
exact: false,
});
await expect(searchInput).toBeVisible();
await page.keyboard.press('Control+Alt+/');
await expect(searchInput).toBeFocused();
});
});