PasswordCheck Moc
describe("EncryptionSection", () => {
describe("if encryption is enabled", () => {
beforeEach(() => {
- mockUseEncryption.mockReturnValue({
+ mockUseConfigModel.mockReturnValue({
encryption: {
method: "luks2",
password: "12345",
@@ -52,7 +51,7 @@ describe("EncryptionSection", () => {
describe("and uses TPM", () => {
beforeEach(() => {
- mockUseEncryption.mockReturnValue({
+ mockUseConfigModel.mockReturnValue({
encryption: {
method: "tpmFde",
password: "12345",
@@ -69,7 +68,7 @@ describe("EncryptionSection", () => {
describe("if encryption is disabled", () => {
beforeEach(() => {
- mockUseEncryption.mockReturnValue({});
+ mockUseConfigModel.mockReturnValue({});
});
it("renders encryption as disabled", () => {
diff --git a/web/src/components/storage/EncryptionSettingsPage.test.tsx b/web/src/components/storage/EncryptionSettingsPage.test.tsx
index 9722a44fd8..5f41a3b942 100644
--- a/web/src/components/storage/EncryptionSettingsPage.test.tsx
+++ b/web/src/components/storage/EncryptionSettingsPage.test.tsx
@@ -24,58 +24,62 @@ import React from "react";
import { screen } from "@testing-library/react";
import { installerRender } from "~/test-utils";
import EncryptionSettingsPage from "./EncryptionSettingsPage";
-import { EncryptionHook } from "~/queries/storage/config-model";
+import type { ConfigModel } from "~/model/storage/config-model";
jest.mock("~/components/users/PasswordCheck", () => () =>
PasswordCheck Mock
);
-const mockLuks2Encryption: EncryptionHook = {
+const mockLuks2Config: ConfigModel.Config = {
encryption: {
method: "luks2",
password: "12345",
},
- enable: jest.fn(),
- disable: jest.fn(),
};
-const mockTpmEncryption: EncryptionHook = {
+const mockTpmConfig: ConfigModel.Config = {
encryption: {
method: "tpmFde",
password: "12345",
},
- enable: jest.fn(),
- disable: jest.fn(),
};
-const mockNoEncryption: EncryptionHook = {
- encryption: undefined,
- enable: jest.fn(),
- disable: jest.fn(),
-};
+const mockNoEncryptionConfig: ConfigModel.Config = {};
jest.mock("~/components/product/ProductRegistrationAlert", () => () => (
registration alert
));
+jest.mock("~/hooks/model/system", () => ({
+ useSystem: () => ({
+ l10n: {
+ keymap: "us",
+ timezone: "Europe/Berlin",
+ locale: "en_US",
+ },
+ }),
+}));
+
const mockUseEncryptionMethods = jest.fn();
-jest.mock("~/queries/storage", () => ({
- ...jest.requireActual("~/queries/storage"),
+jest.mock("~/hooks/model/system/storage", () => ({
useEncryptionMethods: () => mockUseEncryptionMethods(),
}));
-const mockUseEncryption = jest.fn();
-jest.mock("~/queries/storage/config-model", () => ({
- ...jest.requireActual("~/queries/storage/config-model"),
- useEncryption: () => mockUseEncryption(),
+const mockUseConfigModel = jest.fn();
+const mockSetEncryption = jest.fn();
+jest.mock("~/hooks/model/storage/config-model", () => ({
+ useConfigModel: () => mockUseConfigModel(),
+ useSetEncryption: () => mockSetEncryption,
}));
describe("EncryptionSettingsPage", () => {
beforeEach(() => {
mockUseEncryptionMethods.mockReturnValue(["luks2", "tpmFde"]);
+ mockSetEncryption.mockClear();
+ mockUseConfigModel.mockClear();
});
describe("when encryption is not enabled", () => {
beforeEach(() => {
- mockUseEncryption.mockReturnValue(mockNoEncryption);
+ mockUseConfigModel.mockReturnValue(mockNoEncryptionConfig);
});
it("allows enabling the encryption", async () => {
@@ -89,13 +93,13 @@ describe("EncryptionSettingsPage", () => {
await user.type(passwordConfirmationInput, "12345");
const acceptButton = screen.getByRole("button", { name: "Accept" });
await user.click(acceptButton);
- expect(mockNoEncryption.enable).toHaveBeenCalledWith("luks2", "12345");
+ expect(mockSetEncryption).toHaveBeenCalledWith({ method: "luks2", password: "12345" });
});
});
describe("when encryption is enabled", () => {
beforeEach(() => {
- mockUseEncryption.mockReturnValue(mockLuks2Encryption);
+ mockUseConfigModel.mockReturnValue(mockLuks2Config);
});
it("allows disabling the encryption", async () => {
@@ -106,13 +110,13 @@ describe("EncryptionSettingsPage", () => {
const acceptButton = screen.getByRole("button", { name: "Accept" });
await user.click(acceptButton);
- expect(mockLuks2Encryption.disable).toHaveBeenCalled();
+ expect(mockSetEncryption).toHaveBeenCalledWith(null);
});
});
describe("when using TPM", () => {
beforeEach(() => {
- mockUseEncryption.mockReturnValue(mockTpmEncryption);
+ mockUseConfigModel.mockReturnValue(mockTpmConfig);
});
it("allows disabling TPM", async () => {
@@ -123,7 +127,7 @@ describe("EncryptionSettingsPage", () => {
await user.click(tpmCheckbox);
expect(tpmCheckbox).not.toBeChecked();
await user.click(acceptButton);
- expect(mockTpmEncryption.enable).toHaveBeenCalledWith("luks2", "12345");
+ expect(mockSetEncryption).toHaveBeenCalledWith({ method: "luks2", password: "12345" });
});
});
diff --git a/web/src/components/storage/FilesystemMenu.test.tsx b/web/src/components/storage/FilesystemMenu.test.tsx
new file mode 100644
index 0000000000..908e2cfdaa
--- /dev/null
+++ b/web/src/components/storage/FilesystemMenu.test.tsx
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) [2025] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, contact SUSE LLC.
+ *
+ * To contact SUSE LLC about this file by physical or electronic mail, you may
+ * find current contact information at www.suse.com.
+ */
+
+import React from "react";
+import { screen } from "@testing-library/react";
+import { generatePath } from "react-router";
+import { installerRender, mockNavigateFn } from "~/test-utils";
+import FilesystemMenu from "./FilesystemMenu";
+import { STORAGE as PATHS } from "~/routes/paths";
+import type { ConfigModel } from "~/model/storage/config-model";
+
+jest.mock("react-router", () => ({
+ ...jest.requireActual("react-router"),
+ useNavigate: () => mockNavigateFn,
+}));
+
+const mockPartitionable = jest.fn();
+
+jest.mock("~/hooks/model/storage/config-model", () => ({
+ ...jest.requireActual("~/hooks/model/storage/config-model"),
+ usePartitionable: () => mockPartitionable(),
+}));
+
+describe("FilesystemMenu", () => {
+ it("should render the toggle button and open the menu on click", async () => {
+ const deviceModel: ConfigModel.Drive = {
+ name: "/dev/vda",
+ mountPath: "/home",
+ filesystem: { type: "btrfs", default: false, snapshots: true },
+ };
+ mockPartitionable.mockReturnValue(deviceModel);
+
+ const { user } = installerRender(
);
+
+ // Test that the toggle button renders with the correct description
+ const toggleButton = screen.getByRole("button", {
+ name: 'The device will be formatted as Btrfs with snapshots and mounted at "/home"',
+ });
+ expect(toggleButton).toBeInTheDocument();
+
+ // Open the menu
+ await user.click(toggleButton);
+ expect(screen.getByRole("menu")).toBeInTheDocument();
+ expect(screen.getByText("Edit")).toBeInTheDocument();
+ });
+
+ it("should navigate to edit filesystem path when 'Edit' is clicked", async () => {
+ const deviceModel: ConfigModel.Drive = {
+ name: "/dev/vda",
+ mountPath: "/home",
+ filesystem: { type: "btrfs", default: false, snapshots: true },
+ };
+ mockPartitionable.mockReturnValue(deviceModel);
+
+ const { user } = installerRender(
);
+ const toggleButton = screen.getByRole("button", {
+ name: 'The device will be formatted as Btrfs with snapshots and mounted at "/home"',
+ });
+ await user.click(toggleButton);
+
+ const editItem = screen.getByText("Edit");
+ await user.click(editItem);
+
+ const expectedPath = generatePath(PATHS.formatDevice, { collection: "drives", index: 0 });
+ expect(mockNavigateFn).toHaveBeenCalledWith(expectedPath);
+ });
+
+ describe("deviceDescription function logic", () => {
+ it("should return 'The device will be mounted' when no mount path and reuse = true", () => {
+ const deviceModel: ConfigModel.Drive = {
+ name: "/dev/vda",
+ filesystem: { reuse: true, default: false },
+ };
+ mockPartitionable.mockReturnValue(deviceModel);
+
+ installerRender(
);
+ expect(
+ screen.getByRole("button", { name: "The device will be mounted" }),
+ ).toBeInTheDocument();
+ });
+
+ it("should return 'The device will be formatted' when no mount path and reuse = false", () => {
+ const deviceModel: ConfigModel.Drive = {
+ name: "/dev/vda",
+ filesystem: { reuse: false, default: false },
+ };
+ mockPartitionable.mockReturnValue(deviceModel);
+
+ installerRender(
);
+ expect(
+ screen.getByRole("button", { name: "The device will be formatted" }),
+ ).toBeInTheDocument();
+ });
+
+ it("should return 'The current file system will be mounted at \"/home\"' when mount path and reuse = true", () => {
+ const deviceModel: ConfigModel.Drive = {
+ name: "/dev/vda",
+ mountPath: "/home",
+ filesystem: { reuse: true, default: false },
+ };
+ mockPartitionable.mockReturnValue(deviceModel);
+
+ installerRender(
);
+ expect(
+ screen.getByRole("button", { name: 'The current file system will be mounted at "/home"' }),
+ ).toBeInTheDocument();
+ });
+
+ it("should return 'The device will be formatted as XFS and mounted at \"/var\"' when mount path and reuse = false", () => {
+ const deviceModel: ConfigModel.Drive = {
+ name: "/dev/vda",
+ mountPath: "/var",
+ filesystem: { reuse: false, default: false, type: "xfs" },
+ };
+ mockPartitionable.mockReturnValue(deviceModel);
+
+ installerRender(
);
+ expect(
+ screen.getByRole("button", {
+ name: 'The device will be formatted as XFS and mounted at "/var"',
+ }),
+ ).toBeInTheDocument();
+ });
+ });
+});
diff --git a/web/src/components/storage/FormattableDevicePage.test.tsx b/web/src/components/storage/FormattableDevicePage.test.tsx
index a3177b70ef..c0fa6f24d8 100644
--- a/web/src/components/storage/FormattableDevicePage.test.tsx
+++ b/web/src/components/storage/FormattableDevicePage.test.tsx
@@ -24,41 +24,31 @@ import React from "react";
import { screen, within } from "@testing-library/react";
import { installerRender, mockParams } from "~/test-utils";
import FormattableDevicePage from "~/components/storage/FormattableDevicePage";
-import { StorageDevice, model } from "~/storage";
-import { Volume } from "~/api/storage/types";
+import type { Storage } from "~/model/system";
+import type { ConfigModel } from "~/model/storage/config-model";
import { gib } from "./utils";
-const sda: StorageDevice = {
+const sda: Storage.Device = {
sid: 59,
- isDrive: true,
- type: "disk",
+ class: "drive",
name: "/dev/sda",
- size: gib(10),
description: "",
+ block: {
+ start: 1,
+ size: gib(10),
+ shrinking: { supported: false },
+ },
};
-const sdaModel: model.Drive = {
+const sdaModel: ConfigModel.Drive = {
name: "/dev/sda",
spacePolicy: "keep",
partitions: [],
- list: "drives",
- listIndex: 0,
- isExplicitBoot: false,
- isUsed: true,
- isAddingPartitions: true,
- isReusingPartitions: true,
- isTargetDevice: false,
- isBoot: true,
- getMountPaths: jest.fn(),
- getVolumeGroups: jest.fn(),
- getPartition: jest.fn(),
- getConfiguredExistingPartitions: jest.fn(),
};
-const homeVolume: Volume = {
+const homeVolume: Storage.Volume = {
mountPath: "/home",
mountOptions: [],
- target: "default",
fsType: "btrfs",
minSize: gib(1),
maxSize: gib(5),
@@ -76,40 +66,38 @@ const homeVolume: Volume = {
},
};
-jest.mock("~/queries/issues", () => ({
- ...jest.requireActual("~/queries/issues"),
- useIssues: () => [],
-}));
-
-jest.mock("~/queries/storage", () => ({
- ...jest.requireActual("~/queries/storage"),
- useDevices: () => [sda],
-}));
-
-const mockModel = jest.fn();
-jest.mock("~/hooks/storage/model", () => ({
- ...jest.requireActual("~/hooks/storage/model"),
- useModel: () => mockModel(),
+const mockUseDevice = jest.fn();
+const mockUsePartitionable = jest.fn();
+const mockUseConfigModel = jest.fn();
+const mockUseMissingMountPaths = jest.fn();
+const mockUseVolumeTemplate = jest.fn();
+const mockSetFilesystem = jest.fn();
+
+jest.mock("~/hooks/model/system/storage", () => ({
+ ...jest.requireActual("~/hooks/model/system/storage"),
+ useDevice: (name: string) => mockUseDevice(name),
+ useVolumeTemplate: (mountPath: string) => mockUseVolumeTemplate(mountPath),
}));
-jest.mock("~/hooks/storage/product", () => ({
- ...jest.requireActual("~/hooks/storage/product"),
- useMissingMountPaths: () => ["/home", "swap"],
- useVolume: () => homeVolume,
+jest.mock("~/hooks/model/storage/config-model", () => ({
+ ...jest.requireActual("~/hooks/model/storage/config-model"),
+ usePartitionable: (collection: string, index: number) => mockUsePartitionable(collection, index),
+ useConfigModel: () => mockUseConfigModel(),
+ useMissingMountPaths: () => mockUseMissingMountPaths(),
+ useSetFilesystem: () => mockSetFilesystem,
}));
-const mockAddFilesystem = jest.fn();
-jest.mock("~/hooks/storage/filesystem", () => ({
- ...jest.requireActual("~/hooks/storage/filesystem"),
- useAddFilesystem: () => mockAddFilesystem,
-}));
+jest.mock("~/components/product/ProductRegistrationAlert", () => () => (
+
registration alert
+));
beforeEach(() => {
- mockParams({ list: "drives", listIndex: "0" });
- mockModel.mockReturnValue({
- drives: [sdaModel],
- getMountPaths: () => [],
- });
+ mockParams({ collection: "drives", index: "0" });
+ mockUsePartitionable.mockReturnValue(sdaModel);
+ mockUseDevice.mockReturnValue(sda);
+ mockUseConfigModel.mockReturnValue({ drives: [sdaModel] });
+ mockUseMissingMountPaths.mockReturnValue(["/home", "swap"]);
+ mockUseVolumeTemplate.mockReturnValue(homeVolume);
});
describe("FormattableDevicePage", () => {
@@ -161,7 +149,7 @@ describe("FormattableDevicePage", () => {
});
describe("if the device has already a filesystem config", () => {
- const formattedSdaModel: model.Drive = {
+ const formattedSdaModel: ConfigModel.Drive = {
...sdaModel,
mountPath: "/home",
filesystem: {
@@ -171,11 +159,15 @@ describe("FormattableDevicePage", () => {
},
};
+ const formattedSda: Storage.Device = {
+ ...sda,
+ filesystem: { sid: 100, type: "xfs" },
+ };
+
beforeEach(() => {
- mockModel.mockReturnValue({
- drives: [formattedSdaModel],
- getMountPaths: () => [],
- });
+ mockUsePartitionable.mockReturnValue(formattedSdaModel);
+ mockUseDevice.mockReturnValue(formattedSda);
+ mockUseConfigModel.mockReturnValue({ drives: [formattedSdaModel] });
});
it("initializes the form with the current values", async () => {
@@ -206,7 +198,7 @@ describe("FormattableDevicePage", () => {
await user.type(labelInput, "TEST");
const acceptButton = screen.getByRole("button", { name: "Accept" });
await user.click(acceptButton);
- expect(mockAddFilesystem).toHaveBeenCalledWith("drives", 0, {
+ expect(mockSetFilesystem).toHaveBeenCalledWith("drives", 0, {
mountPath: "/home",
filesystem: {
type: "xfs",
diff --git a/web/src/components/storage/LogicalVolumePage.tsx b/web/src/components/storage/LogicalVolumePage.tsx
index 363c69c008..7382040cfd 100644
--- a/web/src/components/storage/LogicalVolumePage.tsx
+++ b/web/src/components/storage/LogicalVolumePage.tsx
@@ -658,7 +658,7 @@ export default function LogicalVolumePage() {
const changeSizeMode = (mode: SizeMode, size: SizeRange) => {
setSizeOption(mode);
setMinSize(size.min);
- if (mode === "custom" && initialValue.sizeOption === "auto" && size.min !== size.max) {
+ if (mode === "custom" && initialValue?.sizeOption === "auto" && size.min !== size.max) {
// Automatically stop using a range of sizes when a range is used by default.
setMaxSize("");
} else {
diff --git a/web/src/components/storage/LvmPage.test.tsx b/web/src/components/storage/LvmPage.test.tsx
index 358aebc5dc..b00cef3df9 100644
--- a/web/src/components/storage/LvmPage.test.tsx
+++ b/web/src/components/storage/LvmPage.test.tsx
@@ -23,60 +23,72 @@
import React from "react";
import { screen, within } from "@testing-library/react";
import { installerRender, mockParams } from "~/test-utils";
-import { model, StorageDevice } from "~/storage";
+import type { ConfigModel } from "~/model/storage/config-model";
+import type { Storage } from "~/model/system";
import { gib } from "./utils";
import LvmPage from "./LvmPage";
-const sda1: StorageDevice = {
+const sda1: Storage.Device = {
sid: 69,
+ class: "partition",
name: "/dev/sda1",
description: "Swap partition",
- isDrive: false,
- type: "partition",
- size: gib(2),
- shrinking: { unsupported: ["Resizing is not supported"] },
- start: 1,
+ block: {
+ start: 1,
+ size: gib(2),
+ shrinking: { supported: false },
+ },
};
-const sda: StorageDevice = {
+const sda: Storage.Device = {
sid: 59,
- isDrive: true,
- type: "disk",
- vendor: "Micron",
- model: "Micron 1100 SATA",
- driver: ["ahci", "mmcblk"],
- bus: "IDE",
- busId: "",
- transport: "usb",
- dellBOSS: false,
- sdCard: true,
- active: true,
+ class: "drive",
name: "/dev/sda",
- size: 1024,
- shrinking: { unsupported: ["Resizing is not supported"] },
- systems: [],
- partitionTable: {
- type: "gpt",
- partitions: [sda1],
- unpartitionedSize: 0,
- unusedSlots: [{ start: 3, size: gib(2) }],
+ description: "SDA drive",
+ drive: {
+ type: "disk",
+ model: "Micron 1100 SATA",
+ vendor: "Micron",
+ bus: "IDE",
+ busId: "",
+ transport: "usb",
+ driver: ["ahci", "mmcblk"],
+ info: {
+ dellBoss: false,
+ sdCard: true,
+ },
},
- udevIds: ["ata-Micron_1100_SATA_512GB_12563", "scsi-0ATA_Micron_1100_SATA_512GB"],
- udevPaths: ["pci-0000:00-12", "pci-0000:00-12-ata"],
- description: "",
+ block: {
+ start: 1,
+ size: gib(20),
+ active: true,
+ encrypted: false,
+ systems: [],
+ shrinking: { supported: false },
+ },
+ partitions: [sda1],
};
-const sdb: StorageDevice = {
+const sdb: Storage.Device = {
sid: 60,
- isDrive: true,
- type: "disk",
+ class: "drive",
name: "/dev/sdb",
- size: 1024,
- systems: [],
- description: "",
+ block: {
+ start: 1,
+ size: gib(10),
+ shrinking: { supported: false },
+ systems: [],
+ },
+ drive: {
+ type: "disk",
+ info: {
+ dellBoss: false,
+ sdCard: false,
+ },
+ },
};
-const mockSdaDrive: model.Drive = {
+const mockSdaDrive: ConfigModel.Drive = {
name: "/dev/sda",
spacePolicy: "delete",
partitions: [
@@ -84,13 +96,9 @@ const mockSdaDrive: model.Drive = {
mountPath: "swap",
size: {
min: gib(2),
- default: false, // false: user provided, true: calculated
+ default: false,
},
- filesystem: { default: false, type: "swap" },
- isNew: true,
- isUsed: false,
- isReused: false,
- isUsedBySpacePolicy: false,
+ filesystem: { default: true, type: "swap" },
},
{
mountPath: "/home",
@@ -98,93 +106,68 @@ const mockSdaDrive: model.Drive = {
min: gib(16),
default: true,
},
- filesystem: { default: false, type: "xfs" },
- isNew: true,
- isUsed: false,
- isReused: false,
- isUsedBySpacePolicy: false,
+ filesystem: { default: true, type: "xfs" },
},
],
- list: "drives",
- listIndex: 1,
- isExplicitBoot: false,
- isUsed: true,
- isAddingPartitions: true,
- isReusingPartitions: true,
- isTargetDevice: true,
- isBoot: false,
- getMountPaths: () => ["/home", "swap"],
- getVolumeGroups: () => [],
- getPartition: jest.fn(),
- getConfiguredExistingPartitions: jest.fn(),
};
-const mockRootVolumeGroup: model.VolumeGroup = {
+const mockRootVolumeGroup: ConfigModel.VolumeGroup = {
vgName: "fakeRootVg",
- list: "volumeGroups",
- listIndex: 1,
+ targetDevices: ["/dev/sda"],
logicalVolumes: [],
- getTargetDevices: () => [mockSdaDrive],
- getMountPaths: () => [],
};
-const mockHomeVolumeGroup: model.VolumeGroup = {
+const mockHomeVolumeGroup: ConfigModel.VolumeGroup = {
vgName: "fakeHomeVg",
- list: "volumeGroups",
- listIndex: 2,
+ targetDevices: ["/dev/sda"],
logicalVolumes: [],
- getTargetDevices: () => [mockSdaDrive],
- getMountPaths: () => [],
};
const mockAddVolumeGroup = jest.fn();
const mockEditVolumeGroup = jest.fn();
+const mockUseConfigModel = jest.fn();
+const mockUseVolumeGroup = jest.fn();
+const mockUseAvailableDevices = jest.fn();
-let mockUseModel = {
- drives: [mockSdaDrive],
- mdRaids: [],
- volumeGroups: [],
-};
-
-const mockUseAllDevices = [sda, sdb];
-
-jest.mock("~/queries/issues", () => ({
- ...jest.requireActual("~/queries/issues"),
- useIssuesChanges: jest.fn(),
- useIssues: () => [],
-}));
-
-jest.mock("~/queries/storage", () => ({
- ...jest.requireActual("~/queries/storage"),
- useDevices: () => mockUseAllDevices,
-}));
-
-jest.mock("~/hooks/storage/system", () => ({
- ...jest.requireActual("~/hooks/storage/system"),
- useAvailableDevices: () => mockUseAllDevices,
-}));
-
-jest.mock("~/hooks/storage/model", () => ({
- ...jest.requireActual("~/hooks/storage/model"),
- __esModule: true,
- useModel: () => mockUseModel,
+jest.mock("~/hooks/model/system/storage", () => ({
+ ...jest.requireActual("~/hooks/model/system/storage"),
+ useAvailableDevices: () => mockUseAvailableDevices(),
}));
-jest.mock("~/hooks/storage/volume-group", () => ({
- ...jest.requireActual("~/hooks/storage/volume-group"),
+jest.mock("~/hooks/model/storage/config-model", () => ({
+ ...jest.requireActual("~/hooks/model/storage/config-model"),
__esModule: true,
+ useConfigModel: () => mockUseConfigModel(),
+ useVolumeGroup: (id?: string) => mockUseVolumeGroup(id),
useAddVolumeGroup: () => mockAddVolumeGroup,
useEditVolumeGroup: () => mockEditVolumeGroup,
}));
+jest.mock("~/components/product/ProductRegistrationAlert", () => () => (
+
registration alert
+));
+
describe("LvmPage", () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ mockParams({});
+ mockUseAvailableDevices.mockReturnValue([sda, sdb]);
+ mockUseVolumeGroup.mockReturnValue(undefined);
+ });
+
describe("when creating a new volume group", () => {
it("allows configuring a new LVM volume group (without moving mount points)", async () => {
+ mockUseConfigModel.mockReturnValue({
+ drives: [mockSdaDrive],
+ mdRaids: [],
+ volumeGroups: [],
+ });
+
const { user } = installerRender(
);
const name = screen.getByRole("textbox", { name: "Name" });
const disks = screen.getByRole("group", { name: "Disks" });
- const sdaCheckbox = within(disks).getByRole("checkbox", { name: "sda (1 KiB)" });
- const sdbCheckbox = within(disks).getByRole("checkbox", { name: "sdb (1 KiB)" });
+ const sdaCheckbox = within(disks).getByRole("checkbox", { name: "sda (20 GiB)" });
+ const sdbCheckbox = within(disks).getByRole("checkbox", { name: "sdb (10 GiB)" });
const moveMountPointsCheckbox = screen.getByRole("checkbox", {
name: /Move the mount points currently configured at the selected disks to logical volumes/,
});
@@ -209,9 +192,15 @@ describe("LvmPage", () => {
});
it("allows configuring a new LVM volume group (moving mount points)", async () => {
+ mockUseConfigModel.mockReturnValue({
+ drives: [mockSdaDrive],
+ mdRaids: [],
+ volumeGroups: [],
+ });
+
const { user } = installerRender(
);
const disks = screen.getByRole("group", { name: "Disks" });
- const sdbCheckbox = within(disks).getByRole("checkbox", { name: "sdb (1 KiB)" });
+ const sdbCheckbox = within(disks).getByRole("checkbox", { name: "sdb (10 GiB)" });
const moveMountPointsCheckbox = screen.getByRole("checkbox", {
name: /Move the mount points currently configured at the selected disks to logical volumes/,
});
@@ -227,10 +216,16 @@ describe("LvmPage", () => {
});
it("performs basic validations", async () => {
+ mockUseConfigModel.mockReturnValue({
+ drives: [mockSdaDrive],
+ mdRaids: [],
+ volumeGroups: [],
+ });
+
const { user } = installerRender(
);
const name = screen.getByRole("textbox", { name: "Name" });
const disks = screen.getByRole("group", { name: "Disks" });
- const sdaCheckbox = within(disks).getByRole("checkbox", { name: "sda (1 KiB)" });
+ const sdaCheckbox = within(disks).getByRole("checkbox", { name: "sda (20 GiB)" });
const acceptButton = screen.getByRole("button", { name: "Accept" });
// Unselect sda
@@ -262,11 +257,11 @@ describe("LvmPage", () => {
describe("when there are LVM volume groups", () => {
beforeEach(() => {
- mockUseModel = {
+ mockUseConfigModel.mockReturnValue({
drives: [mockSdaDrive],
mdRaids: [],
volumeGroups: [mockRootVolumeGroup],
- };
+ });
});
it("does not pre-fill the name input", () => {
@@ -278,11 +273,11 @@ describe("LvmPage", () => {
describe("when there are no LVM volume groups yet", () => {
beforeEach(() => {
- mockUseModel = {
+ mockUseConfigModel.mockReturnValue({
drives: [mockSdaDrive],
mdRaids: [],
volumeGroups: [],
- };
+ });
});
it("pre-fills the name input with 'system'", () => {
@@ -296,18 +291,19 @@ describe("LvmPage", () => {
describe("when editing", () => {
beforeEach(() => {
mockParams({ id: "fakeRootVg" });
- mockUseModel = {
+ mockUseConfigModel.mockReturnValue({
drives: [mockSdaDrive],
mdRaids: [],
volumeGroups: [mockRootVolumeGroup, mockHomeVolumeGroup],
- };
+ });
+ mockUseVolumeGroup.mockReturnValue(mockRootVolumeGroup);
});
it("performs basic validations", async () => {
const { user } = installerRender(
);
const name = screen.getByRole("textbox", { name: "Name" });
const disks = screen.getByRole("group", { name: "Disks" });
- const sdaCheckbox = within(disks).getByRole("checkbox", { name: "sda (1 KiB)" });
+ const sdaCheckbox = within(disks).getByRole("checkbox", { name: "sda (20 GiB)" });
const acceptButton = screen.getByRole("button", { name: "Accept" });
// Let's clean the default given name
@@ -329,7 +325,7 @@ describe("LvmPage", () => {
it("pre-fills form with the current volume group configuration", async () => {
installerRender(
);
const name = screen.getByRole("textbox", { name: "Name" });
- const sdaCheckbox = screen.getByRole("checkbox", { name: "sda (1 KiB)" });
+ const sdaCheckbox = screen.getByRole("checkbox", { name: "sda (20 GiB)" });
expect(name).toHaveValue("fakeRootVg");
expect(sdaCheckbox).toBeChecked();
});
diff --git a/web/src/components/storage/PartitionPage.test.tsx b/web/src/components/storage/PartitionPage.test.tsx
index fc1a5b0f18..a182c7ab8b 100644
--- a/web/src/components/storage/PartitionPage.test.tsx
+++ b/web/src/components/storage/PartitionPage.test.tsx
@@ -24,162 +24,143 @@ import React from "react";
import { screen, within } from "@testing-library/react";
import { installerRender, mockParams } from "~/test-utils";
import PartitionPage from "./PartitionPage";
-import { StorageDevice, model } from "~/storage";
-import { apiModel, Volume } from "~/api/storage/types";
+import type { ConfigModel } from "~/model/storage/config-model";
+import type { Storage } from "~/model/system";
import { gib } from "./utils";
-jest.mock("~/queries/issues", () => ({
- ...jest.requireActual("~/queries/issues"),
- useIssuesChanges: jest.fn(),
- useIssues: () => [],
-}));
-
jest.mock("./ProposalResultSection", () => () =>
result section
);
jest.mock("./ProposalTransactionalInfo", () => () =>
transactional info
);
+jest.mock("~/components/product/ProductRegistrationAlert", () => () => (
+
registration alert
+));
-const mockGetPartition = jest.fn();
-
-const sda1: StorageDevice = {
+const sda1: Storage.Device = {
sid: 69,
+ class: "partition",
name: "/dev/sda1",
description: "Swap partition",
- isDrive: false,
- type: "partition",
- size: gib(2),
- shrinking: { unsupported: ["Resizing is not supported"] },
- start: 1,
+ block: {
+ start: 1,
+ size: gib(2),
+ shrinking: { supported: false },
+ },
};
-const sda: StorageDevice = {
+const sda: Storage.Device = {
sid: 59,
- isDrive: true,
- type: "disk",
- vendor: "Micron",
- model: "Micron 1100 SATA",
- driver: ["ahci", "mmcblk"],
- bus: "IDE",
- busId: "",
- transport: "usb",
- dellBOSS: false,
- sdCard: true,
- active: true,
+ class: "drive",
name: "/dev/sda",
- size: 1024,
- shrinking: { unsupported: ["Resizing is not supported"] },
- systems: [],
- partitionTable: {
- type: "gpt",
- partitions: [sda1],
- unpartitionedSize: 0,
- unusedSlots: [{ start: 3, size: gib(2) }],
+ description: "SDA drive",
+ drive: {
+ type: "disk",
+ model: "Micron 1100 SATA",
+ vendor: "Micron",
+ bus: "IDE",
+ busId: "",
+ transport: "usb",
+ driver: ["ahci", "mmcblk"],
+ info: {
+ dellBoss: false,
+ sdCard: true,
+ },
+ },
+ block: {
+ start: 1,
+ size: gib(20),
+ active: true,
+ encrypted: false,
+ systems: [],
+ shrinking: { supported: false },
},
- udevIds: ["ata-Micron_1100_SATA_512GB_12563", "scsi-0ATA_Micron_1100_SATA_512GB"],
- udevPaths: ["pci-0000:00-12", "pci-0000:00-12-ata"],
- description: "",
+ partitions: [sda1],
};
-const mockPartition: model.Partition = {
- isNew: false,
- isUsed: true,
- isReused: false,
- isUsedBySpacePolicy: false,
+const swap: ConfigModel.Partition = {
+ mountPath: "swap",
+ size: {
+ min: gib(2),
+ default: false,
+ },
+ filesystem: { default: false, type: "swap" },
+};
+
+const home: ConfigModel.Partition = {
+ mountPath: "/home",
+ size: {
+ default: false,
+ min: gib(5),
+ max: gib(5),
+ },
+ filesystem: {
+ default: false,
+ type: "xfs",
+ label: "HOME",
+ },
};
-const mockDrive: model.Drive = {
+const drive: ConfigModel.Drive = {
name: "/dev/sda",
spacePolicy: "delete",
- partitions: [
- {
- mountPath: "swap",
- size: {
- min: gib(2),
- default: false, // false: user provided, true: calculated
- },
- filesystem: { default: false, type: "swap" },
- isNew: true,
- isUsed: false,
- isReused: false,
- isUsedBySpacePolicy: false,
- },
- {
- mountPath: "/home",
- size: {
- min: gib(16),
- default: true,
- },
- filesystem: { default: false, type: "xfs" },
- isNew: true,
- isUsed: false,
- isReused: false,
- isUsedBySpacePolicy: false,
- },
- ],
- list: "drives",
- listIndex: 1,
- isExplicitBoot: false,
- isUsed: true,
- isAddingPartitions: true,
- isReusingPartitions: true,
- isTargetDevice: false,
- isBoot: true,
- getMountPaths: jest.fn(),
- getVolumeGroups: jest.fn(),
- getPartition: mockGetPartition,
- getConfiguredExistingPartitions: () => [mockPartition],
+ partitions: [swap],
};
-const mockSolvedConfigModel: apiModel.Config = {
- drives: [mockDrive],
+const driveWithHome: ConfigModel.Drive = {
+ name: "/dev/sda",
+ spacePolicy: "delete",
+ partitions: [swap, home],
};
-const mockHomeVolume: Volume = {
+const homeVolume: Storage.Volume = {
mountPath: "/home",
- mountOptions: [],
- target: "default",
fsType: "btrfs",
minSize: 1024,
maxSize: 1024,
- autoSize: false,
snapshots: false,
- transactional: false,
+ autoSize: false,
outline: {
required: false,
fsTypes: ["btrfs"],
- supportAutoSize: false,
snapshotsConfigurable: false,
snapshotsAffectSizes: false,
+ supportAutoSize: false,
sizeRelevantVolumes: [],
- adjustByRam: false,
},
};
-jest.mock("~/queries/storage", () => ({
- ...jest.requireActual("~/queries/storage"),
- useDevices: () => [sda],
- useVolume: () => mockHomeVolume,
-}));
-
-jest.mock("~/hooks/storage/model", () => ({
- ...jest.requireActual("~/hooks/storage/model"),
- useModel: () => ({
- drives: [mockDrive],
- getMountPaths: () => [],
- }),
-}));
+const mockUseDevice = jest.fn();
+const mockUsePartitionable = jest.fn();
+const mockUseConfigModel = jest.fn();
+const mockUseSolvedConfigModel = jest.fn();
+const mockUseMissingMountPaths = jest.fn();
+const mockUseVolumeTemplate = jest.fn();
+const mockAddPartition = jest.fn();
+const mockEditPartition = jest.fn();
-jest.mock("~/hooks/storage/product", () => ({
- ...jest.requireActual("~/hooks/storage/product"),
- useMissingMountPaths: () => ["/home", "swap"],
+jest.mock("~/hooks/model/system/storage", () => ({
+ ...jest.requireActual("~/hooks/model/system/storage"),
+ useDevice: (name: string) => mockUseDevice(name),
+ useVolumeTemplate: (mountPath: string) => mockUseVolumeTemplate(mountPath),
}));
-jest.mock("~/queries/storage/config-model", () => ({
- ...jest.requireActual("~/queries/storage/config-model"),
- useConfigModel: () => ({ drives: [mockDrive] }),
- useSolvedConfigModel: () => mockSolvedConfigModel,
+jest.mock("~/hooks/model/storage/config-model", () => ({
+ ...jest.requireActual("~/hooks/model/storage/config-model"),
+ usePartitionable: (collection: string, index: number) => mockUsePartitionable(collection, index),
+ useConfigModel: () => mockUseConfigModel(),
+ useSolvedConfigModel: (model?: ConfigModel.Config) => mockUseSolvedConfigModel(model),
+ useMissingMountPaths: () => mockUseMissingMountPaths(),
+ useAddPartition: () => mockAddPartition,
+ useEditPartition: () => mockEditPartition,
}));
beforeEach(() => {
- mockParams({ list: "drives", listIndex: "0" });
+ jest.clearAllMocks();
+ mockParams({ collection: "drives", index: "0" });
+ mockUsePartitionable.mockReturnValue(drive);
+ mockUseDevice.mockReturnValue(sda);
+ mockUseConfigModel.mockReturnValue({ drives: [drive] });
+ mockUseSolvedConfigModel.mockReturnValue({ drives: [driveWithHome] });
+ mockUseMissingMountPaths.mockReturnValue(["/home", "swap"]);
+ mockUseVolumeTemplate.mockReturnValue(homeVolume);
});
describe("PartitionPage", () => {
@@ -282,20 +263,9 @@ describe("PartitionPage", () => {
describe("if editing a partition", () => {
beforeEach(() => {
- mockParams({ list: "drives", listIndex: "0", partitionId: "/home" });
- mockGetPartition.mockReturnValue({
- mountPath: "/home",
- size: {
- default: false,
- min: gib(5),
- max: gib(5),
- },
- filesystem: {
- default: false,
- type: "xfs",
- label: "HOME",
- },
- });
+ mockParams({ collection: "drives", index: "0", partitionId: "/home" });
+ mockUsePartitionable.mockReturnValue(driveWithHome);
+ mockUseConfigModel.mockReturnValue({ drives: [driveWithHome] });
});
it("initializes the form with the partition values", async () => {
@@ -318,18 +288,16 @@ describe("PartitionPage", () => {
describe("if the max size is unlimited", () => {
beforeEach(() => {
- mockParams({ list: "drives", listIndex: "0", partitionId: "/home" });
- mockGetPartition.mockReturnValue({
- mountPath: "/home",
- size: {
- default: false,
- min: gib(5),
- },
- filesystem: {
- default: false,
- type: "xfs",
- },
- });
+ const unlimitedHome = {
+ ...home,
+ size: { default: false, min: gib(5) },
+ };
+ const driveWithUnlimited = {
+ ...driveWithHome,
+ partitions: [swap, unlimitedHome],
+ };
+ mockUsePartitionable.mockReturnValue(driveWithUnlimited);
+ mockUseConfigModel.mockReturnValue({ drives: [driveWithUnlimited] });
});
it("checks allow growing", async () => {
@@ -341,19 +309,16 @@ describe("PartitionPage", () => {
describe("if the max size has a value", () => {
beforeEach(() => {
- mockParams({ list: "drives", listIndex: "0", partitionId: "/home" });
- mockGetPartition.mockReturnValue({
- mountPath: "/home",
- size: {
- default: false,
- min: gib(5),
- max: gib(10),
- },
- filesystem: {
- default: false,
- type: "xfs",
- },
- });
+ const rangedHome = {
+ ...home,
+ size: { default: false, min: gib(5), max: gib(10) },
+ };
+ const driveWithRange = {
+ ...driveWithHome,
+ partitions: [swap, rangedHome],
+ };
+ mockUsePartitionable.mockReturnValue(driveWithRange);
+ mockUseConfigModel.mockReturnValue({ drives: [driveWithRange] });
});
it("allows switching to a fixed size", async () => {
@@ -369,19 +334,16 @@ describe("PartitionPage", () => {
describe("if the default size has a max value", () => {
beforeEach(() => {
- mockParams({ list: "drives", listIndex: "0", partitionId: "/home" });
- mockGetPartition.mockReturnValue({
- mountPath: "/home",
- size: {
- default: true,
- min: gib(5),
- max: gib(10),
- },
- filesystem: {
- default: false,
- type: "xfs",
- },
- });
+ const rangedDefaultHome = {
+ ...home,
+ size: { default: true, min: gib(5), max: gib(10) },
+ };
+ const driveWithDefaultRange = {
+ ...driveWithHome,
+ partitions: [swap, rangedDefaultHome],
+ };
+ mockUsePartitionable.mockReturnValue(driveWithDefaultRange);
+ mockUseConfigModel.mockReturnValue({ drives: [driveWithDefaultRange] });
});
it("allows switching to a custom size", async () => {
diff --git a/web/src/components/storage/PartitionPage.tsx b/web/src/components/storage/PartitionPage.tsx
index 11d52f46bd..08309e0557 100644
--- a/web/src/components/storage/PartitionPage.tsx
+++ b/web/src/components/storage/PartitionPage.tsx
@@ -48,7 +48,6 @@ import { SelectWrapperProps as SelectProps } from "~/components/core/SelectWrapp
import SelectTypeaheadCreatable from "~/components/core/SelectTypeaheadCreatable";
import AutoSizeText from "~/components/storage/AutoSizeText";
import SizeModeSelect, { SizeMode, SizeRange } from "~/components/storage/SizeModeSelect";
-import AlertOutOfSync from "~/components/core/AlertOutOfSync";
import ResourceNotFound from "~/components/core/ResourceNotFound";
import configModel from "~/model/storage/config-model";
import { useVolumeTemplate, useDevice } from "~/hooks/model/system/storage";
@@ -842,7 +841,6 @@ const PartitionPageForm = () => {
-