From other session: adapter-html/sx/dom fixes, orchestration improvements, examples-content refactoring, SPA navigation test updates, WASM copies synced. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
101 lines
4.2 KiB
JavaScript
101 lines
4.2 KiB
JavaScript
// SPA navigation tests — verify header, nav, and content survive navigation
|
|
// Tests that OOB swaps update nav while preserving the header island,
|
|
// that sx-select extracts content without nesting, and that rendering
|
|
// errors are scoped to #sx-content via error-boundary.
|
|
|
|
const { test, expect } = require('playwright/test');
|
|
const BASE_URL = process.env.SX_TEST_URL || 'http://localhost:8013';
|
|
|
|
test.describe('SPA navigation', () => {
|
|
|
|
test('no #sx-content nesting after SPA nav', async ({ page }) => {
|
|
await page.goto(BASE_URL + '/sx/(geography)', { waitUntil: 'networkidle' });
|
|
await expect(page.locator('#sx-content')).toHaveCount(1);
|
|
|
|
// Navigate via SPA link to Reactive Islands
|
|
await page.click('a[sx-get*="(geography.(reactive))"]');
|
|
await page.waitForTimeout(4000);
|
|
|
|
// Must still be exactly 1 #sx-content — no nesting
|
|
await expect(page.locator('#sx-content')).toHaveCount(1);
|
|
});
|
|
|
|
test('content renders after SPA nav (not empty boundary)', async ({ page }) => {
|
|
await page.goto(BASE_URL + '/sx/(geography)', { waitUntil: 'networkidle' });
|
|
|
|
await page.click('a[sx-get*="(geography.(reactive))"]');
|
|
await page.waitForTimeout(4000);
|
|
|
|
// Content should have actual page text
|
|
await expect(page.locator('#sx-content')).toContainText('Architecture', { timeout: 5000 });
|
|
});
|
|
|
|
test('nav updates via OOB on SPA navigation', async ({ page }) => {
|
|
await page.goto(BASE_URL + '/sx/(geography)', { waitUntil: 'networkidle' });
|
|
await expect(page.locator('#sx-nav')).toContainText('Geography');
|
|
|
|
await page.click('a[sx-get*="(geography.(reactive))"]');
|
|
await page.waitForTimeout(4000);
|
|
|
|
// Nav should update to show the new page
|
|
await expect(page.locator('#sx-nav')).toContainText('Reactive Islands');
|
|
});
|
|
|
|
test('header island survives SPA nav', async ({ page }) => {
|
|
await page.goto(BASE_URL + '/sx/(geography)', { waitUntil: 'networkidle' });
|
|
await expect(page.locator('[data-sx-island="layouts/header"]')).toHaveCount(1);
|
|
|
|
await page.click('a[sx-get*="(geography.(reactive))"]');
|
|
await page.waitForTimeout(4000);
|
|
|
|
await expect(page.locator('[data-sx-island="layouts/header"]')).toHaveCount(1);
|
|
await expect(page.locator('[data-sx-island="layouts/header"]')).toContainText('sx');
|
|
});
|
|
|
|
test('nav updates even when content has render error', async ({ page }) => {
|
|
await page.goto(BASE_URL + '/sx/(geography.(hypermedia.(example)))', { waitUntil: 'networkidle' });
|
|
await expect(page.locator('#sx-nav')).toContainText('Examples');
|
|
|
|
await page.click('a[sx-get*="click-to-load"]');
|
|
await page.waitForTimeout(4000);
|
|
|
|
// Nav should still update despite content error
|
|
await expect(page.locator('#sx-nav')).toContainText('Click to Load');
|
|
});
|
|
|
|
test('render error scoped to #sx-content via error-boundary', async ({ page }) => {
|
|
await page.goto(BASE_URL + '/sx/(geography.(hypermedia.(example)))', { waitUntil: 'networkidle' });
|
|
|
|
await page.click('a[sx-get*="click-to-load"]');
|
|
await page.waitForTimeout(4000);
|
|
|
|
// Error should be inside the error boundary within #sx-content
|
|
const errors = page.locator('#sx-content .sx-render-error');
|
|
await expect(errors).toHaveCount(1);
|
|
await expect(errors).toContainText('Render error');
|
|
|
|
// Header should still be intact
|
|
await expect(page.locator('[data-sx-island="layouts/header"]')).toHaveCount(1);
|
|
});
|
|
|
|
test('multiple SPA navigations maintain single #sx-content', async ({ page }) => {
|
|
await page.goto(BASE_URL + '/sx/(geography)', { waitUntil: 'networkidle' });
|
|
await expect(page.locator('#sx-content')).toHaveCount(1);
|
|
|
|
// First navigation
|
|
await page.click('a[sx-get*="(geography.(reactive))"]');
|
|
await page.waitForTimeout(4000);
|
|
await expect(page.locator('#sx-content')).toHaveCount(1);
|
|
await expect(page.locator('#sx-nav')).toContainText('Reactive Islands');
|
|
|
|
// Navigate back to Geography
|
|
const geoLink = page.locator('a[sx-get="/sx/(geography)"]');
|
|
if (await geoLink.count() > 0) {
|
|
await geoLink.first().click();
|
|
await page.waitForTimeout(4000);
|
|
await expect(page.locator('#sx-content')).toHaveCount(1);
|
|
await expect(page.locator('#sx-nav')).toContainText('Geography');
|
|
}
|
|
});
|
|
});
|