setup: add vitest test infrastructure with helpers, fixtures, and reactive test patterns
This commit is contained in:
136
test/helpers/reactive.ts
Normal file
136
test/helpers/reactive.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import { signal, computed, effect } from "@preact/signals-core";
|
||||
import { DirectedGraph } from "graphology";
|
||||
|
||||
const IDLE: NodeStatus = "idle";
|
||||
const WAITING: NodeStatus = "waiting";
|
||||
const READY: NodeStatus = "ready";
|
||||
const RUNNING: NodeStatus = "running";
|
||||
const COMPLETED: NodeStatus = "completed";
|
||||
const FAILED: NodeStatus = "failed";
|
||||
const ABORTED: NodeStatus = "aborted";
|
||||
const SKIPPED: NodeStatus = "skipped";
|
||||
|
||||
type NodeStatus =
|
||||
| "idle"
|
||||
| "waiting"
|
||||
| "ready"
|
||||
| "running"
|
||||
| "completed"
|
||||
| "failed"
|
||||
| "aborted"
|
||||
| "skipped";
|
||||
|
||||
export interface TestReactiveRoot {
|
||||
statusMap: Map<string, ReturnType<typeof signal<NodeStatus>>>;
|
||||
preconditions: Map<string, ReturnType<typeof computed<boolean>>>;
|
||||
blockedByFailure: Map<string, ReturnType<typeof computed<boolean>>>;
|
||||
dispose: () => void;
|
||||
}
|
||||
|
||||
export function createTestReactiveRoot(graph: DirectedGraph): TestReactiveRoot {
|
||||
const statusMap = new Map<string, ReturnType<typeof signal<NodeStatus>>>();
|
||||
const preconditions = new Map<string, ReturnType<typeof computed<boolean>>>();
|
||||
const blockedByFailure = new Map<string, ReturnType<typeof computed<boolean>>>();
|
||||
const disposers: (() => void)[] = [];
|
||||
|
||||
for (const node of graph.nodes()) {
|
||||
const predecessors = graph.inNeighbors(node);
|
||||
|
||||
const status = signal<NodeStatus>(IDLE);
|
||||
const preconditionsComputed = computed(() => {
|
||||
return predecessors.every((pred: string) => {
|
||||
const predStatus = statusMap.get(pred);
|
||||
return (
|
||||
predStatus !== undefined &&
|
||||
(predStatus.value === COMPLETED || predStatus.value === SKIPPED)
|
||||
);
|
||||
});
|
||||
});
|
||||
const blockedByFailureComputed = computed(() => {
|
||||
return predecessors.some((pred: string) => {
|
||||
const predStatus = statusMap.get(pred);
|
||||
return (
|
||||
predStatus !== undefined &&
|
||||
(predStatus.value === FAILED || predStatus.value === ABORTED)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const blockerDisposer = effect(() => {
|
||||
if (blockedByFailureComputed.value) {
|
||||
if (status.value === IDLE || status.value === WAITING) {
|
||||
status.value = ABORTED;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
disposers.push(blockerDisposer);
|
||||
statusMap.set(node, status);
|
||||
preconditions.set(node, preconditionsComputed);
|
||||
blockedByFailure.set(node, blockedByFailureComputed);
|
||||
}
|
||||
|
||||
return {
|
||||
statusMap,
|
||||
preconditions,
|
||||
blockedByFailure,
|
||||
dispose: () => {
|
||||
for (const disposer of disposers) {
|
||||
disposer();
|
||||
}
|
||||
statusMap.clear();
|
||||
preconditions.clear();
|
||||
blockedByFailure.clear();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function assertStatus(
|
||||
root: TestReactiveRoot,
|
||||
nodeId: string,
|
||||
expected: NodeStatus,
|
||||
): void {
|
||||
const status = root.statusMap.get(nodeId);
|
||||
if (!status) {
|
||||
throw new Error(`Node ${nodeId} not found in statusMap`);
|
||||
}
|
||||
if (status.value !== expected) {
|
||||
throw new Error(
|
||||
`Node ${nodeId} status: expected ${expected}, got ${status.value}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function assertPreconditions(
|
||||
root: TestReactiveRoot,
|
||||
nodeId: string,
|
||||
expected: boolean,
|
||||
): void {
|
||||
const preconditions = root.preconditions.get(nodeId);
|
||||
if (!preconditions) {
|
||||
throw new Error(`Node ${nodeId} not found in preconditions`);
|
||||
}
|
||||
if (preconditions.value !== expected) {
|
||||
throw new Error(
|
||||
`Node ${nodeId} preconditions: expected ${expected}, got ${preconditions.value}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function assertBlockedByFailure(
|
||||
root: TestReactiveRoot,
|
||||
nodeId: string,
|
||||
expected: boolean,
|
||||
): void {
|
||||
const blocked = root.blockedByFailure.get(nodeId);
|
||||
if (!blocked) {
|
||||
throw new Error(`Node ${nodeId} not found in blockedByFailure`);
|
||||
}
|
||||
if (blocked.value !== expected) {
|
||||
throw new Error(
|
||||
`Node ${nodeId} blockedByFailure: expected ${expected}, got ${blocked.value}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export type { NodeStatus };
|
||||
Reference in New Issue
Block a user