From 1e9ebe14a8d200e3906aa6d2fe5d6bfd5bf4a7dc Mon Sep 17 00:00:00 2001 From: Piyush-jangid-01 Date: Thu, 22 Oct 2026 15:11:53 +0530 Subject: [PATCH] fix: preserve input when swap fails --- .../base64/components/TextTab.test.tsx | 31 +++++++++++- app/(tools)/base64/components/TextTab.tsx | 4 +- .../url-encoder/UrlEncoderClient.test.tsx | 49 +++++++++++++++++-- app/(tools)/url-encoder/UrlEncoderClient.tsx | 4 +- 4 files changed, 82 insertions(+), 6 deletions(-) diff --git a/app/(tools)/base64/components/TextTab.test.tsx b/app/(tools)/base64/components/TextTab.test.tsx index a6b6fa3..1c2460a 100644 --- a/app/(tools)/base64/components/TextTab.test.tsx +++ b/app/(tools)/base64/components/TextTab.test.tsx @@ -1,4 +1,4 @@ -import { cleanup, fireEvent, render, screen } from "@testing-library/react"; +import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react"; import { afterEach, describe, expect, it } from "vitest"; import { ToastProvider } from "@/components/shared/Toast"; import { TextTab } from "./TextTab"; @@ -71,4 +71,33 @@ describe("TextTab switches", () => { // The styling hook stays in step with the state. expect(urlSafe).toHaveAttribute("data-active", "true"); }); + + it("disables Swap and preserves invalid input when the shortcut runs", async () => { + renderTab(); + fireEvent.click(screen.getByRole("button", { name: "Base64 → Text" })); + const input = screen.getByRole("textbox", { name: "Base64 to decode" }); + fireEvent.change(input, { target: { value: "Hello world, how are you?" } }); + + expect(await screen.findByText(/Invalid base64/i)).toBeInTheDocument(); + expect(screen.getByRole("button", { name: "Swap direction" })).toBeDisabled(); + + fireEvent.keyDown(input, { key: "s", ctrlKey: true, shiftKey: true }); + + expect(input).toHaveValue("Hello world, how are you?"); + expect(screen.getByRole("button", { name: "Base64 → Text" })).toHaveAttribute("aria-pressed", "true"); + }); + + it("swaps a valid conversion result", async () => { + renderTab(); + fireEvent.change(screen.getByRole("textbox", { name: "Text to encode" }), { + target: { value: "Hello world" }, + }); + + expect(await screen.findByText("SGVsbG8gd29ybGQ=")).toBeInTheDocument(); + fireEvent.click(screen.getByRole("button", { name: "Swap direction" })); + + expect(screen.getByRole("button", { name: "Base64 → Text" })).toHaveAttribute("aria-pressed", "true"); + expect(screen.getByRole("textbox", { name: "Base64 to decode" })).toHaveValue("SGVsbG8gd29ybGQ="); + await waitFor(() => expect(screen.getByText("Hello world")).toBeInTheDocument()); + }); }); diff --git a/app/(tools)/base64/components/TextTab.tsx b/app/(tools)/base64/components/TextTab.tsx index b3cd7dd..39df1d0 100644 --- a/app/(tools)/base64/components/TextTab.tsx +++ b/app/(tools)/base64/components/TextTab.tsx @@ -110,6 +110,7 @@ export function TextTab() { } function swap() { + if (error || !output) return; setDirection((d) => (d === "encode" ? "decode" : "encode")); setInput(output); setOutput(input); @@ -138,7 +139,7 @@ export function TextTab() { window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [direction, input, opts, urlSafe, padding]); + }, [direction, input, output, error, opts, urlSafe, padding, gzip, chunk]); const inputBytes = useMemo(() => new Blob([input]).size, [input]); const outputBytes = useMemo(() => new Blob([output]).size, [output]); @@ -277,6 +278,7 @@ export function TextTab() {