8d9e26872b
B-1: useViewportPopover — ignoreScrollContainers option; scroll events originating inside the timeline canvas no longer close point-anchor popovers B-2: AllocationPopover, DemandPopover, NewAllocationPopover — thread scrollContainerRef through so horizontal timeline scroll is ignored B-3: AllocationPopover — staleTime 0 so SSE reconnect triggers immediate refetch B-4: useViewportPopover.test.ts — 6 new tests (scroll close, ignore container, resize close, style clamping) B-5: AllocationPopover.test.tsx — loading state + happy-path tests added Co-Authored-By: claude-flow <ruv@ruv.net>
168 lines
4.5 KiB
TypeScript
168 lines
4.5 KiB
TypeScript
import React from "react";
|
|
import { renderToStaticMarkup } from "react-dom/server";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { AllocationPopover } from "./AllocationPopover.js";
|
|
|
|
const mockUseQuery = vi.fn();
|
|
const mockUseMutation = vi.fn(() => ({ mutate: vi.fn() }));
|
|
const mockUseUtils = vi.fn(() => ({
|
|
allocation: {
|
|
getAssignmentById: { invalidate: vi.fn() },
|
|
listView: { invalidate: vi.fn() },
|
|
},
|
|
}));
|
|
|
|
vi.mock("~/lib/trpc/client.js", () => ({
|
|
trpc: {
|
|
useUtils: () => mockUseUtils(),
|
|
allocation: {
|
|
getAssignmentById: {
|
|
useQuery: (...args: unknown[]) => mockUseQuery(...args),
|
|
},
|
|
},
|
|
timeline: {
|
|
updateAllocationInline: {
|
|
useMutation: () => mockUseMutation(),
|
|
},
|
|
carveAllocationRange: {
|
|
useMutation: () => mockUseMutation(),
|
|
},
|
|
},
|
|
},
|
|
}));
|
|
|
|
vi.mock("~/hooks/useInvalidatePlanningViews.js", () => ({
|
|
useInvalidateTimeline: () => vi.fn(),
|
|
}));
|
|
|
|
vi.mock("~/hooks/useViewportPopover.js", () => ({
|
|
useViewportPopover: () => ({
|
|
ref: { current: null },
|
|
style: {},
|
|
}),
|
|
}));
|
|
|
|
vi.mock("~/components/ui/DateInput.js", () => ({
|
|
DateInput: () => null,
|
|
}));
|
|
|
|
vi.mock("~/components/ui/ProjectCombobox.js", () => ({
|
|
ProjectCombobox: () => null,
|
|
}));
|
|
|
|
describe("AllocationPopover", () => {
|
|
beforeEach(() => {
|
|
mockUseQuery.mockReset();
|
|
mockUseMutation.mockClear();
|
|
mockUseUtils.mockClear();
|
|
});
|
|
|
|
it("renders an error state when the allocation lookup fails", () => {
|
|
mockUseQuery.mockReturnValue({
|
|
data: undefined,
|
|
isLoading: false,
|
|
error: new Error("Assignment not found"),
|
|
});
|
|
|
|
const html = renderToStaticMarkup(
|
|
<AllocationPopover
|
|
allocationId="assignment_missing"
|
|
projectId="project_1"
|
|
anchorX={120}
|
|
anchorY={40}
|
|
onClose={() => {}}
|
|
onOpenPanel={() => {}}
|
|
/>,
|
|
);
|
|
|
|
expect(html).toContain("data-testid=\"timeline-allocation-popover-error\"");
|
|
expect(html).toContain("The selected booking could not be loaded right now.");
|
|
expect(html).toContain("Assignment not found");
|
|
});
|
|
|
|
it("renders an unavailable state when the lookup returns no allocation", () => {
|
|
mockUseQuery.mockReturnValue({
|
|
data: undefined,
|
|
isLoading: false,
|
|
error: null,
|
|
});
|
|
|
|
const html = renderToStaticMarkup(
|
|
<AllocationPopover
|
|
allocationId="assignment_missing"
|
|
projectId="project_1"
|
|
anchorX={120}
|
|
anchorY={40}
|
|
onClose={() => {}}
|
|
onOpenPanel={() => {}}
|
|
/>,
|
|
);
|
|
|
|
expect(html).toContain("data-testid=\"timeline-allocation-popover-unavailable\"");
|
|
expect(html).toContain("The selected booking could not be resolved from the current timeline data.");
|
|
});
|
|
|
|
it("renders a loading skeleton when the allocation is being fetched", () => {
|
|
mockUseQuery.mockReturnValue({
|
|
data: undefined,
|
|
isLoading: true,
|
|
error: null,
|
|
});
|
|
|
|
const html = renderToStaticMarkup(
|
|
<AllocationPopover
|
|
allocationId="assignment_loading"
|
|
projectId="project_1"
|
|
anchorX={120}
|
|
anchorY={40}
|
|
onClose={() => {}}
|
|
onOpenPanel={() => {}}
|
|
/>,
|
|
);
|
|
|
|
expect(html).toContain("data-testid=\"timeline-allocation-popover-loading\"");
|
|
});
|
|
|
|
it("renders allocation data when provided as initialAllocation", () => {
|
|
// useQuery is always called (with enabled: false), so we need a baseline return value
|
|
mockUseQuery.mockReturnValue({
|
|
data: undefined,
|
|
isLoading: false,
|
|
error: null,
|
|
});
|
|
|
|
const allocation = {
|
|
id: "assignment_1",
|
|
entityId: "assignment_1",
|
|
projectId: "project_1",
|
|
resourceId: "resource_1",
|
|
startDate: new Date("2026-01-01"),
|
|
endDate: new Date("2026-01-31"),
|
|
hoursPerDay: 8,
|
|
role: "Developer",
|
|
metadata: null,
|
|
resource: {
|
|
displayName: "Alice Smith",
|
|
eid: "alice.smith",
|
|
lcrCents: 0,
|
|
},
|
|
};
|
|
|
|
const html = renderToStaticMarkup(
|
|
<AllocationPopover
|
|
allocationId="assignment_1"
|
|
projectId="project_1"
|
|
initialAllocation={allocation as any}
|
|
anchorX={120}
|
|
anchorY={40}
|
|
onClose={() => {}}
|
|
onOpenPanel={() => {}}
|
|
/>,
|
|
);
|
|
|
|
expect(html).not.toContain("data-testid=\"timeline-allocation-popover-error\"");
|
|
expect(html).not.toContain("data-testid=\"timeline-allocation-popover-unavailable\"");
|
|
expect(html).not.toContain("data-testid=\"timeline-allocation-popover-loading\"");
|
|
});
|
|
});
|