diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 4bdb6051f6..ce5b26ce6a 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -150,7 +150,8 @@ "vite": "^5.4.8", "vite-plugin-checker": "^0.8.0", "vite-plugin-node-polyfills": "^0.17.0", - "zod": "^3.23.8" + "zod": "^3.23.8", + "@hookform/resolvers": "^3.9.0" }, "packageManager": "pnpm@9.9.0", "dependencies": { diff --git a/apps/desktop/src/views/settings/network/UpsertNetworkModal.test.tsx b/apps/desktop/src/views/settings/network/UpsertNetworkModal.test.tsx index fd1524f60c..e6e3714f7c 100644 --- a/apps/desktop/src/views/settings/network/UpsertNetworkModal.test.tsx +++ b/apps/desktop/src/views/settings/network/UpsertNetworkModal.test.tsx @@ -12,6 +12,14 @@ beforeEach(() => { }); describe("", () => { + const updatedNetwork = { + ...customNetwork, + rpcUrl: "https://rpc.com", + tzktApiUrl: "https://tzkt.com", + tzktExplorerUrl: "https://explorer.com", + buyTezUrl: "", + }; + describe("edit mode", () => { beforeEach(() => { store.dispatch(networksActions.upsertNetwork(customNetwork)); @@ -27,14 +35,6 @@ describe("", () => { const user = userEvent.setup(); render(, { store }); - const updatedNetwork = { - ...customNetwork, - rpcUrl: "https://rpc", - tzktApiUrl: "https://tzkt", - tzktExplorerUrl: "https://explorer", - buyTezUrl: "", - }; - await act(() => user.clear(screen.getByLabelText("RPC URL"))); await act(() => user.clear(screen.getByLabelText("Tzkt API URL"))); await act(() => user.clear(screen.getByLabelText("Tzkt Explorer URL"))); @@ -55,14 +55,6 @@ describe("", () => { const user = userEvent.setup(); render(, { store }); - const updatedNetwork = { - ...customNetwork, - rpcUrl: "https://rpc", - tzktApiUrl: "https://tzkt", - tzktExplorerUrl: "https://explorer", - buyTezUrl: "", - }; - await act(() => user.clear(screen.getByLabelText("RPC URL"))); await act(() => user.clear(screen.getByLabelText("Tzkt API URL"))); await act(() => user.clear(screen.getByLabelText("Tzkt Explorer URL"))); @@ -85,6 +77,48 @@ describe("", () => { }); }); + describe("URL fields validation", () => { + const urlFields = [ + { label: "RPC URL", required: true }, + { label: "Tzkt API URL", required: true }, + { label: "Tzkt Explorer URL", required: true }, + { label: "Buy Tez URL", required: false }, + ]; + + it.each(urlFields)("validates $label field", async ({ label, required }) => { + const user = userEvent.setup(); + render(, { store }); + + await user.type(screen.getByLabelText(label), "invalid-url"); + await user.tab(); + + await waitFor(() => { + expect(screen.getByText(`Enter a valid ${label}`)).toBeVisible(); + }); + + await user.clear(screen.getByLabelText(label)); + await user.tab(); + + if (required) { + await waitFor(() => { + expect(screen.getByText(`${label} is required`)).toBeVisible(); + }); + } else { + await waitFor(() => { + expect(screen.queryByText(`${label} is required`)).not.toBeInTheDocument(); + }); + } + + await user.type(screen.getByLabelText(label), "https://valid-url.com"); + await user.tab(); + + await waitFor(() => { + expect(screen.queryByText(`Enter a valid ${label}`)).not.toBeInTheDocument(); + }); + expect(screen.queryByText(`${label} is required`)).not.toBeInTheDocument(); + }); + }); + describe("create mode", () => { describe("name field", () => { it("validates uniqueness", async () => { diff --git a/apps/desktop/src/views/settings/network/UpsertNetworkModal.tsx b/apps/desktop/src/views/settings/network/UpsertNetworkModal.tsx index 419df9af0f..6023157048 100644 --- a/apps/desktop/src/views/settings/network/UpsertNetworkModal.tsx +++ b/apps/desktop/src/views/settings/network/UpsertNetworkModal.tsx @@ -9,7 +9,8 @@ import { ModalFooter, ModalHeader, } from "@chakra-ui/react"; -import { useDynamicModalContext } from "@umami/components"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { getNetworkValidationScheme, useDynamicModalContext } from "@umami/components"; import { networksActions, useAvailableNetworks } from "@umami/state"; import { type Network } from "@umami/tezos"; import { useForm } from "react-hook-form"; @@ -30,7 +31,11 @@ export const UpsertNetworkModal = ({ network }: { network?: Network }) => { formState: { errors, isValid }, register, handleSubmit, - } = useForm({ mode: "onBlur", defaultValues: network }); + } = useForm({ + mode: "onBlur", + defaultValues: network, + resolver: zodResolver(getNetworkValidationScheme(availableNetworks, network)), + }); const onSubmit = (network: Network) => { dispatch(networksActions.upsertNetwork(network)); @@ -51,12 +56,7 @@ export const UpsertNetworkModal = ({ network }: { network?: Network }) => { { - if (availableNetworks.find(n => n.name === name)) { - return "Network with this name already exists"; - } - }, + setValueAs: removeTrailingSlashes, })} /> {errors.name && {errors.name.message}} @@ -67,7 +67,6 @@ export const UpsertNetworkModal = ({ network }: { network?: Network }) => { @@ -78,7 +77,6 @@ export const UpsertNetworkModal = ({ network }: { network?: Network }) => { @@ -89,7 +87,6 @@ export const UpsertNetworkModal = ({ network }: { network?: Network }) => { @@ -98,9 +95,10 @@ export const UpsertNetworkModal = ({ network }: { network?: Network }) => { )} - + Buy Tez URL + {errors.buyTezUrl && {errors.buyTezUrl.message}}