feat(FN-4370): complete Step 1 — fix badge null-merge semantics

Fusion-Task-Id: FN-4370
Fusion-Task-Lineage: c18188d5-c478-4233-943f-943b0125b271
This commit is contained in:
Fusion
2026-05-13 14:54:20 -07:00
committed by gsxdsm
parent a4742da65a
commit 79ca85f8fc
5 changed files with 103 additions and 18 deletions

View File

@@ -31,6 +31,22 @@ vi.mock("../../hooks/useTaskDiffStats", () => ({
useTaskDiffStats: (...args: any[]) => useTaskDiffStatsMock(...args),
}));
const badgeUpdatesMock = new Map<string, any>();
const subscribeToBadgeMock = vi.fn();
const unsubscribeFromBadgeMock = vi.fn();
vi.mock("../../hooks/useBadgeWebSocket", () => ({
useBadgeWebSocket: () => ({
badgeUpdates: badgeUpdatesMock,
isConnected: true,
subscribeToBadge: subscribeToBadgeMock,
unsubscribeFromBadge: unsubscribeFromBadgeMock,
}),
}));
vi.mock("../../hooks/useBatchBadgeFetch", () => ({
getFreshBatchData: vi.fn(() => null),
}));
// Mock the api module
vi.mock("../../api", () => ({
fetchTaskDetail: vi.fn(),
@@ -90,6 +106,9 @@ const highFanout = {
afterEach(() => {
vi.useRealTimers();
useTaskDiffStatsMock.mockReturnValue({ stats: null, loading: false });
badgeUpdatesMock.clear();
subscribeToBadgeMock.mockReset();
unsubscribeFromBadgeMock.mockReset();
});
describe("TaskCard", () => {
@@ -279,6 +298,31 @@ describe("TaskCard", () => {
expect(onOpenDetail).not.toHaveBeenCalled();
});
it("renders GitHub badge from live websocket data even when task payload has no badge fields", () => {
badgeUpdatesMock.set("default:FN-001", {
prInfo: {
url: "https://github.com/owner/repo/pull/77",
number: 77,
status: "open",
title: "Live PR",
headBranch: "feature/live",
baseBranch: "main",
commentCount: 0,
},
timestamp: "2026-05-13T12:00:00.000Z",
});
render(
<TaskCard
task={makeTask({ column: "in-review", prInfo: undefined, issueInfo: undefined })}
onOpenDetail={noop}
addToast={noop}
/>,
);
expect(screen.getByRole("link", { name: "#77" })).toBeDefined();
});
it("clicking issue badge text does not open the task detail modal", () => {
const onOpenDetail = vi.fn();
render(

View File

@@ -0,0 +1,33 @@
import { describe, expect, it } from "vitest";
import { pickPreferredBadge } from "../TaskCardBadge";
describe("pickPreferredBadge", () => {
const taskValue = { number: 1, lastCheckedAt: "2026-05-13T10:00:00.000Z" };
const liveValue = { number: 2, lastCheckedAt: "2026-05-13T11:00:00.000Z" };
it("returns undefined when both values are missing", () => {
expect(pickPreferredBadge(undefined, undefined, undefined, undefined)).toBeUndefined();
});
it("returns undefined when live is null and task is missing", () => {
expect(pickPreferredBadge(null, "2026-05-13T12:00:00.000Z", undefined, undefined)).toBeUndefined();
});
it("keeps task value when live is null", () => {
expect(
pickPreferredBadge(null, "2026-05-13T12:00:00.000Z", taskValue, "2026-05-13T10:00:00.000Z"),
).toEqual(taskValue);
});
it("prefers live when newer", () => {
expect(
pickPreferredBadge(liveValue, "2026-05-13T12:00:00.000Z", taskValue, "2026-05-13T10:00:00.000Z"),
).toEqual(liveValue);
});
it("prefers task when newer", () => {
expect(
pickPreferredBadge(liveValue, "2026-05-13T10:00:00.000Z", taskValue, "2026-05-13T12:00:00.000Z"),
).toEqual(taskValue);
});
});