diff --git a/src/components/SorobanPanel.test.tsx b/src/components/SorobanPanel.test.tsx index 165f8ce..857e269 100644 --- a/src/components/SorobanPanel.test.tsx +++ b/src/components/SorobanPanel.test.tsx @@ -13,8 +13,6 @@ vi.mock("@/context/useSorokit", () => ({ })), })); -const mockInvokeContract = vi.fn(); - // Mock the getClient from lib/client vi.mock("../lib/client", () => ({ getClient: () => ({ @@ -48,7 +46,7 @@ describe("SorobanPanel", () => { const { rerender } = render( ); - + // Fill out contract ID and method to enable the button const methodInput = screen.getByPlaceholderText(/transfer/i); const argsInput = screen.getByPlaceholderText(/\[.*\]/i); @@ -61,19 +59,66 @@ describe("SorobanPanel", () => { rerender(); 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( + {}} />, + ); + 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( {}} />); + 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(); - + const methodInput = screen.getByLabelText("Method"); const invokeBtn = screen.getByRole("button", { name: /invoke/i }); @@ -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(); - + const methodInput = screen.getByLabelText("Method"); const argsInput = screen.getByLabelText("Arguments (JSON array)"); const invokeBtn = screen.getByRole("button", { name: /invoke/i });