diff --git a/components/asset/__tests__/AssetCard.test.tsx b/components/asset/__tests__/AssetCard.test.tsx new file mode 100644 index 0000000..f23c0c5 --- /dev/null +++ b/components/asset/__tests__/AssetCard.test.tsx @@ -0,0 +1,109 @@ +/** + * Tests for components/asset/AssetCard.tsx + * + * Strategy: mock next/link so it renders as a plain without a router, + * build AssetEntry fixtures with makeAsset(), and assert that the card renders + * the asset name, type badge, formatted valuation, and links to the correct + * detail route. Also covers the inactive chip and the holders / supply display. + */ + +import React from "react"; +import { render, screen } from "@testing-library/react"; +import type { AssetEntry } from "@/types"; + +// Render Next.js Link as a plain — no router needed. +jest.mock("next/link", () => { + const MockLink = ({ + children, + href, + }: { + children: React.ReactNode; + href: string; + }) => {children}; + MockLink.displayName = "MockLink"; + return MockLink; +}); + +import { AssetCard } from "../AssetCard"; + +// ── helpers ──────────────────────────────────────────────────────────────── + +function makeAsset(overrides: Partial = {}): AssetEntry { + return { + id: 1n, + tokenContract: "CTOKEN123456789", + issuer: "GISSUER1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ", + name: "Lagos Office Tower", + assetType: "real_estate", + valuation: 5_000_000_00n, + createdAt: 100, + active: true, + ...overrides, + }; +} + +// ── tests ────────────────────────────────────────────────────────────────── + +describe("AssetCard", () => { + it("renders the asset name", () => { + render(); + expect(screen.getByText("Lagos Office Tower")).toBeInTheDocument(); + }); + + it("renders the asset type badge", () => { + render(); + expect(screen.getByText("Real Estate")).toBeInTheDocument(); + }); + + it("renders the formatted valuation", () => { + render(); + // 5_000_000_00n cents => $5M (compact) + expect(screen.getByText("$5M")).toBeInTheDocument(); + }); + + it("links to the correct asset detail route", () => { + render(); + const link = screen.getByRole("link"); + expect(link).toHaveAttribute("href", "/asset/7"); + }); + + it("shows 'Inactive' chip when asset.active is false", () => { + render(); + expect(screen.getByText("Inactive")).toBeInTheDocument(); + }); + + it("does not show the 'Inactive' chip when asset is active", () => { + render(); + expect(screen.queryByText("Inactive")).not.toBeInTheDocument(); + }); + + it("shows the holder count when provided", () => { + render(); + expect(screen.getByText("42")).toBeInTheDocument(); + }); + + it("shows the supply string when provided", () => { + render(); + expect(screen.getByText("1,000,000")).toBeInTheDocument(); + }); + + it("shows '—' when neither holders nor supply is provided", () => { + render(); + expect(screen.getByText("—")).toBeInTheDocument(); + }); + + it("renders 'Unnamed asset' fallback when name is empty", () => { + render(); + expect(screen.getByText("Unnamed asset")).toBeInTheDocument(); + }); + + it("renders the Invoice type badge for an invoice asset", () => { + render(); + expect(screen.getByText("Invoice")).toBeInTheDocument(); + }); + + it("renders the Commodity type badge for a commodity asset", () => { + render(); + expect(screen.getByText("Commodity")).toBeInTheDocument(); + }); +});