MCPcopy Create free account
hub / github.com/TestSprite/testsprite-cli / renderStepsText

Function renderStepsText

src/commands/test.ts:8001–8074  ·  view source on GitHub ↗
(page: Page<CliTestStep>)

Source from the content-addressed store, hash-verified

7999}
8000
8001function renderStepsText(page: Page<CliTestStep>): string {
8002 if (page.items.length === 0) {
8003 return page.nextToken ? `No steps on this page.\nnextToken: ${page.nextToken}` : 'No steps.';
8004 }
8005
8006 // M2.1 piece 4: prefix every row with a marker column. `*` flags
8007 // steps the facade marked as contributing to the test failure
8008 // (synthetic terminal "assertion" rows always get the marker;
8009 // pre-M2.1 callers see no markers because the field is absent or
8010 // null). Two-character column ("* " or " ") so alignment stays
8011 // stable across pages.
8012 // Collapse newlines / runs of whitespace to single spaces and cap the
8013 // column width. Without this, one long or multi-line description — e.g.
8014 // a synthetic "TEST BLOCKED\n\n<paragraphs>…" assertion blob — set the
8015 // column to its full length, padding every short row with hundreds of
8016 // trailing spaces and shoving UPDATED far off-screen; embedded newlines
8017 // broke alignment outright (dogfood 2026-06-04). Full untruncated text
8018 // is still available via `--output json`.
8019 const descOf = (s: CliTestStep): string => {
8020 const isSynthetic =
8021 s.action === 'assertion' && s.htmlSnapshotUrl === null && s.screenshotUrl === null;
8022 const base = s.description.length > 0 ? s.description : '—';
8023 // Truncate the base FIRST, then append the synthetic hint, so the
8024 // "(synthetic assertion failure)" marker always survives truncation
8025 // (it explains why an `assertion` row exists when the code had none).
8026 const oneLine = base.replace(/\s+/g, ' ').trim();
8027 const clamped =
8028 oneLine.length > DESC_COL_MAX ? `${oneLine.slice(0, DESC_COL_MAX - 1)}…` : oneLine;
8029 return isSynthetic ? `${clamped} (synthetic assertion failure)` : clamped;
8030 };
8031
8032 const indexWidth = Math.max(5, ...page.items.map(s => String(s.stepIndex).length));
8033 const actionWidth = Math.max(6, ...page.items.map(s => s.action.length));
8034 const statusWidth = 6; // "passed" / "failed" / "—"
8035 const descWidth = Math.max(11, ...page.items.map(s => descOf(s).length));
8036
8037 const header =
8038 pad(' ', 2) +
8039 pad('INDEX', indexWidth) +
8040 ' ' +
8041 pad('ACTION', actionWidth) +
8042 ' ' +
8043 pad('STATUS', statusWidth) +
8044 ' ' +
8045 pad('DESCRIPTION', descWidth) +
8046 ' ' +
8047 'UPDATED';
8048
8049 const rows = page.items.map(s => {
8050 const marker = s.outcomeContributesToFailure === true ? '* ' : ' ';
8051 return [
8052 marker,
8053 pad(String(s.stepIndex), indexWidth),
8054 pad(s.action, actionWidth),
8055 pad(s.status ?? '—', statusWidth),
8056 pad(descOf(s), descWidth),
8057 s.updatedAt,
8058 ].join(' ');

Callers 1

runStepsFunction · 0.85

Calls 3

descOfFunction · 0.85
uniqueNonNullFunction · 0.85
padFunction · 0.70

Tested by

no test coverage detected