From c229a2741d4d9d353190fa16def71f7743fbdf39 Mon Sep 17 00:00:00 2001 From: Bakar Date: Tue, 21 Jul 2026 18:55:20 +0500 Subject: [PATCH] Trim whitespace from connection URLs --- .../CreateConnection.test.tsx | 72 +++++++++++++++++++ .../CreateConnection/CreateConnection.tsx | 33 +++++++-- 2 files changed, 99 insertions(+), 6 deletions(-) create mode 100644 packages/graph-explorer/src/modules/CreateConnection/CreateConnection.test.tsx diff --git a/packages/graph-explorer/src/modules/CreateConnection/CreateConnection.test.tsx b/packages/graph-explorer/src/modules/CreateConnection/CreateConnection.test.tsx new file mode 100644 index 000000000..ded10545e --- /dev/null +++ b/packages/graph-explorer/src/modules/CreateConnection/CreateConnection.test.tsx @@ -0,0 +1,72 @@ +// @vitest-environment happy-dom +import { render, screen, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { describe, expect, test, vi } from "vitest"; + +import { TooltipProvider } from "@/components"; +import { configurationAtom, getAppStore } from "@/core"; +import { createQueryClient } from "@/core/queryClient"; +import { TestProvider } from "@/utils/testing"; + +import CreateConnection from "./CreateConnection"; + +function renderCreateConnection() { + const store = getAppStore(); + store.set(configurationAtom, new Map()); + + render( + + + + + , + ); + + return store; +} + +describe("CreateConnection", () => { + test("removes newlines and surrounding whitespace from URL fields", async () => { + const user = userEvent.setup(); + const store = renderCreateConnection(); + + await user.type( + screen.getByRole("textbox", { name: "Public or Proxy Endpoint" }), + " https://proxy.example.com/{Enter}path ", + ); + await user.click( + screen.getByRole("checkbox", { name: "Using Proxy-Server" }), + ); + await user.type( + screen.getByRole("textbox", { name: "Graph Connection URL" }), + " https://database.example.com/{Enter}graph ", + ); + await user.click(screen.getByRole("button", { name: "Add Connection" })); + + await waitFor(() => { + expect(store.get(configurationAtom)).toHaveLength(1); + }); + + const [savedConnection] = store.get(configurationAtom).values(); + expect(savedConnection).toMatchObject({ + connection: { + url: "https://proxy.example.com/path", + graphDbUrl: "https://database.example.com/graph", + }, + }); + }); + + test("rejects a URL that is empty after normalization", async () => { + const user = userEvent.setup(); + const store = renderCreateConnection(); + + await user.type( + screen.getByRole("textbox", { name: "Public or Proxy Endpoint" }), + " {Enter} ", + ); + await user.click(screen.getByRole("button", { name: "Add Connection" })); + + expect(store.get(configurationAtom)).toHaveLength(0); + expect(screen.getByText("URL is required")).toBeInTheDocument(); + }); +}); diff --git a/packages/graph-explorer/src/modules/CreateConnection/CreateConnection.tsx b/packages/graph-explorer/src/modules/CreateConnection/CreateConnection.tsx index ac6bb5a1f..1a2ee1cbc 100644 --- a/packages/graph-explorer/src/modules/CreateConnection/CreateConnection.tsx +++ b/packages/graph-explorer/src/modules/CreateConnection/CreateConnection.tsx @@ -50,6 +50,10 @@ type ConnectionForm = { nodeExpansionLimit?: number; }; +function normalizeUrlField(value: string | undefined) { + return value?.replace(/[\r\n]/g, "").trim(); +} + const CONNECTIONS_OP: { label: string; value: QueryEngine; @@ -234,22 +238,35 @@ const CreateConnection = ({ const reset = useResetState(); const onSubmit = () => { - if (!form.name || !form.url || !form.queryEngine) { + const normalizedForm: ConnectionForm = { + ...form, + url: normalizeUrlField(form.url), + graphDbUrl: normalizeUrlField(form.graphDbUrl), + }; + + if ( + !normalizedForm.name || + !normalizedForm.url || + !normalizedForm.queryEngine + ) { setError(true); return; } - if (form.proxyConnection && !form.graphDbUrl) { + if (normalizedForm.proxyConnection && !normalizedForm.graphDbUrl) { setError(true); return; } - if (form.awsAuthEnabled && (!form.awsRegion || !form.serviceType)) { + if ( + normalizedForm.awsAuthEnabled && + (!normalizedForm.awsRegion || !normalizedForm.serviceType) + ) { setError(true); return; } - onSave(form as Required); + onSave(normalizedForm as Required); reset(); onClose(); }; @@ -292,7 +309,9 @@ const CreateConnection = ({ onChange={onFormChange("url")} errorMessage="URL is required" placeholder="https://example.com" - validationState={hasError && !form.url ? "invalid" : "valid"} + validationState={ + hasError && !normalizeUrlField(form.url) ? "invalid" : "valid" + } /> @@ -317,7 +336,9 @@ const CreateConnection = ({ errorMessage="URL is required" placeholder="https://neptune-cluster.amazonaws.com" validationState={ - hasError && !form.graphDbUrl ? "invalid" : "valid" + hasError && !normalizeUrlField(form.graphDbUrl) + ? "invalid" + : "valid" } />