From b9adba92b2367e853c2b49aeeb72655d589f6120 Mon Sep 17 00:00:00 2001 From: w Date: Sat, 25 Jul 2026 12:44:09 -0400 Subject: [PATCH] fix(playground): report prerequisite skips neutrally --- playground/CLAUDE.md | 4 ++-- playground/src/components/DiagnosisView.tsx | 17 +++++++--------- playground/src/lib/auto-test.ts | 13 ++++++++---- playground/src/lib/diagnosis-report.ts | 22 +++++++++------------ playground/tests/e2e/dotli-diagnosis.ts | 9 +++------ 5 files changed, 30 insertions(+), 35 deletions(-) diff --git a/playground/CLAUDE.md b/playground/CLAUDE.md index db388e28..1ed5b6f4 100644 --- a/playground/CLAUDE.md +++ b/playground/CLAUDE.md @@ -42,11 +42,11 @@ The Diagnosis screen emits a per-host markdown report via "Copy report". Aggrega | `src/lib/example-runner.ts` | Transpiles each rustdoc `ts` example via sucrase, runs it inside an `AsyncFunction` with `truapi`, `console`, rxjs, and an ambient `assert` as bindings. Failure is explicit: an example fails iff it throws (via `assert(...)`, a timeout, or any uncaught error); `console.*` is pure output. A tracking Proxy auto-unsubscribes inner `.subscribe(...)` calls so subscriptions clean up when the run ends or the user navigates away. | | `src/lib/monaco-setup.ts` | Configures Monaco's TS worker: registers the bundled `@parity/truapi` types (`truapi-dts`), every rxjs `.d.ts`, and an ambient block (`declare const truapi: Client`, `assert`, `crypto`, `Uint8Array` hex helpers) so examples typecheck without manual imports. Defines the light/dark themes that match the design tokens. | | `src/lib/auto-test.ts` | Runs each method's example and reports pass / fail. A method passes when its example resolves within the timeout and fails when it throws (the thrown/`assert` message plus any logs become the failure output); unary and subscription examples are awaited identically. `runDiagnosis` runs every method one at a time, in service order; methods that prompt the user (signing, permission/resource requests) block on their host dialog before the run continues. `runSingleTest` replays one method (used by the Diagnosis row replay). | -| `src/lib/diagnosis-report.ts` | Renders the diagnosis results as a copy-pasteable GitHub-flavoured markdown table: a `## Truapi Diagnosis` title (host mode via `detectHostMode` — a native host (Electron UA or `__HOST_WEBVIEW_MARK__`) is split by user-agent into Desktop / Android / iOS, a browser iframe ⇒ Web) a `**N success · N failed**` summary line, and one row per method. Skipped methods are reported as failed (`❌`) with the skip reason in the Details column, so every truapi method stays in the compatibility matrix (the aggregator keeps only `✅`/`❌` cells). Deterministic for a given set of results (no timestamp). `renderReportMarkdown(…, { dropSuccessDetails })` emits a compact variant (success-row details blanked, failures kept) for the length-limited issue URL; `reportIssueUrl` caps the whole URL and falls back to a "paste from clipboard" body when a report is still too large. Consumed by the explorer's matrix aggregator. | +| `src/lib/diagnosis-report.ts` | Renders deterministic copy-pasteable GitHub-flavoured markdown: a `## Truapi Diagnosis` title, a `**N success · N failed · N skipped**` summary, and one row per method. Passes use `✅`, genuine failures use `❌`, and intentional prerequisite skips use `⏭`; the explorer aggregator maps skips to unmeasured (`null`) rather than claiming support or failure. `renderReportMarkdown(…, { dropSuccessDetails })` blanks bulky success details for the length-limited issue URL while retaining failure and skip reasons. `reportIssueUrl` caps the URL and falls back to a clipboard-paste prompt. | | `src/lib/host-api-bridge.ts` | Just `stringify`, the JSON-with-bigint helper shared across components. | | `src/components/ExampleEditor.tsx` | Monaco editor wrapper. Auto-folds `// #region helpers` blocks on mount. | | `src/components/MethodView.tsx` | Per-method view: signature link to cargo doc, Example / Output tabs, status LED, Run / Stop buttons. Output is the example's `console.*` log; an explicit `assert`/error throw flips the LED to error and shows the thrown message. | -| `src/components/DiagnosisView.tsx` | Diagnosis screen (own sidebar entry): purpose + login/phone instructions, a Run button, a live per-method log (queued → processing… → success/failed) with per-row expand + replay, a summary showing success / failed counts (skipped methods are shown as failed, with the skip reason as their detail), and Copy report / Submit report buttons. Submit copies the full report to the clipboard and opens a pre-filled GitHub issue carrying a compact variant of it. | +| `src/components/DiagnosisView.tsx` | Diagnosis screen (own sidebar entry): purpose + login/phone instructions, a Run button, a live per-method log (queued → processing… → success / failed / skipped) with per-row expand + replay, a summary showing separate success / failed / skipped counts, and Copy report / Submit report buttons. Submit copies the full report to the clipboard and opens a pre-filled GitHub issue carrying a compact variant of it. | | `src/components/ServiceTable.tsx` / `CommandPalette.tsx` | Method browser and ⌘K search. The browser also hosts the Diagnosis entry. | | `src/app/page.tsx` | Root: connection status, selection state, deep-link sync via `pushState` + `popstate`. | diff --git a/playground/src/components/DiagnosisView.tsx b/playground/src/components/DiagnosisView.tsx index 520bc3f2..f7a176c9 100644 --- a/playground/src/components/DiagnosisView.tsx +++ b/playground/src/components/DiagnosisView.tsx @@ -21,8 +21,6 @@ interface Row { service: string; method: string; status: TestStatus; - // A skipped method is displayed as failed; this flags it so the e2e gate can - // tell an intentional skip apart from a genuine failure. skipped: boolean; output?: string; } @@ -47,27 +45,25 @@ export function DiagnosisView({ const [copied, setCopied] = useState(false); const [expandedId, setExpandedId] = useState(null); - const { rows, hasResults, passCount, failCount } = useMemo(() => { + const { rows, hasResults, passCount, failCount, skipCount } = useMemo(() => { const out: Row[] = []; let pass = 0; let fail = 0; + let skipped = 0; for (const svc of services) { for (const m of svc.methods) { const id = `${svc.name}/${m.name}`; const entry = testResults[id]; - const rawStatus = entry?.status ?? "idle"; - // Skipped methods are shown as failed (their skip reason is the detail) - // so the view stays a pass/fail summary and every method is accounted - // for — matching the compatibility matrix. - const status = rawStatus === "skipped" ? "fail" : rawStatus; + const status = entry?.status ?? "idle"; if (status === "pass") pass++; else if (status === "fail") fail++; + else if (status === "skipped") skipped++; out.push({ id, service: svc.name, method: m.name, status, - skipped: rawStatus === "skipped", + skipped: status === "skipped", output: entry?.output, }); } @@ -77,6 +73,7 @@ export function DiagnosisView({ hasResults: Object.keys(testResults).length > 0, passCount: pass, failCount: fail, + skipCount: skipped, }; }, [services, testResults]); @@ -176,7 +173,7 @@ export function DiagnosisView({ data-testid="diagnosis-summary" data-has-fail={!isRunning && failCount > 0} > - {passCount} success · {failCount} failed + {passCount} success · {failCount} failed · {skipCount} skipped )} {hasResults && !isRunning && ( diff --git a/playground/src/lib/auto-test.ts b/playground/src/lib/auto-test.ts index 3e40c15f..ae7b27ae 100644 --- a/playground/src/lib/auto-test.ts +++ b/playground/src/lib/auto-test.ts @@ -21,8 +21,12 @@ const LIVE_ALLOCATION_TIMEOUT_MS = 420_000; // Services skipped wholesale in the diagnosis until hosts wire them up. const SKIPPED_SERVICES = new Set(["Chat", "Coin Payment", "Payment"]); -// Individual methods skipped while the host surface is intentionally deferred. -const SKIPPED_METHODS = new Set(["Account/create_account_proof"]); +// Individual methods skipped when Diagnosis cannot establish their prerequisite. +const SKIPPED_REASON: Record = { + "Account/create_account_proof": "host surface intentionally deferred", + "Chain/stop_transaction": + "requires an operation id from a live transaction broadcast", +}; // Methods that trigger a host permission/signing prompt, so they need the // longer signing-class timeout to allow for the user to respond. const LONG_TIMEOUT_METHODS = new Set([ @@ -69,10 +73,11 @@ async function runOne({ }); return; } - if (SKIPPED_METHODS.has(id)) { + const skippedReason = SKIPPED_REASON[id]; + if (skippedReason) { onUpdate(id, { status: "skipped", - output: "host surface intentionally deferred", + output: skippedReason, }); return; } diff --git a/playground/src/lib/diagnosis-report.ts b/playground/src/lib/diagnosis-report.ts index 8dfb4d2c..e84ef5d4 100644 --- a/playground/src/lib/diagnosis-report.ts +++ b/playground/src/lib/diagnosis-report.ts @@ -49,33 +49,29 @@ export function renderReportMarkdown( const mode = meta.mode ?? detectHostMode(); let pass = 0; let fail = 0; + let skipped = 0; const rows: string[] = []; for (const svc of services) { for (const m of svc.methods) { const id = `${svc.name}/${m.name}`; const entry = results[id]; const status = entry?.status ?? "idle"; - // Skipped methods are reported as failed so every truapi method stays in - // the compatibility matrix (the aggregator keeps only ✅/❌ cells); the - // reason the method was skipped travels in the Details column. - const reportStatus = status === "skipped" ? "fail" : status; - if (reportStatus === "pass") pass++; - else if (reportStatus === "fail") fail++; + if (status === "pass") pass++; + else if (status === "fail") fail++; + else if (status === "skipped") skipped++; // The issue-URL variant drops success-row details (bulky response - // payloads) to keep the URL under GitHub's length limit; failures keep - // their (short) details. + // payloads) to keep the URL under GitHub's length limit; failures and + // intentional skips keep their (short) details. const detail = - meta.dropSuccessDetails && reportStatus === "pass" - ? "" - : detailCell(entry); - rows.push(`| \`${id}\` | ${ICON[reportStatus]} | ${detail} |`); + meta.dropSuccessDetails && status === "pass" ? "" : detailCell(entry); + rows.push(`| \`${id}\` | ${ICON[status]} | ${detail} |`); } } const lines: string[] = []; lines.push(`## Truapi ${mode} Diagnosis`); lines.push(""); - lines.push(`**${pass} success · ${fail} failed**`); + lines.push(`**${pass} success · ${fail} failed · ${skipped} skipped**`); lines.push(""); lines.push("| Method | Status | Details |"); lines.push("| --- | --- | --- |"); diff --git a/playground/tests/e2e/dotli-diagnosis.ts b/playground/tests/e2e/dotli-diagnosis.ts index 5eb7c7a6..c7418ed2 100644 --- a/playground/tests/e2e/dotli-diagnosis.ts +++ b/playground/tests/e2e/dotli-diagnosis.ts @@ -578,13 +578,10 @@ async function runDiagnosisOnce(page: Page): Promise<{ if (report.trim().length === 0) { throw new Error("diagnosis report markdown is empty"); } - // Skipped methods render as failed (and appear failed in the matrix), but they - // are intentional gaps — exclude them from the CI hard-fail gate so only - // genuine failures fail the run. + // Neutral prerequisite skips use data-status="skipped"; only genuine + // failures enter the CI hard-fail gate. const failedMethods = await frame - .locator( - '[data-testid="diagnosis-row"][data-status="fail"]:not([data-skipped="true"]) .diag__name', - ) + .locator('[data-testid="diagnosis-row"][data-status="fail"] .diag__name') .allInnerTexts(); await frame.locator('[data-testid="diagnosis-copy-report"]').click();