sets up mocks. rolls out menuloop

This commit is contained in:
ThatBen 2025-04-01 14:09:20 +02:00
parent a9c94057e8
commit 1f694f7534
No known key found for this signature in database
GPG Key ID: E020A7DDCD52E1AB
9 changed files with 281 additions and 47 deletions

View File

@ -0,0 +1,15 @@
import { vi } from "vitest";
export const mockUiService = {
showLogo: vi.fn(),
showInfoMessage: vi.fn(),
showErrorMessage: vi.fn(),
askMultipleChoice: vi.fn(),
askPrompt: vi.fn()
};
export const mockConfigService = {
get: vi.fn(),
saveConfig: vi.fn(),
loadConfig: vi.fn(),
};

View File

@ -0,0 +1,9 @@
import { vi } from "vitest";
export const mockInstallMenu = {
show: vi.fn()
};
export const mockConfigMenu = {
show: vi.fn()
};

View File

@ -0,0 +1,16 @@
import { vi } from "vitest";
export const mockPathSelector = {
show: vi.fn(),
};
export const mockNumberSelector = {
show: vi.fn(),
};
export const mockMenuLoop = {
initialize: vi.fn(),
showOnce: vi.fn(),
showLoop: vi.fn(),
stopLoop: vi.fn(),
};

144
src/ui/configmenu.test.js Normal file
View File

@ -0,0 +1,144 @@
import { describe, beforeEach, it, expect, vi } from "vitest";
import { ConfigMenu } from "./configmenu.js";
import { mockUiService } from "../__mocks__/service.mocks.js";
import { mockConfigService } from "../__mocks__/service.mocks.js";
import { mockPathSelector, mockNumberSelector } from "../__mocks__/ui.mocks.js";
describe("ConfigMenu", () => {
let configMenu;
beforeEach(() => {
vi.resetAllMocks();
mockConfigService.get.mockReturnValue({
dataDir: "/data",
logsDir: "/logs",
storageQuota: 1024 * 1024 * 1024,
ports: {
discPort: 8090,
listenPort: 8070,
apiPort: 8080,
}
});
configMenu = new ConfigMenu(
mockUiService,
mockConfigService,
mockPathSelector,
mockNumberSelector
);
});
// it("displays the configuration menu", async () => {
// configMenu.running = false; // Prevent infinite loop
// await configMenu.show();
// expect(mockUiService.showInfoMessage).toHaveBeenCalledWith(
// "Codex Configuration",
// );
// expect(mockUiService.askMultipleChoice).toHaveBeenCalledWith([
// {
// label: `Data path = "${mockConfigService.get().dataDir}"`,
// action: configMenu.editDataDir,
// },
// {
// label: `Logs path = "${mockConfigService.get().logsDir}"`,
// action: configMenu.editLogsDir,
// },
// {
// label: `Storage quota = 1Gb`,
// action: configMenu.editStorageQuota,
// },
// {
// label: `Discovery port = ${mockConfigService.get().ports.discPort}`,
// action: configMenu.editDiscPort,
// },
// {
// label: `P2P listen port = ${mockConfigService.get().ports.listenPort}`,
// action: configMenu.editListenPort,
// },
// {
// label: `API port = ${mockConfigService.get().ports.apiPort}`,
// action: configMenu.editApiPort,
// },
// {
// label: "Save changes and exit",
// action: configMenu.saveChangesAndExit,
// },
// {
// label: "Discard changes and exit",
// action: configMenu.discardChangesAndExit,
// }
// ]);
// });
// it("edits the logs directory", async () => {
// mockPathSelector.show.mockResolvedValue("/new-logs");
// await configMenu.editLogsDir();
// expect(mockPathSelector.show).toHaveBeenCalledWith("/logs", true);
// expect(configMenu.config.logsDir).toEqual("/new-logs");
// });
// it("edits the storage quota", async () => {
// mockNumberSelector.show.mockResolvedValue(200 * 1024 * 1024);
// await configMenu.editStorageQuota();
// expect(mockUiService.showInfoMessage).toHaveBeenCalledWith(
// "You can use: 'GB' or 'gb', etc.",
// );
// expect(mockNumberSelector.show).toHaveBeenCalledWith(
// 1024 * 1024 * 1024,
// "Storage quota",
// true,
// );
// expect(configMenu.config.storageQuota).toEqual(200 * 1024 * 1024);
// });
// it("shows an error if storage quota is too small", async () => {
// mockNumberSelector.show.mockResolvedValue(50 * 1024 * 1024);
// await configMenu.editStorageQuota();
// expect(mockUiService.showErrorMessage).toHaveBeenCalledWith(
// "Storage quote should be >= 100mb.",
// );
// expect(configMenu.config.storageQuota).toEqual(1024 * 1024 * 1024); // Unchanged
// });
// it("edits the discovery port", async () => {
// mockNumberSelector.show.mockResolvedValue(9000);
// await configMenu.editDiscPort();
// expect(mockNumberSelector.show).toHaveBeenCalledWith(8090, "Discovery port", false);
// expect(configMenu.config.ports.discPort).toEqual(9000);
// });
// it("shows an error if port is out of range", async () => {
// mockNumberSelector.show.mockResolvedValue(1000);
// await configMenu.editDiscPort();
// expect(mockUiService.showErrorMessage).toHaveBeenCalledWith(
// "Port should be between 1024 and 65535.",
// );
// expect(configMenu.config.ports.discPort).toEqual(8090); // Unchanged
// });
// it("saves changes and exits", async () => {
// await configMenu.saveChangesAndExit();
// expect(mockConfigService.saveConfig).toHaveBeenCalled();
// expect(mockUiService.showInfoMessage).toHaveBeenCalledWith(
// "Configuration changes saved.",
// );
// expect(configMenu.running).toEqual(false);
// });
// it("discards changes and exits", async () => {
// await configMenu.discardChangesAndExit();
// expect(mockConfigService.loadConfig).toHaveBeenCalled();
// expect(mockUiService.showInfoMessage).toHaveBeenCalledWith(
// "Changes discarded.",
// );
// expect(configMenu.running).toEqual(false);
// });
});

