mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-09-05 08:10:39 +00:00
Conflicts were between this branch's parallel-branch identity work and main's stage billing, review targets, and live stage timing. - Stage fixtures: main added `billing` to each per-file `makeStage`; this branch had hoisted one builder into `lib/test-utils`. Kept the hoisted builder and gave it `billing: makeBilledTokenCounts()`, so both intents hold and the field list stays in one place. `stage-sidebar.test.ts` also builds raw `RunStage` wire payloads, so it keeps importing `makeBilledTokenCounts` directly. - Import lists (`run_projection.rs`, `fabro-api/src/lib.rs`, `run_state.rs`, `stage_projection_round_trip.rs`): unioned both sides — `ParallelBranchId` alongside `timing`, `ReviewTarget`, `ReviewTargetKind`, `AttrValue`, `Node`, and `StageToolBatchProjection`. - `fabro-server` tests: git interleaved two unrelated new tests into one body. Split them back into `list_run_stages_exposes_parallel_branch_identity` and `run_billing_includes_live_stage_timing_in_rows_and_totals`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
108 lines
3.1 KiB
TypeScript
108 lines
3.1 KiB
TypeScript
import { createElement, type ReactNode } from "react";
|
|
import type { EventEnvelope } from "@qltysh/fabro-api-client";
|
|
import TestRenderer, { act } from "react-test-renderer";
|
|
|
|
import type { Stage } from "./stage-sidebar";
|
|
import { makeBilledTokenCounts } from "./test-fixtures";
|
|
|
|
const IS_REACT_ACT_ENV = "IS_REACT_ACT_ENVIRONMENT" as const;
|
|
|
|
/**
|
|
* Per-test setup for code that uses react-test-renderer:
|
|
* - Sets IS_REACT_ACT_ENVIRONMENT (required by act()).
|
|
* - Silences react-test-renderer's deprecation warning.
|
|
*
|
|
* Returns a teardown function; pair with beforeEach/afterEach so the global
|
|
* state is scoped to the test rather than leaking process-wide.
|
|
*/
|
|
export function setupReactTestEnv(): () => void {
|
|
type Globals = { [IS_REACT_ACT_ENV]?: boolean };
|
|
const globals = globalThis as Globals;
|
|
const hadEnv = IS_REACT_ACT_ENV in globals;
|
|
const previousEnv = globals[IS_REACT_ACT_ENV];
|
|
globals[IS_REACT_ACT_ENV] = true;
|
|
|
|
const originalConsoleError = console.error;
|
|
console.error = ((...args: unknown[]) => {
|
|
if (
|
|
typeof args[0] === "string" &&
|
|
args[0].startsWith("react-test-renderer is deprecated")
|
|
) {
|
|
return;
|
|
}
|
|
originalConsoleError(...args);
|
|
}) as typeof console.error;
|
|
|
|
return () => {
|
|
console.error = originalConsoleError;
|
|
if (hadEnv) {
|
|
globals[IS_REACT_ACT_ENV] = previousEnv;
|
|
} else {
|
|
delete globals[IS_REACT_ACT_ENV];
|
|
}
|
|
};
|
|
}
|
|
|
|
/** Build an event envelope fixture; override any field via `partial`. */
|
|
export function makeEventEnvelope(
|
|
seq: number,
|
|
partial: Partial<EventEnvelope>,
|
|
): EventEnvelope {
|
|
return {
|
|
seq,
|
|
id: `evt-${seq}`,
|
|
ts: `2026-04-09T12:00:0${seq}Z`,
|
|
run_id: "run-1",
|
|
event: "stage.prompt",
|
|
...partial,
|
|
} as EventEnvelope;
|
|
}
|
|
|
|
/** Flatten a rendered subtree to its visible text. */
|
|
export function textContent(node: TestRenderer.ReactTestInstance): string {
|
|
return node.children
|
|
.map((child) => (typeof child === "string" ? child : textContent(child)))
|
|
.join("");
|
|
}
|
|
|
|
/**
|
|
* Build a sidebar `Stage` fixture; override any field via `overrides`. Kept
|
|
* here so widening `Stage` updates every fixture at once — test files are
|
|
* excluded from typecheck, so a per-file copy silently goes stale instead.
|
|
*/
|
|
export function makeStage(overrides: Partial<Stage> = {}): Stage {
|
|
return {
|
|
id: "implement@1",
|
|
name: "implement",
|
|
handler: "agent",
|
|
nodeId: "implement",
|
|
visit: 1,
|
|
graphVisit: null,
|
|
resumedFromStageId: null,
|
|
parallelGroupId: null,
|
|
parallelBranchIndex: null,
|
|
status: "running",
|
|
duration: "--",
|
|
startedAt: null,
|
|
providerUsed: null,
|
|
billing: makeBilledTokenCounts(),
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
export function renderHook<T>(
|
|
hook: () => T,
|
|
options: { wrapper: React.ComponentType<{ children: ReactNode }> },
|
|
): { result: { current: T } } {
|
|
const result = { current: undefined as unknown as T };
|
|
function HookHost() {
|
|
result.current = hook();
|
|
return null;
|
|
}
|
|
act(() => {
|
|
TestRenderer.create(
|
|
createElement(options.wrapper, null, createElement(HookHost)),
|
|
);
|
|
});
|
|
return { result };
|
|
}
|