diff --git a/src/components/treasuryOverviewPage/Stream.ts b/src/components/treasuryOverviewPage/Stream.ts index 807dbfa1..6cd5b071 100644 --- a/src/components/treasuryOverviewPage/Stream.ts +++ b/src/components/treasuryOverviewPage/Stream.ts @@ -5,7 +5,7 @@ export interface Stream { id: string; recipient: string; rate: string; - accruedAmount?: string | number; + accruedAmount: number; status: StreamStatus; - startDate?: string; + startDate: string; } diff --git a/src/components/treasuryOverviewPage/StreamRow.tsx b/src/components/treasuryOverviewPage/StreamRow.tsx index 01e52612..dc347fd1 100644 --- a/src/components/treasuryOverviewPage/StreamRow.tsx +++ b/src/components/treasuryOverviewPage/StreamRow.tsx @@ -330,11 +330,9 @@ export default function StreamRow({
{stream.rate}
- {typeof stream.accruedAmount === "number" && ( -
- {formatAccruedAmount(stream.accruedAmount)} -
- )} +
+ {formatAccruedAmount(stream.accruedAmount)} +
diff --git a/src/components/treasuryOverviewPage/__tests__/StreamRow.test.tsx b/src/components/treasuryOverviewPage/__tests__/StreamRow.test.tsx index b96ee0cf..580838b4 100644 --- a/src/components/treasuryOverviewPage/__tests__/StreamRow.test.tsx +++ b/src/components/treasuryOverviewPage/__tests__/StreamRow.test.tsx @@ -11,6 +11,7 @@ const stream: Stream = { rate: "2,500 USDC/mo", accruedAmount: 1234.56, status: "Active", + startDate: "2026-01-01", }; function renderRow(rowStream: Stream = stream) { @@ -206,5 +207,110 @@ describe("StreamRow", () => { // Menu should close after selection expect(screen.queryByRole("menu")).not.toBeInTheDocument(); }); + + describe("Regression tests for issue #1459", () => { + it("renders accrued amount for streams with zero accrued amount", () => { + renderRow({ + ...stream, + id: "STR-ZERO", + accruedAmount: 0, + }); + + expect(screen.getByText("0 USDC accrued")).toBeInTheDocument(); + }); + + it("renders status for completed streams", () => { + renderRow({ + ...stream, + id: "STR-COMP", + status: "Completed", + }); + + expect(screen.getByRole("status", { name: "Completed status" })).toHaveTextContent("COMPLETED"); + }); + + it("renders status for paused streams", () => { + renderRow({ + ...stream, + id: "STR-PAUSED", + status: "Paused", + }); + + expect(screen.getByRole("status", { name: "Paused status" })).toHaveTextContent("PAUSED"); + }); + + it("renders status for active streams", () => { + renderRow({ + ...stream, + id: "STR-ACTIVE", + status: "Active", + }); + + expect(screen.getByRole("status", { name: "Active status" })).toHaveTextContent("ACTIVE"); + }); + + it("renders accrued amount for streams with large values", () => { + renderRow({ + ...stream, + id: "STR-LARGE", + accruedAmount: 999999999.99, + }); + + expect(screen.getByText("999,999,999.99 USDC accrued")).toBeInTheDocument(); + }); + + it("renders all required fields without defensive checks", () => { + renderRow({ + id: "STR-ALL-FIELDS", + name: "Test Stream", + recipient: "GTEST123456789ABCDEF", + rate: "1,000 USDC/mo", + accruedAmount: 5000, + status: "Active", + startDate: "2026-01-01", + }); + + expect(screen.getByText("Test Stream")).toBeInTheDocument(); + expect(screen.getByText("GTEST1...CDEF")).toBeInTheDocument(); + expect(screen.getByText("1,000 USDC/mo")).toBeInTheDocument(); + expect(screen.getByText("5,000 USDC accrued")).toBeInTheDocument(); + expect(screen.getByRole("status", { name: "Active status" })).toHaveTextContent("ACTIVE"); + }); + + it("consistently renders status matching normalized streamRecords data", () => { + // Test all three valid statuses from streamRecords StreamStatus type + const statuses: Array<"Active" | "Paused" | "Completed"> = ["Active", "Paused", "Completed"]; + + statuses.forEach((status) => { + const { unmount } = render( + + + + + +
+
+ ); + + expect(screen.getByRole("status", { name: `${status} status` })).toHaveTextContent(status.toUpperCase()); + unmount(); + }); + }); + + it("renders accrued amount as number consistently with streamRecords normalization", () => { + // streamRecords.readNumber() returns 0 for invalid values, so we test that + // the UI renders the numeric value without type checking + renderRow({ + ...stream, + id: "STR-NUMERIC", + accruedAmount: 0, + }); + + expect(screen.getByText(/0.*USDC accrued/)).toBeInTheDocument(); + }); + }); }); diff --git a/src/components/treasuryOverviewPage/__tests__/malformedRecordDefensive.test.tsx b/src/components/treasuryOverviewPage/__tests__/malformedRecordDefensive.test.tsx index 341d6540..d2577c29 100644 --- a/src/components/treasuryOverviewPage/__tests__/malformedRecordDefensive.test.tsx +++ b/src/components/treasuryOverviewPage/__tests__/malformedRecordDefensive.test.tsx @@ -52,13 +52,15 @@ describe("StreamRow with records missing optional fields", () => { }, ); - it("omits the accrued line when the optional accruedAmount is absent", () => { - const stream = toLegacyStream(makeStreamRecord()); - delete stream.accruedAmount; + it("renders the accrued line even when source data is missing (normalization provides fallback)", () => { + // With the new design, accruedAmount is always required and the normalization + // layer provides a fallback of 0 when source data is missing + const record = normalizeStreamRecord({ id: "STR-NO-AMOUNT" }); + const stream = toLegacyStream(record); renderRow(stream); expect(screen.getByText(stream.name)).toBeInTheDocument(); - expect(screen.queryByText(/accrued/)).not.toBeInTheDocument(); + expect(screen.getByText(/accrued/)).toBeInTheDocument(); }); it("renders the accrued line when accruedAmount is present", () => { diff --git a/src/components/treasuryOverviewPage/__tests__/useTreasuryOverviewData.test.ts b/src/components/treasuryOverviewPage/__tests__/useTreasuryOverviewData.test.ts index 6a8b8428..9b1b897e 100644 --- a/src/components/treasuryOverviewPage/__tests__/useTreasuryOverviewData.test.ts +++ b/src/components/treasuryOverviewPage/__tests__/useTreasuryOverviewData.test.ts @@ -110,6 +110,7 @@ describe("useTreasuryOverviewData", () => { rate: "1,200 XLM/mo", accruedAmount: 500, status: "Active", + startDate: "", }); }); diff --git a/src/components/treasuryOverviewPage/useTreasuryOverviewData.ts b/src/components/treasuryOverviewPage/useTreasuryOverviewData.ts index a3c4a48c..1607fbb5 100644 --- a/src/components/treasuryOverviewPage/useTreasuryOverviewData.ts +++ b/src/components/treasuryOverviewPage/useTreasuryOverviewData.ts @@ -51,7 +51,7 @@ export function toLegacyStream(record: StreamRecord): Stream { rate: formatMonthlyRate(record), accruedAmount: record.streamedAmount, status: record.status, - startDate: record.startDate, + startDate: record.startDate || "", }; }