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();
+ });
+});