Skip to content
Merged
174 changes: 172 additions & 2 deletions web/src/components/network/ConnectionForm.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,16 @@

import React from "react";
import { screen, waitFor } from "@testing-library/react";
import { installerRender } from "~/test-utils";
import { installerRender, mockParams } from "~/test-utils";
import {
Connection,
ConnectionMethod,
ConnectionState,
ConnectionStatus,
ConnectionType,
DeviceState,
} from "~/types/network";
import ConnectionForm from "~/components/network/ConnectionForm";
import { ConnectionMethod, ConnectionType, DeviceState } from "~/types/network";

const mockDevice1 = {
name: "enp1s0",
Expand All @@ -41,18 +48,36 @@ const mockDevice2 = {
};

const mockMutateAsync = jest.fn().mockResolvedValue({});
const mockUseConfig = jest.fn().mockReturnValue({ connections: [] });
const mockUseSystem = jest.fn().mockReturnValue({ connections: [] });

jest.mock("~/hooks/model/config/network", () => ({
useConnectionMutation: () => ({ mutateAsync: mockMutateAsync }),
useConfig: () => mockUseConfig(),
}));

jest.mock("~/hooks/model/system/network", () => ({
useDevices: () => [mockDevice1, mockDevice2],
useSystem: () => mockUseSystem(),
}));

/** Builds a Connection instance from minimal API data, with sensible defaults. */
const makeConnection = (id: string, overrides = {}) =>
Connection.fromApi({
id,
status: ConnectionStatus.UP,
state: ConnectionState.activated,
persistent: true,
addresses: [],
nameservers: [],
dnsSearchList: [],
...overrides,
});

describe("ConnectionForm", () => {
beforeEach(() => {
jest.clearAllMocks();
mockParams({});
});

it("renders common connection fields and options", () => {
Expand Down Expand Up @@ -236,6 +261,151 @@ describe("ConnectionForm", () => {
});
});

describe("when the connection to edit is not found", () => {
beforeEach(() => {
mockParams({ id: "eth0" });
});

it("renders an empty state with a link back to network", () => {
installerRender(<ConnectionForm />);
screen.getByText("Connection not found");
screen.getByRole("link", { name: "Go to network page" });
});
});

describe("when editing an existing connection", () => {
beforeEach(() => {
mockParams({ id: "eth0" });
mockUseSystem.mockReturnValue({ connections: [makeConnection("eth0")] });
});

it("does not show the name field since it cannot be changed", () => {
installerRender(<ConnectionForm />);
expect(screen.queryByLabelText("Name")).not.toBeInTheDocument();
});

it("pre-selects Manual IPv4 when the connection uses manual IPv4 addressing", () => {
mockUseSystem.mockReturnValue({
connections: [makeConnection("eth0", { method4: "manual", addresses: ["192.168.1.1/24"] })],
});
installerRender(<ConnectionForm />);
expect(screen.getByText("IPv4 Addresses")).toBeInTheDocument();
});

it("pre-selects Manual IPv6 when the connection uses manual IPv6 addressing", () => {
mockUseSystem.mockReturnValue({
connections: [makeConnection("eth0", { method6: "manual", addresses: ["2001:db8::1/64"] })],
});
installerRender(<ConnectionForm />);
expect(screen.getByText("IPv6 Addresses")).toBeInTheDocument();
});

it("pre-checks custom DNS when the connection has nameservers", () => {
mockUseSystem.mockReturnValue({
connections: [makeConnection("eth0", { nameservers: ["8.8.8.8"] })],
});
installerRender(<ConnectionForm />);
expect(screen.getByRole("checkbox", { name: "Use custom DNS" })).toBeChecked();
});

it("pre-checks custom DNS search domains when the connection has search domains", () => {
mockUseSystem.mockReturnValue({
connections: [makeConnection("eth0", { dnsSearchList: ["example.com"] })],
});
installerRender(<ConnectionForm />);
expect(screen.getByRole("checkbox", { name: "Use custom DNS search domains" })).toBeChecked();
});

it("submits the updated connection when accepting the form", async () => {
mockUseSystem.mockReturnValue({
connections: [makeConnection("eth0", { nameservers: ["8.8.8.8"] })],
});
const { user } = installerRender(<ConnectionForm />);
await user.click(screen.getByRole("button", { name: "Accept" }));
await waitFor(() =>
expect(mockMutateAsync).toHaveBeenCalledWith(
expect.objectContaining({ id: "eth0", nameservers: ["8.8.8.8"] }),
),
);
});
});

describe("when merging config and system connections for editing", () => {
beforeEach(() => {
mockParams({ id: "eth0" });
});

it("shows Automatic IPv4 when config has no method, despite system reporting auto", () => {
mockUseConfig.mockReturnValue({ connections: [makeConnection("eth0")] });
mockUseSystem.mockReturnValue({
connections: [makeConnection("eth0", { method4: "auto" })],
});
installerRender(<ConnectionForm />);
expect(screen.queryByText("IPv4 Addresses")).not.toBeInTheDocument();
});

it("shows Manual IPv4 when config sets method4 to manual, despite system reporting auto", () => {
mockUseConfig.mockReturnValue({
connections: [makeConnection("eth0", { method4: "manual", addresses: ["192.168.1.1/24"] })],
});
mockUseSystem.mockReturnValue({
connections: [makeConnection("eth0", { method4: "auto" })],
});
installerRender(<ConnectionForm />);
expect(screen.getByText("IPv4 Addresses")).toBeInTheDocument();
});

it("shows Advanced IPv4 when config sets method4 to auto, despite system reporting manual", () => {
mockUseConfig.mockReturnValue({
connections: [makeConnection("eth0", { method4: "auto" })],
});
mockUseSystem.mockReturnValue({
connections: [makeConnection("eth0", { method4: "manual", addresses: ["192.168.1.1/24"] })],
});
installerRender(<ConnectionForm />);
expect(
screen.getByLabelText("IPv4 Gateway (optional, ignored if no addresses provided)"),
).toBeInTheDocument();
});

it.todo("shows Advanced IPv4 when config has no method but system already has IPv4 addresses");

it("shows Automatic IPv6 when config has no method, despite system reporting auto", () => {
mockUseConfig.mockReturnValue({ connections: [makeConnection("eth0")] });
mockUseSystem.mockReturnValue({
connections: [makeConnection("eth0", { method6: "auto" })],
});
installerRender(<ConnectionForm />);
expect(screen.queryByText("IPv6 Addresses")).not.toBeInTheDocument();
});

it("shows Manual IPv6 when config sets method6 to manual, despite system reporting auto", () => {
mockUseConfig.mockReturnValue({
connections: [makeConnection("eth0", { method6: "manual", addresses: ["2001:db8::1/64"] })],
});
mockUseSystem.mockReturnValue({
connections: [makeConnection("eth0", { method6: "auto" })],
});
installerRender(<ConnectionForm />);
expect(screen.getByText("IPv6 Addresses")).toBeInTheDocument();
});

it("shows Advanced IPv6 when config sets method6 to auto, despite system reporting manual", () => {
mockUseConfig.mockReturnValue({
connections: [makeConnection("eth0", { method6: "auto" })],
});
mockUseSystem.mockReturnValue({
connections: [makeConnection("eth0", { method6: "manual", addresses: ["2001:db8::1/64"] })],
});
installerRender(<ConnectionForm />);
expect(
screen.getByLabelText("IPv6 Gateway (optional, ignored if no addresses provided)"),
).toBeInTheDocument();
});

it.todo("shows Advanced IPv6 when config has no method but system already has IPv6 addresses");
});

describe("DNS search domains", () => {
it("does not show the DNS search domains field by default", () => {
installerRender(<ConnectionForm />);
Expand Down
Loading