Spaces:
Build error
Build error
| import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; | |
| import { render, screen, waitFor } from "@testing-library/react"; | |
| import userEvent from "@testing-library/user-event"; | |
| import { afterEach, beforeEach, describe, expect, it, test, vi } from "vitest"; | |
| import OpenHands from "#/api/open-hands"; | |
| import { PaymentForm } from "#/components/features/payment/payment-form"; | |
| describe("PaymentForm", () => { | |
| const getBalanceSpy = vi.spyOn(OpenHands, "getBalance"); | |
| const createCheckoutSessionSpy = vi.spyOn(OpenHands, "createCheckoutSession"); | |
| const getConfigSpy = vi.spyOn(OpenHands, "getConfig"); | |
| const renderPaymentForm = () => | |
| render(<PaymentForm />, { | |
| wrapper: ({ children }) => ( | |
| <QueryClientProvider client={new QueryClient()}> | |
| {children} | |
| </QueryClientProvider> | |
| ), | |
| }); | |
| beforeEach(() => { | |
| // useBalance hook will return the balance only if the APP_MODE is "saas" and the billing feature is enabled | |
| getConfigSpy.mockResolvedValue({ | |
| APP_MODE: "saas", | |
| GITHUB_CLIENT_ID: "123", | |
| POSTHOG_CLIENT_KEY: "456", | |
| FEATURE_FLAGS: { | |
| ENABLE_BILLING: true, | |
| HIDE_LLM_SETTINGS: false, | |
| }, | |
| }); | |
| }); | |
| afterEach(() => { | |
| vi.clearAllMocks(); | |
| }); | |
| it("should render the users current balance", async () => { | |
| getBalanceSpy.mockResolvedValue("100.50"); | |
| renderPaymentForm(); | |
| await waitFor(() => { | |
| const balance = screen.getByTestId("user-balance"); | |
| expect(balance).toHaveTextContent("$100.50"); | |
| }); | |
| }); | |
| it("should render the users current balance to two decimal places", async () => { | |
| getBalanceSpy.mockResolvedValue("100"); | |
| renderPaymentForm(); | |
| await waitFor(() => { | |
| const balance = screen.getByTestId("user-balance"); | |
| expect(balance).toHaveTextContent("$100.00"); | |
| }); | |
| }); | |
| test("the user can top-up a specific amount", async () => { | |
| const user = userEvent.setup(); | |
| renderPaymentForm(); | |
| const topUpInput = await screen.findByTestId("top-up-input"); | |
| await user.type(topUpInput, "50"); | |
| const topUpButton = screen.getByText("PAYMENT$ADD_CREDIT"); | |
| await user.click(topUpButton); | |
| expect(createCheckoutSessionSpy).toHaveBeenCalledWith(50); | |
| }); | |
| it("should only accept integer values", async () => { | |
| const user = userEvent.setup(); | |
| renderPaymentForm(); | |
| const topUpInput = await screen.findByTestId("top-up-input"); | |
| await user.type(topUpInput, "50"); | |
| const topUpButton = screen.getByText("PAYMENT$ADD_CREDIT"); | |
| await user.click(topUpButton); | |
| expect(createCheckoutSessionSpy).toHaveBeenCalledWith(50); | |
| }); | |
| it("should disable the top-up button if the user enters an invalid amount", async () => { | |
| const user = userEvent.setup(); | |
| renderPaymentForm(); | |
| const topUpButton = screen.getByText("PAYMENT$ADD_CREDIT"); | |
| expect(topUpButton).toBeDisabled(); | |
| const topUpInput = await screen.findByTestId("top-up-input"); | |
| await user.type(topUpInput, " "); | |
| expect(topUpButton).toBeDisabled(); | |
| }); | |
| it("should disable the top-up button after submission", async () => { | |
| const user = userEvent.setup(); | |
| renderPaymentForm(); | |
| const topUpInput = await screen.findByTestId("top-up-input"); | |
| await user.type(topUpInput, "50"); | |
| const topUpButton = screen.getByText("PAYMENT$ADD_CREDIT"); | |
| await user.click(topUpButton); | |
| expect(topUpButton).toBeDisabled(); | |
| }); | |
| describe("prevent submission if", () => { | |
| test("user enters a negative amount", async () => { | |
| const user = userEvent.setup(); | |
| renderPaymentForm(); | |
| const topUpInput = await screen.findByTestId("top-up-input"); | |
| await user.type(topUpInput, "-50"); | |
| const topUpButton = screen.getByText("PAYMENT$ADD_CREDIT"); | |
| await user.click(topUpButton); | |
| expect(createCheckoutSessionSpy).not.toHaveBeenCalled(); | |
| }); | |
| test("user enters an empty string", async () => { | |
| const user = userEvent.setup(); | |
| renderPaymentForm(); | |
| const topUpInput = await screen.findByTestId("top-up-input"); | |
| await user.type(topUpInput, " "); | |
| const topUpButton = screen.getByText("PAYMENT$ADD_CREDIT"); | |
| await user.click(topUpButton); | |
| expect(createCheckoutSessionSpy).not.toHaveBeenCalled(); | |
| }); | |
| test("user enters a non-numeric value", async () => { | |
| const user = userEvent.setup(); | |
| renderPaymentForm(); | |
| // With type="number", the browser would prevent non-numeric input, | |
| // but we'll test the validation logic anyway | |
| const topUpInput = await screen.findByTestId("top-up-input"); | |
| await user.type(topUpInput, "abc"); | |
| const topUpButton = screen.getByText("PAYMENT$ADD_CREDIT"); | |
| await user.click(topUpButton); | |
| expect(createCheckoutSessionSpy).not.toHaveBeenCalled(); | |
| }); | |
| test("user enters less than the minimum amount", async () => { | |
| const user = userEvent.setup(); | |
| renderPaymentForm(); | |
| const topUpInput = await screen.findByTestId("top-up-input"); | |
| await user.type(topUpInput, "9"); // test assumes the minimum is 10 | |
| const topUpButton = screen.getByText("PAYMENT$ADD_CREDIT"); | |
| await user.click(topUpButton); | |
| expect(createCheckoutSessionSpy).not.toHaveBeenCalled(); | |
| }); | |
| test("user enters a decimal value", async () => { | |
| const user = userEvent.setup(); | |
| renderPaymentForm(); | |
| // With step="1", the browser would validate this, but we'll test our validation logic | |
| const topUpInput = await screen.findByTestId("top-up-input"); | |
| await user.type(topUpInput, "50.5"); | |
| const topUpButton = screen.getByText("PAYMENT$ADD_CREDIT"); | |
| await user.click(topUpButton); | |
| expect(createCheckoutSessionSpy).not.toHaveBeenCalled(); | |
| }); | |
| }); | |
| }); | |