diff --git a/src/app/(dashboard)/wallet/transfer/page.tsx b/src/app/(dashboard)/wallet/transfer/page.tsx index 8aa1683..fba290d 100644 --- a/src/app/(dashboard)/wallet/transfer/page.tsx +++ b/src/app/(dashboard)/wallet/transfer/page.tsx @@ -270,7 +270,7 @@ function WalletTransferContent() { )} - } + ) } export default function WalletTransferPage() { diff --git a/src/app/(dashboard)/wallet/withdraw/steps/otp-step.test.tsx b/src/app/(dashboard)/wallet/withdraw/steps/otp-step.test.tsx new file mode 100644 index 0000000..ea1adee --- /dev/null +++ b/src/app/(dashboard)/wallet/withdraw/steps/otp-step.test.tsx @@ -0,0 +1,64 @@ +import React from "react" +import { render, screen, fireEvent } from "@testing-library/react" +import { describe, it, expect, vi } from "vitest" +import { OtpStep } from "./otp-step" + +describe("OtpStep Component", () => { + it("renders OTP input and triggers onResend when Resend button is clicked", () => { + const onResend = vi.fn() + const onVerify = vi.fn() + const onOtpChange = vi.fn() + + render( + + ) + + const resendBtn = screen.getByRole("button", { name: /^resend/i }) + expect(resendBtn).not.toBeDisabled() + + fireEvent.click(resendBtn) + expect(onResend).toHaveBeenCalledTimes(1) + }) + + it("disables Resend button during cooldown and displays countdown", () => { + render( + + ) + + const resendBtn = screen.getByRole("button", { name: /resend in 45s/i }) + expect(resendBtn).toBeDisabled() + }) + + it("disables Resend button during active loading state", () => { + render( + + ) + + const resendBtn = screen.getByRole("button", { name: /^resend/i }) + expect(resendBtn).toBeDisabled() + }) +}) diff --git a/src/app/api/claim-name/route.test.ts b/src/app/api/claim-name/route.test.ts new file mode 100644 index 0000000..f3fa9c9 --- /dev/null +++ b/src/app/api/claim-name/route.test.ts @@ -0,0 +1,113 @@ +import { describe, it, expect, beforeEach } from "vitest"; +import { NextRequest } from "next/server"; +import { POST, _resetClaimNameState } from "./route"; + +describe("POST /api/claim-name", () => { + beforeEach(() => { + _resetClaimNameState(); + }); + + it("claims a valid name for an authenticated user", async () => { + const req = new NextRequest("http://localhost/api/claim-name", { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: "Bearer user-100", + }, + body: JSON.stringify({ name: " Satoshi_N " }), + }); + + const res = await POST(req); + expect(res.status).toBe(201); + + const data = await res.json(); + expect(data.success).toBe(true); + expect(data.name).toBe("satoshi_n"); + expect(data.claimedBy).toBe("user-100"); + }); + + it("rejects unauthenticated requests", async () => { + const req = new NextRequest("http://localhost/api/claim-name", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ name: "valid_name" }), + }); + + const res = await POST(req); + expect(res.status).toBe(401); + + const data = await res.json(); + expect(data.error).toMatch(/Authentication required/i); + }); + + it("rejects invalid or malformed names", async () => { + // Too short (< 3 chars) + const reqShort = new NextRequest("http://localhost/api/claim-name", { + method: "POST", + headers: { Authorization: "Bearer user-100" }, + body: JSON.stringify({ name: "ab" }), + }); + const resShort = await POST(reqShort); + expect(resShort.status).toBe(400); + + // Illegal characters + const reqIllegal = new NextRequest("http://localhost/api/claim-name", { + method: "POST", + headers: { Authorization: "Bearer user-100" }, + body: JSON.stringify({ name: "satoshi