Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions tests/content-datatables.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { test, expect, Page } from '@playwright/test';

/**
* Wait for the DataTable to finish loading.
* The tables show a #table-loading indicator while fetching data,
* then render rows into #mainContentList.
*/
async function waitForTableLoaded(page: Page) {
// Wait for loading indicator to appear then disappear
await expect(page.locator('#table-loading')).toBeHidden({ timeout: 30000 });
// Table should have at least one data row
await expect(page.locator('#mainContentList tbody tr').first()).toBeVisible({ timeout: 15000 });
}

// Content type pages and their expected column headers
const contentPages = [
{ path: '/content/canon/', name: 'Canonical Works', minRows: 50 },
{ path: '/content/monographs/', name: 'Monographs', minRows: 50 },
{ path: '/content/articles/', name: 'Articles', minRows: 50, extraColumn: 'Journal' },
{ path: '/content/booklets/', name: 'Booklets', minRows: 10 },
{ path: '/content/essays/', name: 'Essays', minRows: 20 },
{ path: '/content/papers/', name: 'Papers', minRows: 10 },
{ path: '/content/excerpts/', name: 'Excerpts', minRows: 10 },
{ path: '/content/av/', name: 'Audio/Video', minRows: 20 },
{ path: '/content/reference/', name: 'Reference Shelf', minRows: 5 },
];

test('content index page lists all types', async ({ page }) => {
await page.goto('/content/');
await expect(page).toHaveTitle(/Content/i);

// Verify all content type links exist
for (const { name } of contentPages) {
await expect(page.getByRole('link', { name })).toBeVisible();
}
});

for (const { path, name, minRows, extraColumn } of contentPages) {
test(`${name} table loads with data`, async ({ page }) => {
await page.goto(path);
await waitForTableLoaded(page);

// Standard columns should be present
const headers = page.locator('#mainContentList th');
await expect(headers.filter({ hasText: 'Name' })).toBeVisible();
await expect(headers.filter({ hasText: 'Year' })).toBeVisible();
await expect(headers.filter({ hasText: 'Author' })).toBeVisible();

// Check type-specific extra column
if (extraColumn) {
await expect(headers.filter({ hasText: extraColumn })).toBeVisible();
}

// Verify minimum row count
const rows = page.locator('#mainContentList tbody tr');
const count = await rows.count();
expect(count).toBeGreaterThanOrEqual(minRows);
});
}

test('canon table has clickable entry links', async ({ page }) => {
await page.goto('/content/canon/');
await waitForTableLoaded(page);

// First row should have a link in the Name column
const firstLink = page.locator('#mainContentList tbody tr').first().locator('a').first();
await expect(firstLink).toBeVisible();
const href = await firstLink.getAttribute('href');
expect(href).toBeTruthy();
});

test('articles table sorting by year', async ({ page }) => {
await page.goto('/content/articles/');
await waitForTableLoaded(page);

// Click the Year header to sort
const yearHeader = page.locator('#mainContentList th', { hasText: 'Year' });
await yearHeader.click();

// After sorting, first visible year should be a number
const firstYearCell = page.locator('#mainContentList tbody tr').first().locator('td').nth(2);
const yearText = await firstYearCell.textContent();
expect(yearText?.trim()).toMatch(/^\d{4}$/);
});

test('datatable search/filter works', async ({ page }) => {
await page.goto('/content/monographs/');
await waitForTableLoaded(page);

const initialCount = await page.locator('#mainContentList tbody tr').count();

// DataTables adds a search input — find and use it
const dtSearch = page.locator('input[type="search"]');
if (await dtSearch.isVisible()) {
await dtSearch.fill('meditation');
// Wait for filtering to apply
await page.waitForTimeout(500);
const filteredCount = await page.locator('#mainContentList tbody tr').count();
// Filtered results should be fewer (or equal if all match)
expect(filteredCount).toBeLessThanOrEqual(initialCount);
// At least one result should mention meditation
if (filteredCount > 0) {
const firstRow = page.locator('#mainContentList tbody tr').first();
await expect(firstRow).toContainText(/[Mm]editation/);
}
}
});

test('all content page loads total count', async ({ page }) => {
await page.goto('/content/all');
await waitForTableLoaded(page);

// Should have a large number of entries
const rows = page.locator('#mainContentList tbody tr');
const count = await rows.count();
expect(count).toBeGreaterThanOrEqual(200);
});
53 changes: 53 additions & 0 deletions tests/search.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,56 @@ test('bodhi translations in canon filter', async ({ page }) => {
const translatorPill = page.locator('.Label', { hasText: 'Translator: Bhikkhu Bodhi' }).nth(99);
await expect(translatorPill).toBeVisible();
});

test('empty query shows no results', async ({ page }) => {
await page.goto('/search/?q=');
await expect(page.getByText('filter your results by')).toBeVisible();
await expect(page.getByText('results (')).toHaveCount(0);
});

test('search with no matches shows zero results', async ({ page }) => {
await page.goto('/search/?q=zzznonexistentterm99999');
await waitForSearchResults(page);
await expect(page.getByText('results (0)')).toBeVisible();
});

test('search results update live on typing', async ({ page }) => {
await page.goto('/search/?q=mindfulness');
await waitForSearchResults(page);

const resultsText = page.getByText('results (');
await expect(resultsText).toBeVisible();

// Change query and verify results update without page reload
const searchBox = page.locator('#search-box');
await searchBox.fill('dependent origination');
// Wait for at least one result to appear with new content
await expect(page.locator('.Label', { hasText: 'Dependent' }).first()).toBeVisible();
});

test('filter dropdown changes result types', async ({ page }) => {
await page.goto('/search/?q=meditation&filter=%2Bin%3Aav');
await waitForSearchResults(page);

const searchFilter = page.locator('#search-filter');
const filterLabel = await searchFilter.locator('option:checked').textContent();
expect(filterLabel).toEqual('Audio/Video');

// All results should be AV type
const resultTypes = page.locator('.Counter');
const count = await resultTypes.count();
expect(count).toBeGreaterThan(0);
for (let i = 0; i < count; i++) {
await expect(resultTypes.nth(i)).toContainText(/Audio|Video/);
}
});

test('search preserves query params in URL', async ({ page }) => {
await page.goto('/search/');
const searchBox = page.locator('#search-box');
await searchBox.fill('heart sutra');
await waitForSearchResults(page);

// URL should contain the query
expect(page.url()).toContain('q=heart');
});