View File

@ -1,18 +1,17 @@
export class MainMenu {
constructor(uiService, installMenu, configMenu) {
constructor(uiService, menuLoop, installMenu, configMenu) {
this.ui = uiService;
this.loop = menuLoop;
this.installMenu = installMenu;
this.configMenu = configMenu;
this.running = true;
this.loop.initialize(this.promptMainMenu);
}
show = async () => {
this.ui.showLogo();
this.ui.showInfoMessage("hello");
while (this.running) {
await this.promptMainMenu();
}
await this.loop.showLoop();
this.ui.showInfoMessage("K-THX-BYE");
};
@ -29,12 +28,8 @@ export class MainMenu {
},
{
label: "Exit",
action: this.closeMainMenu,
action: this.loop.stopLoop,
},
]);
};
closeMainMenu = async () => {
this.running = false;
};
}

View File

@ -1,56 +1,49 @@
import { describe, beforeEach, it, expect, vi } from "vitest";
import { MainMenu } from "./mainmenu.js";
import { mockUiService } from "../__mocks__/service.mocks.js";
import { mockInstallMenu, mockConfigMenu } from "../__mocks__/ui.mocks.js";
import { mockMenuLoop } from "../__mocks__/utils.mocks.js";
describe("mainmenu", () => {
let mainmenu;
const mockUiService = {
showLogo: vi.fn(),
showInfoMessage: vi.fn(),
askMultipleChoice: vi.fn(),
};
const mockInstallMenu = {
show: vi.fn(),
};
const mockConfigMenu = {
show: vi.fn(),
}
beforeEach(() => {
vi.resetAllMocks();
mainmenu = new MainMenu(mockUiService, mockInstallMenu, mockConfigMenu);
// Presents test getting stuck in main loop.
const originalPrompt = mainmenu.promptMainMenu;
mainmenu.promptMainMenu = async () => {
mainmenu.running = false;
await originalPrompt();
};
mainmenu = new MainMenu(mockUiService, mockMenuLoop, mockInstallMenu, mockConfigMenu);
});
it("shows the main menu", async () => {
it("initializes the menu loop with the promptMainMenu function", () => {
expect(mockMenuLoop.initialize).toHaveBeenCalledWith(mainmenu.promptMainMenu);
});
it("shows the logo", async () => {
await mainmenu.show();
expect(mockUiService.showLogo).toHaveBeenCalled();
expect(mockUiService.showInfoMessage).toHaveBeenCalledWith("hello"); // example, delete this later.
expect(mockUiService.showInfoMessage).toHaveBeenCalledWith("K-THX-BYE"); // example, delete this later.
});
it("starts the menu loop", async () => {
await mainmenu.show();
expect(mockMenuLoop.showLoop).toHaveBeenCalled();
});
it("shows the exit message after the menu loop", async () => {
await mainmenu.show();
expect(mockUiService.showInfoMessage).toHaveBeenCalledWith("K-THX-BYE");
});
it("prompts the main menu with multiple choices", async () => {
await mainmenu.promptMainMenu();
expect(mockUiService.askMultipleChoice).toHaveBeenCalledWith(
"Select an option",
[
{ label: "Install Codex", action: mockInstallMenu.show },
{ label: "Configure Codex", action: mockConfigMenu.show },
{ label: "Exit", action: mainmenu.closeMainMenu },
{ label: "Exit", action: mockMenuLoop.stopLoop },
],
);
});
it("sets running to false when closeMainMenu is called", async () => {
mainmenu.running = true;
await mainmenu.closeMainMenu();
expect(mainmenu.running).toEqual(false);
});
});

20
src/utils/menuLoop.js Normal file
View File

@ -0,0 +1,20 @@
export class MenuLoop {
initialize = (menuPrompt) => {
this.menuPrompt = menuPrompt;
}
showOnce = async () => {
await this.menuPrompt();
}
showLoop = async () => {
this.running = true;
while (this.running) {
await this.menuPrompt();
}
}
stopLoop = () => {
this.running = false;
}
}

View File

@ -0,0 +1,42 @@
import { describe, beforeEach, it, expect, vi } from "vitest";
import { MenuLoop } from "./menuLoop.js";
describe("MenuLoop", () => {
let menuLoop;
const mockPrompt = vi.fn();
beforeEach(() => {
vi.resetAllMocks();
menuLoop = new MenuLoop();
menuLoop.initialize(mockPrompt);
});
it("can show menu once", async () => {
await menuLoop.showOnce();
expect(mockPrompt).toHaveBeenCalledTimes(1);
});
it("can stop the menu loop", async () => {
mockPrompt.mockImplementation(() => {
menuLoop.stopLoop();
});
await menuLoop.showLoop();
expect(mockPrompt).toHaveBeenCalledTimes(1);
expect(menuLoop.running).toBe(false);
});
it("can run menu in a loop", async () => {
let calls = 0;
mockPrompt.mockImplementation(() => {
calls++;
if (calls >= 3) {
menuLoop.stopLoop();
}
});
await menuLoop.showLoop();
expect(mockPrompt).toHaveBeenCalledTimes(3);
});
});

View File

@ -16,7 +16,7 @@ describe("number selector", () => {
});
it("shows the prompt", async () => {
await numberSelector.showNumberSelector(0, prompt, false);
await numberSelector.show(0, prompt, false);
expect(mockUiService.askPrompt).toHaveBeenCalledWith(prompt);
});
@ -24,7 +24,7 @@ describe("number selector", () => {
it("returns a number given valid input", async () => {
mockUiService.askPrompt.mockResolvedValue("123");
const number = await numberSelector.showNumberSelector(0, prompt, false);
const number = await numberSelector.show(0, prompt, false);
expect(number).toEqual(123);
});
@ -34,7 +34,7 @@ describe("number selector", () => {
mockUiService.askPrompt.mockResolvedValue("what?!");
const number = await numberSelector.showNumberSelector(
const number = await numberSelector.show(
currentValue,
prompt,
false,
@ -45,7 +45,7 @@ describe("number selector", () => {
async function run(input) {
mockUiService.askPrompt.mockResolvedValue(input);
return await numberSelector.showNumberSelector(0, prompt, true);
return await numberSelector.show(0, prompt, true);
}
it("allows for metric postfixes (k)", async () => {