1import { test, expect } from '@playwright/test';
2
3const httpHost = process.env.HTTP_HOST
4
5if (typeof httpHost !== 'string') {
6    throw new Error('Environment variable "HTTP_HOST" is not set.')
7}
8
9test.beforeEach(async ({ page }) => {
10    await page.goto(httpHost);
11});
12
13const openSearchModal = async (page) => {
14    await page.getByRole('button', {name: 'Search'}).click();
15    const modal = await page.getByRole('dialog', { name: 'Search modal' });
16
17    // Wait for the modal animation to finish
18    await expect(page.locator('#search-modal__backdrop.show')).not.toHaveClass('showing');
19
20    expect(modal).toBeVisible();
21    return modal;
22}
23
24const expectModalToBeHidden = async (page, modal) => {
25    await expect(page.locator('#search-modal__backdrop')).not.toHaveClass(['show', 'hiding']);
26    await expect(modal).toBeHidden();
27}
28
29const expectOption = async (modal, name) => {
30    await expect(modal.getByRole('option', { name })).toBeVisible();
31}
32
33const expectSelectedOption = async (modal, name) => {
34    await expect(modal.getByRole('option', { name, selected: true })).toBeVisible();
35}
36
37test('should open search modal when search button is clicked', async ({ page }) => {
38    const searchModal = await openSearchModal(page);
39    await expect(searchModal).toBeVisible();
40});
41
42test('should disable window scroll when search modal is open', async ({ page }) => {
43    await openSearchModal(page);
44    await page.mouse.wheel(0, 100);
45    await page.waitForTimeout(100);
46    const currentScrollY = await page.evaluate(() => window.scrollY);
47    expect(currentScrollY).toBe(0);
48});
49
50test('should focus on search input when modal is opened', async ({ page }) => {
51    const modal = await openSearchModal(page);
52    const searchInput = modal.getByRole('searchbox', { name: 'Search docs' });
53    await expect(searchInput).toBeFocused();
54    await expect(searchInput).toHaveValue('');
55});
56
57test('should close search modal when close button is clicked', async ({ page }) => {
58    const modal = await openSearchModal(page);
59    await modal.getByRole('button', { name: 'Close' }).click();
60    await expectModalToBeHidden(page, modal);
61});
62
63test('should re-enable window scroll when search modal is closed', async ({ page }) => {
64    const modal = await openSearchModal(page);
65    await modal.getByRole('button', { name: 'Close' }).click();
66    await expectModalToBeHidden(page, modal);
67    await page.mouse.wheel(0, 100);
68    await page.waitForTimeout(100); // wait for scroll event to be processed
69    const currentScrollY = await page.evaluate(() => window.scrollY);
70    expect(currentScrollY).toBe(100);
71});
72
73test('should close search modal when Escape key is pressed', async ({ page }) => {
74    const modal = await openSearchModal(page);
75    await page.keyboard.press('Escape');
76    await expectModalToBeHidden(page, modal);
77});
78
79test('should close search modal when clicking outside of it', async ({ page }) => {
80    const modal = await openSearchModal(page);
81    await page.click('#search-modal__backdrop', { position: { x: 10, y: 10 } });
82    await expectModalToBeHidden(page, modal);
83});
84
85test('should perform search and display results', async ({ page }) => {
86    const modal = await openSearchModal(page);
87    await modal.getByRole('searchbox').fill('array');
88    await expect(
89        await modal.getByRole('listbox', { name: 'Search results' }).getByRole('option')
90    ).toHaveCount(30);
91});
92
93test('should navigate through search results with arrow keys', async ({ page }) => {
94    const modal = await openSearchModal(page);
95    await modal.getByRole('searchbox').fill('strlen');
96    await expectOption(modal, /^strlen$/);
97
98    await page.keyboard.press('ArrowDown');
99    await expectSelectedOption(modal, /^strlen$/);
100
101    await page.keyboard.press('ArrowDown');
102    await page.keyboard.press('ArrowDown');
103    await page.keyboard.press('ArrowDown');
104    await expectSelectedOption(modal, /^mb_strlen$/);
105
106    await page.keyboard.press('ArrowUp');
107    await expectSelectedOption(modal, /^iconv_strlen$/);
108});
109
110test('should navigate to selected result page when Enter is pressed', async ({ page }) => {
111    const modal = await openSearchModal(page);
112    await modal.getByRole('searchbox').fill('strpos');
113    await expectOption(modal, /^strpos$/);
114
115    await page.keyboard.press('ArrowDown');
116    await page.keyboard.press('Enter');
117    await expect(page).toHaveURL(`http://${httpHost}/manual/en/function.strpos.php`);
118});
119
120test('should navigate to search page when Enter is pressed with no selection', async ({ page }) => {
121    const modal = await openSearchModal(page);
122    await modal.getByRole('searchbox').fill('php basics');
123    await page.keyboard.press('Enter');
124    await expect(page).toHaveURL(`http://${httpHost}/search.php?lang=en&q=php%20basics`);
125});
126