Skip to content
Open
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
97 changes: 88 additions & 9 deletions __mocks__/@sharibo/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,16 @@ import type {
Identity,
ContractProof,
ContractVerificationKey,
SharaboNetworkConfig,
SharaboClient,
ShariboNetworkConfig,
ShariboClient,
TxResult,
CircleView,
MerkleProof,
} from "@sharibo/client";

export const TREE_LEVELS = 4;
export const MAX_CIRCLE_SIZE = 2 ** TREE_LEVELS;

// ── Identity ──────────────────────────────────────────────────────────────────

export const FR_MODULUS =
Expand Down Expand Up @@ -125,34 +128,34 @@ export const estimateClaimFee = vi.fn(

export const connect = vi.fn(
async (
_config: SharaboNetworkConfig,
_config: ShariboNetworkConfig,
_keypair: unknown,
): Promise<SharaboClient> => ({}) as SharaboClient,
): Promise<ShariboClient> => ({}) as ShariboClient,
);

export const createCircle = vi.fn(
async (_client: SharaboClient, _args: unknown): Promise<TxResult<bigint>> => ({
result: 0n,
async (_client: ShariboClient, _args: unknown): Promise<TxResult<bigint>> => ({
result: 37n,
hash: "mockCreateHash",
}),
);

export const fund = vi.fn(
async (_client: SharaboClient, _args: unknown): Promise<TxResult<void>> => ({
async (_client: ShariboClient, _args: unknown): Promise<TxResult<void>> => ({
result: undefined,
hash: "mockFundHash",
}),
);

export const claim = vi.fn(
async (_client: SharaboClient, _args: unknown): Promise<TxResult<void>> => ({
async (_client: ShariboClient, _args: unknown): Promise<TxResult<void>> => ({
result: undefined,
hash: "mockClaimHash",
}),
);

export const getCircle = vi.fn(
async (_client: SharaboClient, _circleId: bigint): Promise<CircleView> => ({
async (_client: ShariboClient, _circleId: bigint): Promise<CircleView> => ({
admin: "MOCK_ADMIN",
token: "MOCK_TOKEN",
root: 12345n,
Expand All @@ -162,3 +165,79 @@ export const getCircle = vi.fn(
pot: 0n,
}),
);

export const getCircleCount = vi.fn(async (): Promise<bigint> => 1n);

export const hasClaimed = vi.fn(
async (_client: ShariboClient, _circleId: bigint, _nullifierHash: bigint): Promise<boolean> =>
false,
);

// ── SDK facade ────────────────────────────────────────────────────────────────
//
// Mirrors the real `ShariboSDK.connect(...)` interface. In tests the bound
// client is discarded; each method simply delegates to the stub free
// functions above so existing assertions on `connect`/`createCircle`/... keep
// working and interaction flows can be asserted through the facade too.

export class ShariboSDK {
readonly networkConfig: ShariboNetworkConfig;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
readonly signer: any;
readonly publicKey: string;
readonly client: ShariboClient;

private constructor(
networkConfig: ShariboNetworkConfig,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
signer: any,
publicKey: string,
) {
this.networkConfig = networkConfig;
this.signer = signer;
this.publicKey = publicKey;
this.client = ({} as ShariboClient);
}

static async connect(
config: ShariboNetworkConfig,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
keypairOrSigner: any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
_options?: any,
): Promise<ShariboSDK> {
const publicKey =
typeof keypairOrSigner?.publicKey === "function"
? keypairOrSigner.publicKey()
: keypairOrSigner?.publicKey ?? "MOCK_PUBLIC_KEY";
return new ShariboSDK(config, keypairOrSigner, publicKey);
}

createCircle(args: unknown): Promise<TxResult<bigint>> {
return createCircle(this.client, args);
}

fund(args: unknown): Promise<TxResult<void>> {
return fund(this.client, args);
}

claim(args: unknown): Promise<TxResult<void>> {
return claim(this.client, args);
}

getCircle(circleId: bigint): Promise<CircleView> {
return getCircle(this.client, circleId);
}

getCircleCount(): Promise<bigint> {
return getCircleCount(this.client);
}

getStatus(): Promise<bigint> {
return this.getCircleCount();
}

hasClaimed(circleId: bigint, nullifierHash: bigint): Promise<boolean> {
return hasClaimed(this.client, circleId, nullifierHash);
}
}
22 changes: 17 additions & 5 deletions app/src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen } from "@testing-library/react";
import App from "./App";
import { I18nProvider } from "./i18n";

// Activate the manual mock at <root>/__mocks__/@sharibo/client.ts.
vi.mock("@sharibo/client");
Expand All @@ -30,32 +31,43 @@ vi.mock("@stellar/stellar-sdk", async (importOriginal) => {
// Stub fetch so the "friendbot" call in startCircle never fires.
global.fetch = vi.fn().mockResolvedValue({ ok: true, json: async () => ({}) });

// The app reads a `sharibo.locale` preference from localStorage; give each
// test a clean slate.
function renderApp() {
return render(
<I18nProvider>
<App />
</I18nProvider>,
);
}

describe("App β€” landing screen", () => {
beforeEach(() => {
vi.clearAllMocks();
window.localStorage.clear();
});

it("renders the SHARIBO heading", () => {
render(<App />);
renderApp();
expect(screen.getByRole("heading", { name: /sharibo/i })).toBeInTheDocument();
});

it("renders the launch button", () => {
render(<App />);
renderApp();
const btn = screen.getByRole("button", { name: /launch a 5-member circle on testnet/i });
expect(btn).toBeInTheDocument();
expect(btn).not.toBeDisabled();
});

it("renders the tagline copy", () => {
render(<App />);
renderApp();
expect(
screen.getByText(/private rotating savings circle/i),
).toBeInTheDocument();
});

it("renders the testnet-only disclaimer fineprint", () => {
render(<App />);
renderApp();
expect(screen.getByText(/testnet only/i)).toBeInTheDocument();
});
});
});
Loading