Skip to content

Commit 6bacedf

Browse files
committed
test: add e2e tests for search edge cases and content datatables
Search tests added: - Empty query shows filter prompt (no results) - Non-existent term shows zero results - Live search updates on typing - Filter dropdown restricts result types (AV) - Query params preserved in URL Datatable tests (new file): - Content index page lists all 9 content types - Each content type table loads with expected columns and minimum rows - Canon table entries have clickable links - Articles table sorting by year - DataTable search/filter functionality - /content/all page total count >= 200
1 parent 8694733 commit 6bacedf

2 files changed

Lines changed: 170 additions & 0 deletions

File tree

‎tests/content-datatables.spec.ts‎

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { test, expect, Page } from '@playwright/test';
2+
3+
/**
4+
* Wait for the DataTable to finish loading.
5+
* The tables show a #table-loading indicator while fetching data,
6+
* then render rows into #mainContentList.
7+
*/
8+
async function waitForTableLoaded(page: Page) {
9+
// Wait for loading indicator to appear then disappear
10+
await expect(page.locator('#table-loading')).toBeHidden({ timeout: 30000 });
11+
// Table should have at least one data row
12+
await expect(page.locator('#mainContentList tbody tr').first()).toBeVisible({ timeout: 15000 });
13+
}
14+
15+
// Content type pages and their expected column headers
16+
const contentPages = [
17+
{ path: '/content/canon/', name: 'Canonical Works', minRows: 50 },
18+
{ path: '/content/monographs/', name: 'Monographs', minRows: 50 },
19+
{ path: '/content/articles/', name: 'Articles', minRows: 50, extraColumn: 'Journal' },
20+
{ path: '/content/booklets/', name: 'Booklets', minRows: 10 },
21+
{ path: '/content/essays/', name: 'Essays', minRows: 20 },
22+
{ path: '/content/papers/', name: 'Papers', minRows: 10 },
23+
{ path: '/content/excerpts/', name: 'Excerpts', minRows: 10 },
24+
{ path: '/content/av/', name: 'Audio/Video', minRows: 20 },
25+
{ path: '/content/reference/', name: 'Reference Shelf', minRows: 5 },
26+
];
27+
28+
test('content index page lists all types', async ({ page }) => {
29+
await page.goto('/content/');
30+
await expect(page).toHaveTitle(/Content/i);
31+
32+
// Verify all content type links exist
33+
for (const { name } of contentPages) {
34+
await expect(page.getByRole('link', { name })).toBeVisible();
35+
}
36+
});
37+
38+
for (const { path, name, minRows, extraColumn } of contentPages) {
39+
test(`${name} table loads with data`, async ({ page }) => {
40+
await page.goto(path);
41+
await waitForTableLoaded(page);
42+
43+
// Standard columns should be present
44+
const headers = page.locator('#mainContentList th');
45+
await expect(headers.filter({ hasText: 'Name' })).toBeVisible();
46+
await expect(headers.filter({ hasText: 'Year' })).toBeVisible();
47+
await expect(headers.filter({ hasText: 'Author' })).toBeVisible();
48+
49+
// Check type-specific extra column
50+
if (extraColumn) {
51+
await expect(headers.filter({ hasText: extraColumn })).toBeVisible();
52+
}
53+
54+
// Verify minimum row count
55+
const rows = page.locator('#mainContentList tbody tr');
56+
const count = await rows.count();
57+
expect(count).toBeGreaterThanOrEqual(minRows);
58+
});
59+
}
60+
61+
test('canon table has clickable entry links', async ({ page }) => {
62+
await page.goto('/content/canon/');
63+
await waitForTableLoaded(page);
64+
65+
// First row should have a link in the Name column
66+
const firstLink = page.locator('#mainContentList tbody tr').first().locator('a').first();
67+
await expect(firstLink).toBeVisible();
68+
const href = await firstLink.getAttribute('href');
69+
expect(href).toBeTruthy();
70+
});
71+
72+
test('articles table sorting by year', async ({ page }) => {
73+
await page.goto('/content/articles/');
74+
await waitForTableLoaded(page);
75+
76+
// Click the Year header to sort
77+
const yearHeader = page.locator('#mainContentList th', { hasText: 'Year' });
78+
await yearHeader.click();
79+
80+
// After sorting, first visible year should be a number
81+
const firstYearCell = page.locator('#mainContentList tbody tr').first().locator('td').nth(2);
82+
const yearText = await firstYearCell.textContent();
83+
expect(yearText?.trim()).toMatch(/^\d{4}$/);
84+
});
85+
86+
test('datatable search/filter works', async ({ page }) => {
87+
await page.goto('/content/monographs/');
88+
await waitForTableLoaded(page);
89+
90+
const initialCount = await page.locator('#mainContentList tbody tr').count();
91+
92+
// DataTables adds a search input — find and use it
93+
const dtSearch = page.locator('input[type="search"]');
94+
if (await dtSearch.isVisible()) {
95+
await dtSearch.fill('meditation');
96+
// Wait for filtering to apply
97+
await page.waitForTimeout(500);
98+
const filteredCount = await page.locator('#mainContentList tbody tr').count();
99+
// Filtered results should be fewer (or equal if all match)
100+
expect(filteredCount).toBeLessThanOrEqual(initialCount);
101+
// At least one result should mention meditation
102+
if (filteredCount > 0) {
103+
const firstRow = page.locator('#mainContentList tbody tr').first();
104+
await expect(firstRow).toContainText(/[Mm]editation/);
105+
}
106+
}
107+
});
108+
109+
test('all content page loads total count', async ({ page }) => {
110+
await page.goto('/content/all');
111+
await waitForTableLoaded(page);
112+
113+
// Should have a large number of entries
114+
const rows = page.locator('#mainContentList tbody tr');
115+
const count = await rows.count();
116+
expect(count).toBeGreaterThanOrEqual(200);
117+
});

