import { test, expect } from '@playwright/test'; test.describe('Insert Modes', () => { // Return HTML fragments directly inserted by the framework function htmlPage(pageNum: number) { return `
Page ${pageNum} Item A
` + `
Page ${pageNum} Item B
`; } // ── get-insert="append" ────────────────────────────────────────────── test('1 — get-insert="append": new content appended after existing', async ({ page }) => { await page.route('**/api/items*', (route) => { const url = new URL(route.request().url(), 'http://localhost'); const p = parseInt(url.searchParams.get('page') || '1'); if (p > 2) { route.fulfill({ status: 200, contentType: 'text/html', body: '' }); } else { route.fulfill({ status: 200, contentType: 'text/html', body: htmlPage(p) }); } }); await page.goto('/e2e/examples/pagination-button.html'); // Page 1 loads await expect(page.getByTestId('item').first()).toBeVisible({ timeout: 5000 }); const firstPageTexts = await page.getByTestId('item').allTextContents(); expect(firstPageTexts).toEqual(['Page 1 Item A', 'Page 1 Item B']); // Load page 2 const loadMoreBtn = page.getByTestId('button-container').locator('[data-nojs-load-more]'); await expect(loadMoreBtn).toBeVisible({ timeout: 5000 }); await loadMoreBtn.click(); await expect(page.getByTestId('item')).toHaveCount(4, { timeout: 5000 }); // Verify order: page 1 items first (original position), then page 2 items (appended) const allItems = await page.getByTestId('item').allTextContents(); expect(allItems[0]).toBe('Page 1 Item A'); expect(allItems[1]).toBe('Page 1 Item B'); expect(allItems[2]).toBe('Page 2 Item A'); expect(allItems[3]).toBe('Page 2 Item B'); }); // ── get-insert="prepend" ───────────────────────────────────────────── test('2 — get-insert="prepend": new content prepended before existing', async ({ page }) => { await page.route('**/api/feed*', (route) => { const url = new URL(route.request().url(), 'http://localhost'); const p = parseInt(url.searchParams.get('page') || '1'); if (p > 2) { route.fulfill({ status: 200, contentType: 'text/html', body: '' }); } else { route.fulfill({ status: 200, contentType: 'text/html', body: htmlPage(p) }); } }); await page.goto('/e2e/examples/insert-prepend.html'); // Page 1 loads await expect(page.getByTestId('item').first()).toBeVisible({ timeout: 5000 }); const firstPageTexts = await page.getByTestId('item').allTextContents(); expect(firstPageTexts).toEqual(['Page 1 Item A', 'Page 1 Item B']); // Load page 2 const loadMoreBtn = page.getByTestId('prepend-container').locator('[data-nojs-load-more]'); await expect(loadMoreBtn).toBeVisible({ timeout: 5000 }); await loadMoreBtn.click(); await expect(page.getByTestId('item')).toHaveCount(4, { timeout: 5000 }); // Verify order: page 2 items first (prepended), then page 1 items const allItems = await page.getByTestId('item').allTextContents(); expect(allItems[0]).toBe('Page 2 Item A'); expect(allItems[1]).toBe('Page 2 Item B'); expect(allItems[2]).toBe('Page 1 Item A'); expect(allItems[3]).toBe('Page 1 Item B'); }); // ── Default (no get-insert): replace ───────────────────────────────── test('3 — default (no get-insert): content replaced on each fetch', async ({ page }) => { await page.route('**/api/users', (route) => { route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify([ { name: 'Alice' }, { name: 'Bob' }, ]), }); }); await page.route('**/api/slow', (route) => { route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ message: 'Done!' }) }); }); await page.route('**/api/error', (route) => { route.fulfill({ status: 500, contentType: 'application/json', body: '{}' }); }); await page.route('**/api/empty', (route) => { route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify([]) }); }); await page.route('**/api/users/1', (route) => { route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ name: 'User One' }) }); }); // Use the existing fetch.html which has no get-insert (replace mode) await page.goto('/e2e/examples/fetch.html'); // First fetch renders const items = page.getByTestId('user-item'); await expect(items.first()).toBeVisible({ timeout: 5000 }); expect(await items.count()).toBe(2); // Content matches the response (replace, not accumulate) await expect(items.first()).toHaveText('Alice'); await expect(items.nth(1)).toHaveText('Bob'); }); // ── Sentinel element ───────────────────────────────────────────────── test('4 — sentinel element exists in append mode', async ({ page }) => { await page.route('**/api/items*', (route) => { const url = new URL(route.request().url(), 'http://localhost'); const p = parseInt(url.searchParams.get('page') || '1'); if (p > 1) { route.fulfill({ status: 200, contentType: 'text/html', body: '' }); } else { route.fulfill({ status: 200, contentType: 'text/html', body: htmlPage(1) }); } }); await page.goto('/e2e/examples/pagination-button.html'); // Wait for page 1 to load await expect(page.getByTestId('item').first()).toBeVisible({ timeout: 5000 }); // Check that the sentinel element exists within the container const sentinel = page.getByTestId('button-container').locator('[data-nojs-sentinel]'); await expect(sentinel).toBeAttached(); // Sentinel should be zero-height and hidden from accessibility await expect(sentinel).toHaveAttribute('aria-hidden', 'true'); const height = await sentinel.evaluate((el) => getComputedStyle(el).height); expect(height).toBe('0px'); }); });