Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/__tests__/exports.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
import { describe, it, expect } from "vitest";
import * as exports from "@/components/index";

describe("Public Exports Smoke Test", () => {
it("exports are valid functions or components", () => {
const namedExports = Object.entries(exports);

expect(namedExports.length).toBeGreaterThan(0);

for (const [name, value] of namedExports) {
// Components, providers, and hooks should be functions
// We skip checking types as they disappear at runtime and won't be in the object
expect(typeof value).toBe("function", `Export ${name} is not a function. Got ${typeof value}`);
}
import { describe, expect,it } from 'vitest';

import * as exports from '../components/index';
Expand Down
90 changes: 90 additions & 0 deletions src/__tests__/payment-flow.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { render, screen, act, fireEvent, waitFor } from "@testing-library/react";
import { describe, it, expect, vi, beforeEach } from "vitest";
import { SorokitProvider } from "@/context/SorokitProvider";
import { TransactionPanel } from "@/components/TransactionPanel";
import { getClient } from "@/lib/client";

describe("Payment Flow Integration", () => {
let mockClient: ReturnType<typeof getClient>;
const mockAddress = "GABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";

beforeEach(() => {
mockClient = {
wallet: {
connect: vi.fn().mockResolvedValue({ data: { address: mockAddress }, error: null }),
disconnect: vi.fn().mockResolvedValue(undefined),
},
account: {
getAccount: vi.fn().mockResolvedValue({ data: { sequence: "123456789" }, error: null }),
getBalances: vi.fn().mockResolvedValue({ data: [{ asset: "XLM", balance: "100.5" }], error: null }),
},
network: {
getNetwork: vi.fn().mockResolvedValue({ data: { name: "mainnet" }, error: null }),
switchNetwork: vi.fn().mockResolvedValue({ data: { name: "testnet" }, error: null }),
},
transaction: {
submit: vi.fn().mockResolvedValue({ data: { hash: "txhash123", ledger: 100 }, error: null }),
}
} as unknown as ReturnType<typeof getClient>;
});

it("submits payment with correct destination, amount, and source", async () => {
// We render SorokitProvider, but we must "connect" it first to have an active address.
// We can simulate this by calling client.wallet.connect() inside a wrapper, or we can just
// click a connect button if we add one.
// Alternatively, SorokitProvider might just rely on the client.
// Let's create a small component to trigger connection.
const Wrapper = () => {
return (
<SorokitProvider client={mockClient}>
<TransactionPanel />
<ConnectTrigger />
</SorokitProvider>
);
};

const ConnectTrigger = () => {
const { connectWallet } = require("@/context/useSorokit").useSorokit();
return <button onClick={() => connectWallet()}>Test Connect</button>;
};

render(<Wrapper />);

// Connect the wallet first so TransactionPanel doesn't show "Wallet not connected"
await act(async () => {
fireEvent.click(screen.getByRole("button", { name: "Test Connect" }));
});

const destInput = screen.getByLabelText("Destination Address");
const amountInput = screen.getByLabelText("Amount (XLM)");
const submitBtn = screen.getByRole("button", { name: "Send Payment" });

const validDest = "GCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC";
const validAmount = "15.5";

fireEvent.change(destInput, { target: { value: validDest } });
fireEvent.change(amountInput, { target: { value: validAmount } });

await act(async () => {
fireEvent.click(submitBtn);
});

// Verify loading state appears
expect(screen.getByRole("button", { name: "Submitting…" })).toBeInTheDocument();

// Verify success state renders
await waitFor(() => {
expect(screen.getByText("Transaction submitted")).toBeInTheDocument();
expect(screen.getByText("txhash123")).toBeInTheDocument();
});

// Most importantly, verify the mock client's transaction.submit was called with the correct parameters
expect(mockClient.transaction.submit).toHaveBeenCalledWith(
expect.objectContaining({
destination: validDest,
amount: validAmount,
source: mockAddress
})
);
});
});
66 changes: 66 additions & 0 deletions src/__tests__/wallet-flow.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { render, screen, act, fireEvent, waitFor } from "@testing-library/react";
import { describe, it, expect, vi, beforeEach } from "vitest";
import { SorokitProvider } from "@/context/SorokitProvider";
import { WalletConnectButton } from "@/components/WalletConnectButton";
import { AccountCard } from "@/components/AccountCard";
import { getClient } from "@/lib/client";

describe("Wallet Connect Flow Integration", () => {
let mockClient: ReturnType<typeof getClient>;

beforeEach(() => {
mockClient = {
wallet: {
connect: vi.fn().mockResolvedValue({ data: { address: "GABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" }, error: null }),
disconnect: vi.fn().mockResolvedValue(undefined),
},
account: {
getAccount: vi.fn().mockResolvedValue({ data: { sequence: "123456789" }, error: null }),
getBalances: vi.fn().mockResolvedValue({ data: [{ asset: "XLM", balance: "100.5" }], error: null }),
},
network: {
getNetwork: vi.fn().mockResolvedValue({ data: { name: "mainnet" }, error: null }),
switchNetwork: vi.fn().mockResolvedValue({ data: { name: "testnet" }, error: null }),
},
} as unknown as ReturnType<typeof getClient>;
});

it("connects wallet, displays account, and disconnects", async () => {
render(
<SorokitProvider client={mockClient}>
<WalletConnectButton />
<AccountCard />
</SorokitProvider>
);

// Initial state: Wallet not connected, AccountCard should not show sequence
expect(screen.getByRole("button", { name: /connect/i })).toBeInTheDocument();
expect(screen.queryByText(/Sequence:/i)).not.toBeInTheDocument();

// Action 1: Connect Wallet
await act(async () => {
fireEvent.click(screen.getByRole("button", { name: /connect/i }));
});

// Verification 1: Wallet connected, AccountCard renders account data
expect(mockClient.wallet.connect).toHaveBeenCalled();

// Disconnect button should appear inside WalletConnectButton or similar if it toggles
// We wait for the account data to be fetched and rendered in AccountCard
await waitFor(() => {
expect(screen.getByText(/123456789/i)).toBeInTheDocument(); // Sequence number
});

// Action 2: Disconnect Wallet
// The WalletConnectButton usually turns into a Disconnect button when connected
const disconnectBtn = screen.getByRole("button", { name: /disconnect/i });
await act(async () => {
fireEvent.click(disconnectBtn);
});

// Verification 2: Wallet disconnected, AccountCard returns null (or clears data)
expect(mockClient.wallet.disconnect).toHaveBeenCalled();
expect(screen.getByRole("button", { name: /connect/i })).toBeInTheDocument();
expect(screen.queryByText(/123456789/i)).not.toBeInTheDocument();
});
});