‎tests/search.spec.ts‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,56 @@ test('bodhi translations in canon filter', async ({ page }) => {
6464
const translatorPill = page.locator('.Label', { hasText: 'Translator: Bhikkhu Bodhi' }).nth(99);
6565
await expect(translatorPill).toBeVisible();
6666
});
67+
68+
test('empty query shows no results', async ({ page }) => {
69+
await page.goto('/search/?q=');
70+
await expect(page.getByText('filter your results by')).toBeVisible();
71+
await expect(page.getByText('results (')).toHaveCount(0);
72+
});
73+
74+
test('search with no matches shows zero results', async ({ page }) => {
75+
await page.goto('/search/?q=zzznonexistentterm99999');
76+
await waitForSearchResults(page);
77+
await expect(page.getByText('results (0)')).toBeVisible();
78+
});
79+
80+
test('search results update live on typing', async ({ page }) => {
81+
await page.goto('/search/?q=mindfulness');
82+
await waitForSearchResults(page);
83+
84+
const resultsText = page.getByText('results (');
85+
await expect(resultsText).toBeVisible();
86+
87+
// Change query and verify results update without page reload
88+
const searchBox = page.locator('#search-box');
89+
await searchBox.fill('dependent origination');
90+
// Wait for at least one result to appear with new content
91+
await expect(page.locator('.Label', { hasText: 'Dependent' }).first()).toBeVisible();
92+
});
93+
94+
test('filter dropdown changes result types', async ({ page }) => {
95+
await page.goto('/search/?q=meditation&filter=%2Bin%3Aav');
96+
await waitForSearchResults(page);
97+
98+
const searchFilter = page.locator('#search-filter');
99+
const filterLabel = await searchFilter.locator('option:checked').textContent();
100+
expect(filterLabel).toEqual('Audio/Video');
101+
102+
// All results should be AV type
103+
const resultTypes = page.locator('.Counter');
104+
const count = await resultTypes.count();
105+
expect(count).toBeGreaterThan(0);
106+
for (let i = 0; i < count; i++) {
107+
await expect(resultTypes.nth(i)).toContainText(/Audio|Video/);
108+
}
109+
});
110+
111+
test('search preserves query params in URL', async ({ page }) => {
112+
await page.goto('/search/');
113+
const searchBox = page.locator('#search-box');
114+
await searchBox.fill('heart sutra');
115+
await waitForSearchResults(page);
116+
117+
// URL should contain the query
118+
expect(page.url()).toContain('q=heart');
119+
});

0 commit comments

Comments
 (0)