Files
projectbrain-Public/src/shared/lib/activityRuntimePresentation.test.ts
T

83 lines
4.4 KiB
TypeScript

import { describe, expect, it } from "vitest";
import type { RuntimeJob, RuntimeRunner } from "@/shared/api/portfolioTypes";
import { getActivityOperationSections, latestCompletedRuntimeJobByRunner, prioritizeRuntimeJobs, runtimeJobGroups, runtimeJobLabel, runtimeQueueExplanation, runtimeRunnerCapacity } from "@/shared/lib/activityRuntimePresentation";
const job = (queueState: RuntimeJob["queueState"], input: Partial<RuntimeJob> = {}): RuntimeJob => ({
id: 1,
name: "quality",
status: queueState === "runnable" ? "queued" : queueState === "dependency-blocked" ? "pending" : queueState === "running" ? "in_progress" : "completed",
queueState,
completedSteps: 0,
totalSteps: 1,
url: "https://gitea.example/jobs/1",
createdAt: "2026-09-01T12:00:00Z",
...input,
});
describe("activity runtime presentation", () => {
it("keeps running jobs ahead of newer queued jobs before the display limit", () => {
const jobs = [...Array.from({ length: 30 }, (_, i) => job("runnable", { id: 100 + i })), job("running", { id: 1 }), job("running", { id: 2 })];
expect(prioritizeRuntimeJobs(jobs).slice(0, 12).slice(0, 2).map((item) => item.id)).toEqual([2, 1]);
expect(jobs[0].id).toBe(100);
expect(prioritizeRuntimeJobs([job("completed", { id: 9 }), job("dependency-blocked", { id: 8 }), job("runnable", { id: 7 }), job("running", { id: 6 })]).map((item) => item.id)).toEqual([6, 7, 8, 9]);
});
it("never presents a queued job as completed", () => {
expect(runtimeJobLabel(job("runnable"))).toBe("Wacht op runner");
expect(runtimeQueueExplanation(38, 0, 0)).toContain("nog geen afgeronde jobs");
});
it("explains persistent queueing when free runners exist", () => {
expect(runtimeQueueExplanation(2, 0, 1)).toContain("1 vrije runner");
expect(runtimeQueueExplanation(2, 0, 2)).toContain("joblabels");
});
it("keeps runner and dependency queues separate", () => {
const groups = runtimeJobGroups([job("running"), job("runnable", { id: 2 }), job("dependency-blocked", { id: 3 })]);
expect(groups.running).toHaveLength(1);
expect(groups.runnable).toHaveLength(1);
expect(groups.dependencyBlocked).toHaveLength(1);
});
it("classifies current work before queues and historical outcomes", () => {
const sections = getActivityOperationSections([
job("completed", { id: 1, conclusion: "success" }),
job("dependency-blocked", { id: 2 }),
job("runnable", { id: 3 }),
job("running", { id: 4 }),
job("completed", { id: 5, conclusion: "failure" }),
]);
expect(sections.active.map((item) => item.id)).toEqual([4]);
expect(sections.waitingForRunner.map((item) => item.id)).toEqual([3]);
expect(sections.waitingForDependency.map((item) => item.id)).toEqual([2]);
expect(sections.recentCompleted.map((item) => item.id)).toEqual([1]);
expect(sections.recentFailed.map((item) => item.id)).toEqual([5]);
});
it("counts only online non-busy runners as free capacity", () => {
const runners: RuntimeRunner[] = [
{ id: 1, name: "busy", status: "online", busy: true, disabled: false, labels: [] },
{ id: 2, name: "idle", status: "online", busy: false, disabled: false, labels: [] },
{ id: 3, name: "offline", status: "offline", busy: false, disabled: false, labels: [] },
{ id: 4, name: "disabled", status: "online", busy: false, disabled: true, labels: [] },
];
expect(runtimeRunnerCapacity(runners)).toEqual({ total: 4, enabled: 3, online: 2, busy: 1, idle: 1, disabled: 1 });
});
it("keeps the latest completed activity per named runner", () => {
const latest = latestCompletedRuntimeJobByRunner([
job("completed", { id: 1, runnerName: "runner-a", repository: "Jens/old", completedAt: "2026-09-01T12:05:00Z" }),
job("completed", { id: 2, runnerName: "runner-a", repository: "Jens/new", completedAt: "2026-09-01T12:10:00Z" }),
job("completed", { id: 3, runnerName: "runner-b", repository: "Jens/other", completedAt: "2026-09-01T12:07:00Z" }),
job("running", { id: 4, runnerName: "runner-a" }),
]);
expect(latest["runner-a"]?.id).toBe(2);
expect(latest["runner-b"]?.repository).toBe("Jens/other");
});
it("translates final conclusions independently from queue state", () => {
expect(runtimeJobLabel(job("completed", { conclusion: "success" }))).toBe("Geslaagd");
expect(runtimeJobLabel(job("completed", { conclusion: "failure" }))).toBe("Mislukt");
});
});