mirror of
https://github.com/logos-storage/logos-storage-installer.git
synced 2026-01-04 06:23:07 +00:00
wip: install menu
This commit is contained in:
parent
8365e556a0
commit
c8d96425d8
@ -14,3 +14,7 @@ export const mockMenuLoop = {
|
|||||||
showLoop: vi.fn(),
|
showLoop: vi.fn(),
|
||||||
stopLoop: vi.fn(),
|
stopLoop: vi.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const mockDataDirMover = {
|
||||||
|
moveDataDir: vi.fn(),
|
||||||
|
};
|
||||||
|
|||||||
@ -99,7 +99,7 @@ export async function main() {
|
|||||||
const fsService = new FsService();
|
const fsService = new FsService();
|
||||||
const pathSelector = new PathSelector(uiService, new MenuLoop(), fsService);
|
const pathSelector = new PathSelector(uiService, new MenuLoop(), fsService);
|
||||||
const numberSelector = new NumberSelector(uiService);
|
const numberSelector = new NumberSelector(uiService);
|
||||||
const installMenu = new InstallMenu(uiService, configService);
|
const installMenu = new InstallMenu(uiService, configService, pathSelector);
|
||||||
const configMenu = new ConfigMenu(
|
const configMenu = new ConfigMenu(
|
||||||
uiService,
|
uiService,
|
||||||
new MenuLoop(),
|
new MenuLoop(),
|
||||||
@ -112,6 +112,7 @@ export async function main() {
|
|||||||
new MenuLoop(),
|
new MenuLoop(),
|
||||||
installMenu,
|
installMenu,
|
||||||
configMenu,
|
configMenu,
|
||||||
|
new DataDirMover(fsService, uiService)
|
||||||
);
|
);
|
||||||
|
|
||||||
await mainMenu.show();
|
await mainMenu.show();
|
||||||
|
|||||||
@ -38,4 +38,8 @@ export class FsService {
|
|||||||
makeDir = (dir) => {
|
makeDir = (dir) => {
|
||||||
fs.mkdirSync(dir);
|
fs.mkdirSync(dir);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
moveDir = (oldPath, newPath) => {
|
||||||
|
fs.moveSync(oldPath, newPath);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,18 +5,21 @@ export class ConfigMenu {
|
|||||||
configService,
|
configService,
|
||||||
pathSelector,
|
pathSelector,
|
||||||
numberSelector,
|
numberSelector,
|
||||||
|
dataDirMover,
|
||||||
) {
|
) {
|
||||||
this.ui = uiService;
|
this.ui = uiService;
|
||||||
this.loop = menuLoop;
|
this.loop = menuLoop;
|
||||||
this.configService = configService;
|
this.configService = configService;
|
||||||
this.pathSelector = pathSelector;
|
this.pathSelector = pathSelector;
|
||||||
this.numberSelector = numberSelector;
|
this.numberSelector = numberSelector;
|
||||||
|
this.dataDirMover = dataDirMover;
|
||||||
|
|
||||||
this.loop.initialize(this.showConfigMenu);
|
this.loop.initialize(this.showConfigMenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
show = async () => {
|
show = async () => {
|
||||||
this.config = this.configService.get();
|
this.config = this.configService.get();
|
||||||
|
this.originalDataDir = this.config.dataDir;
|
||||||
this.ui.showInfoMessage("Codex Configuration");
|
this.ui.showInfoMessage("Codex Configuration");
|
||||||
await this.loop.showLoop();
|
await this.loop.showLoop();
|
||||||
};
|
};
|
||||||
@ -76,46 +79,10 @@ export class ConfigMenu {
|
|||||||
};
|
};
|
||||||
|
|
||||||
editDataDir = async () => {
|
editDataDir = async () => {
|
||||||
// todo
|
this.config.dataDir = await this.pathSelector.show(
|
||||||
// function updateDataDir(config, newDataDir) {
|
this.config.dataDir,
|
||||||
// if (config.dataDir == newDataDir) return config;
|
false,
|
||||||
// // The Codex dataDir is a little strange:
|
);
|
||||||
// // If the old one is empty: The new one should not exist, so that codex creates it
|
|
||||||
// // with the correct security permissions.
|
|
||||||
// // If the old one does exist: We move it.
|
|
||||||
// if (isDir(config.dataDir)) {
|
|
||||||
// console.log(
|
|
||||||
// showInfoMessage(
|
|
||||||
// "Moving Codex data folder...\n" +
|
|
||||||
// `From: "${config.dataDir}"\n` +
|
|
||||||
// `To: "${newDataDir}"`,
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
// try {
|
|
||||||
// fs.moveSync(config.dataDir, newDataDir);
|
|
||||||
// } catch (error) {
|
|
||||||
// console.log(
|
|
||||||
// showErrorMessage("Error while moving dataDir: " + error.message),
|
|
||||||
// );
|
|
||||||
// throw error;
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// // Old data dir does not exist.
|
|
||||||
// if (isDir(newDataDir)) {
|
|
||||||
// console.log(
|
|
||||||
// showInfoMessage(
|
|
||||||
// "Warning: the selected data path already exists.\n" +
|
|
||||||
// `New data path = "${newDataDir}"\n` +
|
|
||||||
// "Codex may overwrite data in this folder.\n" +
|
|
||||||
// "Codex will fail to start if this folder does not have the required\n" +
|
|
||||||
// "security permissions.",
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// config.dataDir = newDataDir;
|
|
||||||
// return config;
|
|
||||||
// }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
editLogsDir = async () => {
|
editLogsDir = async () => {
|
||||||
@ -181,6 +148,15 @@ export class ConfigMenu {
|
|||||||
};
|
};
|
||||||
|
|
||||||
saveChangesAndExit = async () => {
|
saveChangesAndExit = async () => {
|
||||||
|
if (this.config.dataDir !== this.originalDataDir) {
|
||||||
|
// The Codex data-dir is a little special.
|
||||||
|
// Use a dedicated module to move it.
|
||||||
|
await this.dataDirMover.moveDataDir(
|
||||||
|
this.originalDataDir,
|
||||||
|
this.config.dataDir,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
this.configService.saveConfig();
|
this.configService.saveConfig();
|
||||||
this.ui.showInfoMessage("Configuration changes saved.");
|
this.ui.showInfoMessage("Configuration changes saved.");
|
||||||
this.loop.stopLoop();
|
this.loop.stopLoop();
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import {
|
|||||||
mockPathSelector,
|
mockPathSelector,
|
||||||
mockNumberSelector,
|
mockNumberSelector,
|
||||||
mockMenuLoop,
|
mockMenuLoop,
|
||||||
|
mockDataDirMover,
|
||||||
} from "../__mocks__/utils.mocks.js";
|
} from "../__mocks__/utils.mocks.js";
|
||||||
|
|
||||||
describe("ConfigMenu", () => {
|
describe("ConfigMenu", () => {
|
||||||
@ -31,6 +32,7 @@ describe("ConfigMenu", () => {
|
|||||||
mockConfigService,
|
mockConfigService,
|
||||||
mockPathSelector,
|
mockPathSelector,
|
||||||
mockNumberSelector,
|
mockNumberSelector,
|
||||||
|
mockDataDirMover,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -57,6 +59,11 @@ describe("ConfigMenu", () => {
|
|||||||
expect(configMenu.config).toEqual(config);
|
expect(configMenu.config).toEqual(config);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("sets the original datadir field", async () => {
|
||||||
|
await configMenu.show();
|
||||||
|
expect(configMenu.originalDataDir).toEqual(config.dataDir);
|
||||||
|
});
|
||||||
|
|
||||||
describe("config menu options", () => {
|
describe("config menu options", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
configMenu.config = config;
|
configMenu.config = config;
|
||||||
@ -103,6 +110,15 @@ describe("ConfigMenu", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("edits the logs directory", async () => {
|
||||||
|
const originalPath = config.dataDir;
|
||||||
|
mockPathSelector.show.mockResolvedValue("/new-data");
|
||||||
|
await configMenu.editDataDir();
|
||||||
|
|
||||||
|
expect(mockPathSelector.show).toHaveBeenCalledWith(originalPath, false);
|
||||||
|
expect(configMenu.config.dataDir).toEqual("/new-data");
|
||||||
|
});
|
||||||
|
|
||||||
it("edits the logs directory", async () => {
|
it("edits the logs directory", async () => {
|
||||||
const originalPath = config.logsDir;
|
const originalPath = config.logsDir;
|
||||||
mockPathSelector.show.mockResolvedValue("/new-logs");
|
mockPathSelector.show.mockResolvedValue("/new-logs");
|
||||||
@ -219,8 +235,11 @@ describe("ConfigMenu", () => {
|
|||||||
);
|
);
|
||||||
expect(configMenu.config.ports.apiPort).toEqual(originalPort);
|
expect(configMenu.config.ports.apiPort).toEqual(originalPort);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("save and discard changes", () => {
|
||||||
it("saves changes and exits", async () => {
|
it("saves changes and exits", async () => {
|
||||||
|
await configMenu.show();
|
||||||
await configMenu.saveChangesAndExit();
|
await configMenu.saveChangesAndExit();
|
||||||
|
|
||||||
expect(mockConfigService.saveConfig).toHaveBeenCalled();
|
expect(mockConfigService.saveConfig).toHaveBeenCalled();
|
||||||
@ -230,6 +249,19 @@ describe("ConfigMenu", () => {
|
|||||||
expect(mockMenuLoop.stopLoop).toHaveBeenCalled();
|
expect(mockMenuLoop.stopLoop).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("calls the dataDirMover when the new datadir is not equal to the original dataDir when saving changes", async () => {
|
||||||
|
config.dataDir = "/original-data";
|
||||||
|
await configMenu.show();
|
||||||
|
|
||||||
|
configMenu.config.dataDir = "/new-data";
|
||||||
|
await configMenu.saveChangesAndExit();
|
||||||
|
|
||||||
|
expect(mockDataDirMover.moveDataDir).toHaveBeenCalledWith(
|
||||||
|
configMenu.originalDataDir,
|
||||||
|
configMenu.config.dataDir,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("discards changes and exits", async () => {
|
it("discards changes and exits", async () => {
|
||||||
await configMenu.discardChangesAndExit();
|
await configMenu.discardChangesAndExit();
|
||||||
|
|
||||||
|
|||||||
@ -1,16 +1,16 @@
|
|||||||
export class InstallMenu {
|
export class InstallMenu {
|
||||||
constructor(uiService, configService) {
|
constructor(uiService, configService, pathSelector) {
|
||||||
this.ui = uiService;
|
this.ui = uiService;
|
||||||
|
this.configService = configService;
|
||||||
this.config = configService.get();
|
this.config = configService.get();
|
||||||
|
this.pathSelector = pathSelector;
|
||||||
}
|
}
|
||||||
|
|
||||||
show = async () => {
|
show = async () => {
|
||||||
await this.ui.askMultipleChoice("Configure your Codex installation", [
|
await this.ui.askMultipleChoice("Configure your Codex installation", [
|
||||||
{
|
{
|
||||||
label: "Install path: " + this.config.codexPath,
|
label: "Install path: " + this.config.codexPath,
|
||||||
action: async function () {
|
action: this.selectInstallPath,
|
||||||
console.log("run path selector");
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Storage provider module: Disabled (todo)",
|
label: "Storage provider module: Disabled (todo)",
|
||||||
@ -22,17 +22,25 @@ export class InstallMenu {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Cancel",
|
label: "Cancel",
|
||||||
action: async function () {},
|
action: this.doNothing,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
selectInstallPath = async () => {
|
||||||
|
this.config.codexPath = await this.pathSelector.show(
|
||||||
|
this.config.codexPath,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
this.configService.saveConfig();
|
||||||
|
};
|
||||||
|
|
||||||
storageProviderOption = async () => {
|
storageProviderOption = async () => {
|
||||||
this.ui.showInfoMessage("This option is not currently available.");
|
this.ui.showInfoMessage("This option is not currently available.");
|
||||||
await this.show();
|
await this.show();
|
||||||
};
|
};
|
||||||
|
|
||||||
performInstall = async () => {
|
performInstall = async () => {};
|
||||||
console.log("todo");
|
|
||||||
};
|
doNothing = async () => {};
|
||||||
}
|
}
|
||||||
|
|||||||
71
src/ui/installMenu.test.js
Normal file
71
src/ui/installMenu.test.js
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import { describe, beforeEach, it, expect, vi } from "vitest";
|
||||||
|
import { InstallMenu } from "./installMenu.js";
|
||||||
|
import { mockUiService } from "../__mocks__/service.mocks.js";
|
||||||
|
import { mockConfigService } from "../__mocks__/service.mocks.js";
|
||||||
|
import { mockPathSelector } from "../__mocks__/utils.mocks.js";
|
||||||
|
|
||||||
|
describe("InstallMenu", () => {
|
||||||
|
const config = {
|
||||||
|
codexPath: "/codex",
|
||||||
|
};
|
||||||
|
let installMenu;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.resetAllMocks();
|
||||||
|
mockConfigService.get.mockReturnValue(config);
|
||||||
|
|
||||||
|
installMenu = new InstallMenu(
|
||||||
|
mockUiService,
|
||||||
|
mockConfigService,
|
||||||
|
mockPathSelector,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("displays the install menu", async () => {
|
||||||
|
await installMenu.show();
|
||||||
|
expect(mockUiService.askMultipleChoice).toHaveBeenCalledWith(
|
||||||
|
"Configure your Codex installation",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
label: "Install path: " + config.codexPath,
|
||||||
|
action: installMenu.selectInstallPath,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Storage provider module: Disabled (todo)",
|
||||||
|
action: installMenu.storageProviderOption,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Install!",
|
||||||
|
action: installMenu.performInstall,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Cancel",
|
||||||
|
action: installMenu.doNothing,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows selecting the install path", async () => {
|
||||||
|
const originalPath = config.codexPath;
|
||||||
|
const newPath = "/new/path";
|
||||||
|
mockPathSelector.show.mockResolvedValue(newPath);
|
||||||
|
|
||||||
|
await installMenu.selectInstallPath();
|
||||||
|
|
||||||
|
expect(mockPathSelector.show).toHaveBeenCalledWith(originalPath, false);
|
||||||
|
expect(config.codexPath).toBe(newPath);
|
||||||
|
expect(mockConfigService.saveConfig).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows storage provider option is unavailable", async () => {
|
||||||
|
const showMock = vi.fn();
|
||||||
|
installMenu.show = showMock;
|
||||||
|
|
||||||
|
await installMenu.storageProviderOption();
|
||||||
|
|
||||||
|
expect(mockUiService.showInfoMessage).toHaveBeenCalledWith(
|
||||||
|
"This option is not currently available.",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
53
src/utils/dataDirMover.js
Normal file
53
src/utils/dataDirMover.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
export class DataDirMover {
|
||||||
|
constructor(fsService, uiService) {
|
||||||
|
this.fs = fsService;
|
||||||
|
this.ui = uiService;
|
||||||
|
}
|
||||||
|
|
||||||
|
moveDataDir = (oldPath, newPath) => {
|
||||||
|
if (oldPath === newPath) return;
|
||||||
|
|
||||||
|
// The Codex dataDir is a little strange:
|
||||||
|
// If the old one is empty: The new one should not exist, so that codex creates it with the correct security permissions.
|
||||||
|
// If the old one does exist: We move it.
|
||||||
|
|
||||||
|
if (this.fs.isDir(oldPath)) {
|
||||||
|
this.moveDir(oldPath, newPath);
|
||||||
|
} else {
|
||||||
|
this.ensureDoesNotExist(newPath);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
moveDir = (oldPath, newPath) => {
|
||||||
|
this.ui.showInfoMessage(
|
||||||
|
"Moving Codex data folder...\n" +
|
||||||
|
`From: "${oldPath}"\n` +
|
||||||
|
`To: "${newPath}"`,
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.fs.moveDir(oldPath, newPath);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(
|
||||||
|
this.ui.showErrorMessage(
|
||||||
|
"Error while moving dataDir: " + error.message,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ensureDoesNotExist = (path) => {
|
||||||
|
if (this.fs.isDir(path)) {
|
||||||
|
console.log(
|
||||||
|
this.ui.showInfoMessage(
|
||||||
|
"Warning: the selected data path already exists.\n" +
|
||||||
|
`New data path = "${path}"\n` +
|
||||||
|
"Codex may overwrite data in this folder.\n" +
|
||||||
|
"Codex will fail to start if this folder does not have the required\n" +
|
||||||
|
"security permissions.",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -55,13 +55,13 @@ export class PathSelector {
|
|||||||
splitPath = (str) => {
|
splitPath = (str) => {
|
||||||
var result = this.dropEmptyParts(str.replaceAll("\\", "/").split("/"));
|
var result = this.dropEmptyParts(str.replaceAll("\\", "/").split("/"));
|
||||||
if (str.startsWith("/") && this.roots.includes("/")) {
|
if (str.startsWith("/") && this.roots.includes("/")) {
|
||||||
result = ["/", ...result];
|
result = ["/", ...result];
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
dropEmptyParts = (parts) => {
|
dropEmptyParts = (parts) => {
|
||||||
return parts.filter(part => part.length > 0);
|
return parts.filter((part) => part.length > 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
combine = (parts) => {
|
combine = (parts) => {
|
||||||
@ -141,7 +141,7 @@ export class PathSelector {
|
|||||||
getSubDirOptions = () => {
|
getSubDirOptions = () => {
|
||||||
const fullPath = this.combine(this.currentPath);
|
const fullPath = this.combine(this.currentPath);
|
||||||
const entries = this.fs.readDir(fullPath);
|
const entries = this.fs.readDir(fullPath);
|
||||||
return entries.filter(entry => this.isSubDir(entry));
|
return entries.filter((entry) => this.isSubDir(entry));
|
||||||
};
|
};
|
||||||
|
|
||||||
downOne = async () => {
|
downOne = async () => {
|
||||||
|
|||||||
@ -20,7 +20,9 @@ describe("PathSelector", () => {
|
|||||||
|
|
||||||
describe("initialization", () => {
|
describe("initialization", () => {
|
||||||
it("initializes the menu loop", () => {
|
it("initializes the menu loop", () => {
|
||||||
expect(mockMenuLoop.initialize).toHaveBeenCalledWith(pathSelector.showPathSelector);
|
expect(mockMenuLoop.initialize).toHaveBeenCalledWith(
|
||||||
|
pathSelector.showPathSelector,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -92,9 +94,9 @@ describe("PathSelector", () => {
|
|||||||
it("shows down directory navigation", async () => {
|
it("shows down directory navigation", async () => {
|
||||||
mockFsService.readDir.mockReturnValue(["subdir1", "subdir2"]);
|
mockFsService.readDir.mockReturnValue(["subdir1", "subdir2"]);
|
||||||
mockFsService.isDir.mockReturnValue(true);
|
mockFsService.isDir.mockReturnValue(true);
|
||||||
|
|
||||||
await pathSelector.downOne();
|
await pathSelector.downOne();
|
||||||
|
|
||||||
expect(mockUiService.askMultipleChoice).toHaveBeenCalled();
|
expect(mockUiService.askMultipleChoice).toHaveBeenCalled();
|
||||||
expect(mockFsService.readDir).toHaveBeenCalledWith(mockStartPath);
|
expect(mockFsService.readDir).toHaveBeenCalledWith(mockStartPath);
|
||||||
});
|
});
|
||||||
@ -106,7 +108,7 @@ describe("PathSelector", () => {
|
|||||||
options[0].action(); // Select the first option
|
options[0].action(); // Select the first option
|
||||||
});
|
});
|
||||||
await pathSelector.downOne();
|
await pathSelector.downOne();
|
||||||
|
|
||||||
expect(pathSelector.currentPath).toEqual(["/", "home", "user", subdir]);
|
expect(pathSelector.currentPath).toEqual(["/", "home", "user", subdir]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -114,9 +116,11 @@ describe("PathSelector", () => {
|
|||||||
const newDir = "newdir";
|
const newDir = "newdir";
|
||||||
mockUiService.askPrompt.mockResolvedValue(newDir);
|
mockUiService.askPrompt.mockResolvedValue(newDir);
|
||||||
await pathSelector.createSubDir();
|
await pathSelector.createSubDir();
|
||||||
|
|
||||||
expect(mockUiService.askPrompt).toHaveBeenCalledWith("Enter name:");
|
expect(mockUiService.askPrompt).toHaveBeenCalledWith("Enter name:");
|
||||||
expect(mockFsService.makeDir).toHaveBeenCalled(mockStartPath + "/" + newDir);
|
expect(mockFsService.makeDir).toHaveBeenCalled(
|
||||||
|
mockStartPath + "/" + newDir,
|
||||||
|
);
|
||||||
expect(pathSelector.currentPath).toEqual(["/", "home", "user", newDir]);
|
expect(pathSelector.currentPath).toEqual(["/", "home", "user", newDir]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -135,7 +139,9 @@ describe("PathSelector", () => {
|
|||||||
it("validates full paths", () => {
|
it("validates full paths", () => {
|
||||||
mockFsService.isDir.mockReturnValue(false);
|
mockFsService.isDir.mockReturnValue(false);
|
||||||
pathSelector.updateCurrentIfValidFull("/invalid/path");
|
pathSelector.updateCurrentIfValidFull("/invalid/path");
|
||||||
expect(mockUiService.showErrorMessage).toHaveBeenCalledWith("The path does not exist.");
|
expect(mockUiService.showErrorMessage).toHaveBeenCalledWith(
|
||||||
|
"The path does not exist.",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user