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