SPA navigation, page component refactors, WASM rebuild
Refactor page components (docs, examples, specs, reference, layouts) and adapters (adapter-sx, boot-helpers, orchestration) across sx/ and web/ directories. Add Playwright SPA navigation tests. Rebuild WASM kernel with updated bytecode. Add OCaml primitives for request handling. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
90
tests/playwright/spa-navigation.spec.js
Normal file
90
tests/playwright/spa-navigation.spec.js
Normal file
@@ -0,0 +1,90 @@
|
||||
// SPA navigation tests — verify header, nav, and content survive navigation
|
||||
// Tests that OOB swaps update nav while preserving the header island,
|
||||
// and that rendering errors are scoped to #sx-content.
|
||||
|
||||
const { test, expect } = require('playwright/test');
|
||||
const BASE_URL = process.env.SX_TEST_URL || 'http://localhost:8013';
|
||||
|
||||
test.describe('SPA navigation', () => {
|
||||
|
||||
test('header island survives SPA nav to sibling page', async ({ page }) => {
|
||||
await page.goto(BASE_URL + '/sx/(geography.(hypermedia.(example)))', { waitUntil: 'networkidle' });
|
||||
|
||||
// Header should be present and hydrated
|
||||
const headerIsland = page.locator('[data-sx-island="layouts/header"]');
|
||||
await expect(headerIsland).toHaveCount(1);
|
||||
await expect(headerIsland).toContainText('sx');
|
||||
|
||||
// Navigate via SPA
|
||||
await page.click('a[href*="click-to-load"]');
|
||||
await page.waitForURL('**/click-to-load**');
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Header island should still exist
|
||||
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 via OOB on SPA navigation', async ({ page }) => {
|
||||
await page.goto(BASE_URL + '/sx/(geography.(hypermedia.(example)))', { waitUntil: 'networkidle' });
|
||||
|
||||
// Nav should show current breadcrumbs
|
||||
const nav = page.locator('#sx-nav');
|
||||
await expect(nav).toHaveCount(1);
|
||||
const navTextBefore = await nav.textContent();
|
||||
expect(navTextBefore).toContain('Examples');
|
||||
|
||||
// Navigate to a child page
|
||||
await page.click('a[href*="click-to-load"]');
|
||||
await page.waitForURL('**/click-to-load**');
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Nav should update to show the new page in breadcrumbs
|
||||
const navTextAfter = await page.locator('#sx-nav').first().textContent();
|
||||
expect(navTextAfter).toContain('Click to Load');
|
||||
});
|
||||
|
||||
test('#sx-content exists after SPA navigation', async ({ page }) => {
|
||||
await page.goto(BASE_URL + '/sx/(geography.(hypermedia.(example)))', { waitUntil: 'networkidle' });
|
||||
await expect(page.locator('#sx-content')).toHaveCount(1);
|
||||
|
||||
await page.click('a[href*="click-to-load"]');
|
||||
await page.waitForURL('**/click-to-load**');
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// sx-content should still exist (may contain error boundary, but not be missing)
|
||||
await expect(page.locator('#sx-content').first()).toBeAttached();
|
||||
});
|
||||
|
||||
test('rendering error scoped to #sx-content, not full page', async ({ page }) => {
|
||||
await page.goto(BASE_URL + '/sx/(geography.(hypermedia.(example)))', { waitUntil: 'networkidle' });
|
||||
|
||||
// Verify page structure before nav
|
||||
await expect(page.locator('#sx-nav')).toHaveCount(1);
|
||||
await expect(page.locator('#sx-content')).toHaveCount(1);
|
||||
|
||||
await page.click('a[href*="click-to-load"]');
|
||||
await page.waitForURL('**/click-to-load**');
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// If there's a render error, it should be inside #sx-content
|
||||
const errors = page.locator('.sx-render-error');
|
||||
const errorCount = await errors.count();
|
||||
if (errorCount > 0) {
|
||||
// Error should be a descendant of #sx-content, not replacing the whole page
|
||||
const errorParent = await errors.first().evaluate(el => {
|
||||
let p = el;
|
||||
while (p) {
|
||||
if (p.id === 'sx-content') return 'sx-content';
|
||||
if (p.id === 'main-panel') return 'main-panel';
|
||||
p = p.parentElement;
|
||||
}
|
||||
return 'unknown';
|
||||
});
|
||||
expect(errorParent).toBe('sx-content');
|
||||
|
||||
// Nav should still be present even with an error
|
||||
await expect(page.locator('#sx-nav').first()).toContainText('Click to Load');
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user