boot-init now sets data-sx-ready on <html> and dispatches an sx:ready CustomEvent after all islands are hydrated. Playwright tests use this instead of networkidle + hard-coded sleeps (50+ seconds eliminated). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
198 lines
6.8 KiB
JavaScript
198 lines
6.8 KiB
JavaScript
// @ts-check
|
|
/**
|
|
* Geography demos — comprehensive page load + interaction tests.
|
|
*/
|
|
const { test, expect } = require('playwright/test');
|
|
const { loadPage } = require('./helpers');
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Helpers
|
|
// ---------------------------------------------------------------------------
|
|
async function expectIsland(page, pattern) {
|
|
const island = page.locator(`[data-sx-island*="${pattern}"]`);
|
|
await expect(island).toBeVisible({ timeout: 8000 });
|
|
return island;
|
|
}
|
|
|
|
|
|
// ===========================================================================
|
|
// Geography section index pages
|
|
// ===========================================================================
|
|
|
|
test.describe('Geography sections', () => {
|
|
const sections = [
|
|
['(geography)', 'Geography'],
|
|
['(geography.(reactive))', 'Reactive'],
|
|
['(geography.(hypermedia))', 'Hypermedia'],
|
|
['(geography.(marshes))', 'Marshes'],
|
|
['(geography.(scopes))', 'Scopes'],
|
|
['(geography.(cek))', 'CEK'],
|
|
['(geography.(isomorphism))', 'Isomorphism'],
|
|
['(geography.(spreads))', 'Spreads'],
|
|
['(geography.(provide))', 'Provide'],
|
|
];
|
|
|
|
for (const [path, text] of sections) {
|
|
test(`${path} loads`, async ({ page }) => {
|
|
await loadPage(page, path);
|
|
const root = page.locator('#sx-root');
|
|
expect(await root.textContent()).toContain(text);
|
|
});
|
|
}
|
|
});
|
|
|
|
|
|
// ===========================================================================
|
|
// Reactive demos — each test is isolated but fast (cached)
|
|
// ===========================================================================
|
|
|
|
test.describe('Reactive demos', () => {
|
|
const demos = [
|
|
['counter', 'counter'],
|
|
['temperature', 'temperature'],
|
|
['stopwatch', 'stopwatch'],
|
|
['input-binding', 'input-binding'],
|
|
['dynamic-class', 'dynamic-class'],
|
|
['reactive-list', 'reactive-list'],
|
|
['stores', 'store-writer'],
|
|
['refs', 'refs'],
|
|
['portal', 'portal'],
|
|
['resource', null],
|
|
['imperative', null],
|
|
['transition', null],
|
|
['error-boundary', null],
|
|
['event-bridge-demo', null],
|
|
['defisland', null],
|
|
['coverage', null],
|
|
];
|
|
|
|
for (const [slug, islandPattern] of demos) {
|
|
test(`${slug} loads`, async ({ page }) => {
|
|
await loadPage(page, `(geography.(reactive.(examples.${slug})))`);
|
|
if (islandPattern) await expectIsland(page, islandPattern);
|
|
});
|
|
}
|
|
|
|
test('counter: buttons change count', async ({ page }) => {
|
|
await loadPage(page, '(geography.(reactive.(examples.counter)))');
|
|
const el = await expectIsland(page, 'counter');
|
|
const buttons = el.locator('button');
|
|
await expect(buttons).toHaveCount(2);
|
|
const textBefore = await el.textContent();
|
|
await buttons.last().click();
|
|
await page.waitForTimeout(300);
|
|
expect(await el.textContent()).not.toBe(textBefore);
|
|
});
|
|
|
|
test('temperature: buttons change conversion', async ({ page }) => {
|
|
await loadPage(page, '(geography.(reactive.(examples.temperature)))');
|
|
const el = await expectIsland(page, 'temperature');
|
|
const buttons = el.locator('button');
|
|
if (await buttons.count() >= 2) {
|
|
const textBefore = await el.textContent();
|
|
await buttons.last().click();
|
|
await page.waitForTimeout(300);
|
|
expect(await el.textContent()).not.toBe(textBefore);
|
|
}
|
|
});
|
|
|
|
test('stores: shared state across islands', async ({ page }) => {
|
|
await loadPage(page, '(geography.(reactive.(examples.stores)))');
|
|
await expect(page.locator('[data-sx-island*="store-reader"]')).toBeVisible({ timeout: 8000 });
|
|
await expect(page.locator('[data-sx-island*="store-writer"]')).toBeVisible({ timeout: 8000 });
|
|
});
|
|
});
|
|
|
|
|
|
// ===========================================================================
|
|
// Hypermedia demos
|
|
// ===========================================================================
|
|
|
|
test.describe('Hypermedia demos', () => {
|
|
const demos = [
|
|
'click-to-load', 'form-submission', 'polling', 'delete-row', 'edit-row',
|
|
'tabs', 'active-search', 'inline-validation', 'lazy-loading',
|
|
'infinite-scroll', 'select-filter', 'loading-states', 'dialogs',
|
|
'oob-swaps', 'bulk-update', 'animations', 'inline-edit', 'progress-bar',
|
|
'swap-positions', 'sync-replace', 'keyboard-shortcuts', 'json-encoding',
|
|
'put-patch', 'retry', 'reset-on-submit', 'value-select', 'vals-and-headers',
|
|
];
|
|
|
|
for (const slug of demos) {
|
|
test(`${slug} loads`, async ({ page }) => {
|
|
await loadPage(page, `(geography.(hypermedia.(example.${slug})))`);
|
|
});
|
|
}
|
|
});
|
|
|
|
|
|
// ===========================================================================
|
|
// Cross-navigation reactivity
|
|
// ===========================================================================
|
|
|
|
test('counter → temperature → counter: all stay reactive', async ({ page }) => {
|
|
await loadPage(page, '(geography.(reactive.(examples.counter)))');
|
|
|
|
let el = await expectIsland(page, 'counter');
|
|
let buttons = el.locator('button');
|
|
let before = await el.textContent();
|
|
await buttons.last().click();
|
|
await page.waitForTimeout(300);
|
|
expect(await el.textContent()).not.toBe(before);
|
|
|
|
// SPA navigate to temperature
|
|
const tempLink = page.locator('a[href*="temperature"]').first();
|
|
if (await tempLink.count() > 0) {
|
|
await tempLink.click();
|
|
el = await expectIsland(page, 'temperature');
|
|
buttons = el.locator('button');
|
|
if (await buttons.count() >= 2) {
|
|
before = await el.textContent();
|
|
await buttons.last().click();
|
|
await page.waitForTimeout(300);
|
|
expect(await el.textContent()).not.toBe(before);
|
|
}
|
|
}
|
|
|
|
// SPA navigate back to counter
|
|
const counterLink = page.locator('a[href*="counter"]').first();
|
|
if (await counterLink.count() > 0) {
|
|
await counterLink.click();
|
|
el = await expectIsland(page, 'counter');
|
|
buttons = el.locator('button');
|
|
before = await el.textContent();
|
|
await buttons.last().click();
|
|
await page.waitForTimeout(300);
|
|
expect(await el.textContent()).not.toBe(before);
|
|
}
|
|
});
|
|
|
|
|
|
// ===========================================================================
|
|
// Other geography pages
|
|
// ===========================================================================
|
|
|
|
test.describe('Other geography pages', () => {
|
|
const pages = [
|
|
'(geography.(cek.demo))', '(geography.(cek.content))', '(geography.(cek.freeze))',
|
|
'(geography.(marshes.hypermedia-feeds))', '(geography.(marshes.on-settle))',
|
|
'(geography.(marshes.server-signals))', '(geography.(marshes.signal-triggers))',
|
|
'(geography.(marshes.view-transform))',
|
|
'(geography.(isomorphism))',
|
|
];
|
|
|
|
for (const path of pages) {
|
|
test(`${path} loads`, async ({ page }) => {
|
|
await loadPage(page, path);
|
|
});
|
|
}
|
|
});
|
|
|
|
test.describe('Reference pages', () => {
|
|
for (const sub of ['attributes', 'events', 'headers', 'js-api']) {
|
|
test(`${sub} loads`, async ({ page }) => {
|
|
await loadPage(page, `(geography.(hypermedia.(reference.${sub})))`);
|
|
});
|
|
}
|
|
});
|