Files
rose-ash/tests/playwright/spec-explorer.spec.js
giles 6e885f49b6 Spec explorer: fix SxExpr rendering bugs, add drill-in UX, Playwright tests
Fix 3 OCaml bugs that caused spec explorer to hang:
- sx_types: inspect outputs quoted string for SxExpr (not bare symbol)
- sx_primitives: serialize/to_string extract SxExpr/RawHTML content
- sx_render: handle SxExpr in both render-to-html paths

Restructure spec explorer for performance:
- Lightweight overview: name + kind only (was full source for 141 defs)
- Drill-in detail: click definition → params, effects, signature
- explore() page function accepts optional second arg for drill-in
- spec() passes through non-string slugs from nested routing

Fix aser map result wrapping:
- aser-special map now wraps results in fragment (<> ...) via aser-fragment
- Prevents ((div ...) (div ...)) nested lists that caused client "Not callable"

5 Playwright tests: overview load, no errors, SPA nav, drill-in detail+params

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 23:46:13 +00:00

75 lines
3.0 KiB
JavaScript

const { test, expect } = require('playwright/test');
const { BASE_URL, trackErrors } = require('./helpers');
const LOAD_TIMEOUT = 30000;
async function loadExplorer(page, path) {
await page.goto(BASE_URL + '/sx/' + path, {
waitUntil: 'domcontentloaded',
timeout: LOAD_TIMEOUT,
});
await page.waitForSelector('h1, .sx-render-error, [data-sx-boundary]', {
timeout: LOAD_TIMEOUT,
});
}
test.describe('Spec Explorer', () => {
test('overview loads with evaluator definitions', async ({ page }) => {
await loadExplorer(page, '(language.(spec.(explore.evaluator)))');
await expect(page.locator('h1')).toHaveText('Evaluator');
await expect(page.locator('text=141 defines')).toBeVisible();
await expect(page.locator('text=2501 lines')).toBeVisible();
await expect(page.locator('h2:has-text("Definitions")')).toBeVisible();
await expect(page.locator('#fn-make-cek-state')).toBeVisible();
await expect(page.locator('#fn-cek-step')).toBeVisible();
// eval-expr has 2 entries (forward decl + real), just check at least 1
await expect(page.locator('#fn-eval-expr').first()).toBeVisible();
await expect(page.locator('.sx-render-error')).toHaveCount(0);
});
test('no errors on initial load', async ({ page }) => {
const tracker = trackErrors(page);
await loadExplorer(page, '(language.(spec.(explore.evaluator)))');
await expect(page.locator('h1')).toHaveText('Evaluator');
expect(tracker.errors()).toEqual([]);
});
test('SPA nav to explorer has no render error', async ({ page }) => {
await page.goto(BASE_URL + '/sx/(language.(spec))', {
waitUntil: 'domcontentloaded',
timeout: LOAD_TIMEOUT,
});
await page.waitForSelector('h1, h2', { timeout: LOAD_TIMEOUT });
const link = page.locator('a[href*="explore.evaluator"]');
if (await link.count() > 0) {
await link.click();
await page.waitForSelector('#fn-cek-step', { timeout: LOAD_TIMEOUT });
await expect(page.locator('h1')).toHaveText('Evaluator');
const content = await page.locator('#sx-content').textContent();
expect(content).not.toContain('Render error');
expect(content).not.toContain('Not callable');
}
});
test('drill-in shows definition detail', async ({ page }) => {
await loadExplorer(page, '(language.(spec.(explore.evaluator.cek-step)))');
await expect(page.locator('text=cek-step').first()).toBeVisible();
await expect(page.locator('text=function').first()).toBeVisible();
await expect(page.locator('a:has-text("Back to evaluator")')).toBeVisible();
await expect(page.locator('text=define cek-step')).toBeVisible();
});
test('drill-in shows params and pure badge', async ({ page }) => {
await loadExplorer(page, '(language.(spec.(explore.evaluator.make-cek-state)))');
await expect(page.locator('text=control').first()).toBeVisible();
await expect(page.locator('text=env').first()).toBeVisible();
await expect(page.locator('text=kont').first()).toBeVisible();
await expect(page.locator('text=pure').first()).toBeVisible();
});
});