Two bugs fixed: 1. Hydration flash: The effect's schedule-idle called rebuild-preview on initial hydration, which cleared the SSR HTML and re-rendered (flashing raw SX source or blank). Fix: skip rebuild-preview on initial render — the SSR content is already correct. Only rebuild when stepping. 2. Stepping structure: After do-back calls rebuild-preview, the DOM stack was reset to just [container]. Subsequent do-step calls appended elements to the wrong parent (e.g. "sx" span outside h1). Fix: compute the correct stack depth by replaying open/close step counts, then walk the rendered DOM tree that many levels deep via lastElementChild to find the actual DOM nodes. Proven by harness test: compute-depth returns 3 at step 10 (inside div > h1 > span), 2 at step 8 (inside div > h1), 0 at step 16 (all closed). Playwright test: lake never shows raw SX (~tw, :tokens) during hydration — now passes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
97 lines
3.1 KiB
JavaScript
97 lines
3.1 KiB
JavaScript
const { test, expect } = require('playwright/test');
|
|
const { loadPage, trackErrors } = require('./helpers');
|
|
|
|
test('stepper: lake never shows raw SX source during hydration', async ({ page }) => {
|
|
// Monitor the lake content during page load to catch flashes
|
|
const lakeStates = [];
|
|
await page.context().clearCookies();
|
|
|
|
// Set up mutation observer before navigation
|
|
await page.addInitScript(() => {
|
|
window.__lakeStates = [];
|
|
const observer = new MutationObserver(() => {
|
|
const lake = document.querySelector('[data-sx-lake="home-preview"]');
|
|
if (lake) {
|
|
const text = lake.textContent;
|
|
if (text && text.length > 0) {
|
|
window.__lakeStates.push(text.slice(0, 100));
|
|
}
|
|
}
|
|
});
|
|
// Start observing once DOM is ready
|
|
if (document.body) {
|
|
observer.observe(document.body, { childList: true, subtree: true, characterData: true });
|
|
} else {
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
observer.observe(document.body, { childList: true, subtree: true, characterData: true });
|
|
});
|
|
}
|
|
});
|
|
|
|
await page.goto('http://localhost:8013/', { waitUntil: 'networkidle' });
|
|
await page.waitForTimeout(3000);
|
|
|
|
const states = await page.evaluate(() => window.__lakeStates);
|
|
|
|
// No state should contain raw SX component calls
|
|
for (const state of states) {
|
|
expect(state).not.toContain('~tw');
|
|
expect(state).not.toContain('~cssx');
|
|
expect(state).not.toContain(':tokens');
|
|
}
|
|
});
|
|
|
|
test('stepper: default view shows all four words after hydration', async ({ page }) => {
|
|
await page.context().clearCookies();
|
|
await loadPage(page, '');
|
|
|
|
const lake = page.locator('[data-sx-lake="home-preview"]');
|
|
await expect(lake).toBeVisible({ timeout: 10000 });
|
|
await page.waitForTimeout(3000);
|
|
|
|
const text = await lake.textContent();
|
|
expect(text).toContain('the');
|
|
expect(text).toContain('joy');
|
|
expect(text).toContain('of');
|
|
expect(text).toContain('sx');
|
|
});
|
|
|
|
test('stepper: stepped spans have colored text', async ({ page }) => {
|
|
await page.context().addCookies([{
|
|
name: 'sx-home-stepper',
|
|
value: '0',
|
|
url: 'http://localhost:8013'
|
|
}]);
|
|
await loadPage(page, '');
|
|
await page.waitForTimeout(3000);
|
|
|
|
const fwdBtn = page.locator('button:has-text("▶")');
|
|
await expect(fwdBtn).toBeVisible({ timeout: 5000 });
|
|
|
|
// Step forward 16 times to complete the expression
|
|
for (let i = 0; i < 16; i++) {
|
|
await fwdBtn.click();
|
|
await page.waitForTimeout(200);
|
|
}
|
|
|
|
const lake = page.locator('[data-sx-lake="home-preview"]');
|
|
|
|
// Check each span has a non-black computed color
|
|
const colors = await lake.evaluate(el => {
|
|
const spans = el.querySelectorAll('span');
|
|
return Array.from(spans).map(s => ({
|
|
text: s.textContent,
|
|
color: getComputedStyle(s).color,
|
|
hasClass: s.className.length > 0
|
|
}));
|
|
});
|
|
|
|
expect(colors.length).toBeGreaterThanOrEqual(4);
|
|
for (const span of colors) {
|
|
// Each span should have a class applied
|
|
expect(span.hasClass).toBe(true);
|
|
// Color should not be default black (rgb(0, 0, 0))
|
|
expect(span.color).not.toBe('rgb(0, 0, 0)');
|
|
}
|
|
});
|