Skip to content
Merged
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
61 changes: 53 additions & 8 deletions src/components/SorobanPanel.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ vi.mock("@/context/useSorokit", () => ({
})),
}));

const mockInvokeContract = vi.fn();

// Mock the getClient from lib/client
vi.mock("../lib/client", () => ({
getClient: () => ({
Expand Down Expand Up @@ -48,7 +46,7 @@ describe("SorobanPanel", () => {
const { rerender } = render(
<SorobanPanel contractId={currentContractId} onContractIdChange={setContractId} />
);

// Fill out contract ID and method to enable the button
const methodInput = screen.getByPlaceholderText(/transfer/i);
const argsInput = screen.getByPlaceholderText(/\[.*\]/i);
Expand All @@ -61,19 +59,66 @@ describe("SorobanPanel", () => {
rerender(<SorobanPanel contractId="C123" onContractIdChange={setContractId} />);

expect(invokeBtn).not.toBeDisabled();

fireEvent.click(invokeBtn);

const errorText = await screen.findByText(/Invalid JSON in arguments/i);
expect(errorText).toBeInTheDocument();
});

// ── Non-array JSON args (#118) ────────────────────────────────────────────
// Valid JSON that is not an array (e.g. `{}` or `42`) must be rejected before
// it is forwarded to invokeContract, which expects an argument array.
async function invokeWithArgs(argsValue: string) {
const { rerender } = render(
<SorobanPanel contractId="" onContractIdChange={() => {}} />,
);
fireEvent.change(screen.getByPlaceholderText(/c\.\.\./i), {
target: { value: "C123" },
});
fireEvent.change(screen.getByPlaceholderText(/transfer/i), {
target: { value: "mint" },
});
fireEvent.change(screen.getByPlaceholderText(/\[.*\]/i), {
target: { value: argsValue },
});
rerender(<SorobanPanel contractId="C123" onContractIdChange={() => {}} />);
fireEvent.click(screen.getByRole("button", { name: /invoke/i }));
}

it("rejects a JSON object (non-array) with a 'must be a JSON array' error", async () => {
await invokeWithArgs("{}");
expect(
await screen.findByText(/Arguments must be a JSON array/i),
).toBeInTheDocument();
});

it("rejects a JSON number (non-array) with a 'must be a JSON array' error", async () => {
await invokeWithArgs("42");
expect(
await screen.findByText(/Arguments must be a JSON array/i),
).toBeInTheDocument();
});

it("accepts a valid JSON array and reaches the success state", async () => {
mockInvokeContract.mockResolvedValueOnce({
data: { success: true },
error: null,
});
await invokeWithArgs('["arg1", 42]');
// No validation error; the mocked invokeContract resolves successfully.
expect(
screen.queryByText(/Arguments must be a JSON array/i),
).not.toBeInTheDocument();
expect(await screen.findByText(/Result/i)).toBeInTheDocument();
});

it("should show error when invokeContract fails", async () => {
mockInvokeContract.mockResolvedValueOnce({ data: null, error: "Contract execution failed" });

const onContractIdChange = vi.fn();
render(<SorobanPanel contractId="C123" onContractIdChange={onContractIdChange} />);

const methodInput = screen.getByLabelText("Method");
const invokeBtn = screen.getByRole("button", { name: /invoke/i });

Expand All @@ -86,10 +131,10 @@ describe("SorobanPanel", () => {

it("should invoke contract successfully, show result, and reset state on Clear", async () => {
mockInvokeContract.mockResolvedValueOnce({ data: { success: true, balance: 1000 }, error: null });

const onContractIdChange = vi.fn();
render(<SorobanPanel contractId="C123" onContractIdChange={onContractIdChange} />);

const methodInput = screen.getByLabelText("Method");
const argsInput = screen.getByLabelText("Arguments (JSON array)");
const invokeBtn = screen.getByRole("button", { name: /invoke/i });
Expand Down
Loading