/ src / ui / mainMenu.test.js
mainMenu.test.js
  1  import { describe, beforeEach, it, expect, vi } from "vitest";
  2  import { MainMenu } from "./mainMenu.js";
  3  import { mockUiService, mockCodexApp } from "../__mocks__/service.mocks.js";
  4  import {
  5    mockInstallMenu,
  6    mockConfigMenu,
  7    mockDataMenu,
  8    mockNodeStatusMenu,
  9  } from "../__mocks__/ui.mocks.js";
 10  import {
 11    mockInstaller,
 12    mockProcessControl,
 13  } from "../__mocks__/handler.mocks.js";
 14  import { mockMenuLoop } from "../__mocks__/utils.mocks.js";
 15  
 16  describe("mainmenu", () => {
 17    let mainmenu;
 18  
 19    beforeEach(() => {
 20      vi.resetAllMocks();
 21  
 22      mainmenu = new MainMenu(
 23        mockUiService,
 24        mockMenuLoop,
 25        mockInstallMenu,
 26        mockConfigMenu,
 27        mockInstaller,
 28        mockProcessControl,
 29        mockCodexApp,
 30        mockDataMenu,
 31        mockNodeStatusMenu,
 32      );
 33    });
 34  
 35    describe("constructor", () => {
 36      it("initializes the menu loop with the promptMainMenu function", () => {
 37        expect(mockMenuLoop.initialize).toHaveBeenCalledWith(
 38          mainmenu.promptMainMenu,
 39        );
 40      });
 41    });
 42  
 43    describe("show", () => {
 44      it("shows the logo", async () => {
 45        await mainmenu.show();
 46  
 47        expect(mockUiService.showLogo).toHaveBeenCalled();
 48      });
 49  
 50      it("starts the menu loop", async () => {
 51        await mainmenu.show();
 52  
 53        expect(mockMenuLoop.showLoop).toHaveBeenCalled();
 54      });
 55    });
 56  
 57    describe("promptMainMenu", () => {
 58      beforeEach(() => {
 59        mainmenu.showRunningMenu = vi.fn();
 60        mainmenu.showNotRunningMenu = vi.fn();
 61        mainmenu.showNotInstalledMenu = vi.fn();
 62      });
 63  
 64      it("shows running menu when number of codex processes is greater than zero", async () => {
 65        mockProcessControl.getNumberOfCodexProcesses.mockResolvedValue(1);
 66  
 67        await mainmenu.promptMainMenu();
 68  
 69        expect(mainmenu.showRunningMenu).toHaveBeenCalled();
 70        expect(mainmenu.showNotRunningMenu).not.toHaveBeenCalled();
 71        expect(mainmenu.showNotInstalledMenu).not.toHaveBeenCalled();
 72      });
 73  
 74      it("shows not running menu when number of codex processes is zero and codex is installed", async () => {
 75        mockProcessControl.getNumberOfCodexProcesses.mockResolvedValue(0);
 76        mockInstaller.isCodexInstalled.mockResolvedValue(true);
 77  
 78        await mainmenu.promptMainMenu();
 79  
 80        expect(mainmenu.showRunningMenu).not.toHaveBeenCalled();
 81        expect(mainmenu.showNotRunningMenu).toHaveBeenCalled();
 82        expect(mainmenu.showNotInstalledMenu).not.toHaveBeenCalled();
 83      });
 84  
 85      it("shows not installed menu when number of codex processes is zero and codex is not installed", async () => {
 86        mockProcessControl.getNumberOfCodexProcesses.mockResolvedValue(0);
 87        mockInstaller.isCodexInstalled.mockResolvedValue(false);
 88  
 89        await mainmenu.promptMainMenu();
 90  
 91        expect(mainmenu.showRunningMenu).not.toHaveBeenCalled();
 92        expect(mainmenu.showNotRunningMenu).not.toHaveBeenCalled();
 93        expect(mainmenu.showNotInstalledMenu).toHaveBeenCalled();
 94      });
 95    });
 96  
 97    describe("showNotInstalledMenu", () => {
 98      it("shows a menu with options to install Codex or exit", async () => {
 99        await mainmenu.showNotInstalledMenu();
100  
101        expect(mockUiService.askMultipleChoice).toHaveBeenCalledWith(
102          "Codex is not installed",
103          [
104            { label: "Install Codex", action: mockInstallMenu.show },
105            { label: "Exit", action: mockMenuLoop.stopLoop },
106          ],
107        );
108      });
109    });
110  
111    describe("showRunningMenu", () => {
112      it("shows a menu with options to stop Codex, open Codex app, upload, download, or exit", async () => {
113        await mainmenu.showRunningMenu();
114  
115        expect(mockUiService.askMultipleChoice).toHaveBeenCalledWith(
116          "Codex is running",
117          [
118            { label: "Open Codex app", action: mockCodexApp.openCodexApp },
119            { label: "Stop Codex", action: mainmenu.stopCodex },
120            {
121              label: "Show node status",
122              action: mockNodeStatusMenu.showNodeStatus,
123            },
124            { label: "Upload a file", action: mockDataMenu.performUpload },
125            { label: "Download a file", action: mockDataMenu.performDownload },
126            { label: "Show local data", action: mockDataMenu.showLocalData },
127            {
128              label: "Exit (Codex keeps running)",
129              action: mockMenuLoop.stopLoop,
130            },
131          ],
132        );
133      });
134    });
135  
136    describe("showNotRunningMenu", () => {
137      it("shows a menu with options to start Codex, configure, uninstall, or exit", async () => {
138        await mainmenu.showNotRunningMenu();
139  
140        expect(mockUiService.askMultipleChoice).toHaveBeenCalledWith(
141          "Codex is installed but not running",
142          [
143            {
144              label: "Start Codex",
145              action: mainmenu.startCodex,
146            },
147            { label: "Edit Codex config", action: mockConfigMenu.show },
148            { label: "Uninstall Codex", action: mockInstallMenu.show },
149            { label: "Exit", action: mockMenuLoop.stopLoop },
150          ],
151        );
152      });
153    });
154  
155    describe("process control", () => {
156      const mockSpinner = {
157        isMock: "yes",
158      };
159  
160      beforeEach(() => {
161        mockUiService.createAndStartSpinner.mockReturnValue(mockSpinner);
162      });
163  
164      describe("startCodex", () => {
165        it("starts codex", async () => {
166          await mainmenu.startCodex();
167  
168          expect(mockProcessControl.startCodexProcess).toHaveBeenCalled();
169        });
170  
171        it("shows error message when process control throws", async () => {
172          mockProcessControl.startCodexProcess.mockRejectedValueOnce(
173            new Error("A!"),
174          );
175  
176          await mainmenu.startCodex();
177  
178          expect(mockUiService.showErrorMessage).toHaveBeenCalledWith(
179            'Failed to start Codex. "Error: A!"',
180          );
181        });
182  
183        it("starts spinner", async () => {
184          await mainmenu.startCodex();
185  
186          expect(mockUiService.createAndStartSpinner).toHaveBeenCalledWith(
187            "Starting...",
188          );
189        });
190  
191        it("stops spinner on success", async () => {
192          await mainmenu.startCodex();
193  
194          expect(mockUiService.stopSpinnerSuccess).toHaveBeenCalledWith(
195            mockSpinner,
196          );
197        });
198  
199        it("stops spinner on failure", async () => {
200          mockProcessControl.startCodexProcess.mockRejectedValueOnce(
201            new Error("A!"),
202          );
203  
204          await mainmenu.startCodex();
205  
206          expect(mockUiService.stopSpinnerError).toHaveBeenCalledWith(
207            mockSpinner,
208          );
209        });
210      });
211  
212      describe("stopCodex", () => {
213        it("stops codex", async () => {
214          await mainmenu.stopCodex();
215  
216          expect(mockProcessControl.stopCodexProcess).toHaveBeenCalled();
217        });
218  
219        it("shows error message when process control throws", async () => {
220          mockProcessControl.stopCodexProcess.mockRejectedValueOnce(
221            new Error("A!"),
222          );
223  
224          await mainmenu.stopCodex();
225  
226          expect(mockUiService.showErrorMessage).toHaveBeenCalledWith(
227            'Failed to stop Codex. "Error: A!"',
228          );
229        });
230  
231        it("starts spinner", async () => {
232          await mainmenu.stopCodex();
233  
234          expect(mockUiService.createAndStartSpinner).toHaveBeenCalledWith(
235            "Stopping...",
236          );
237        });
238  
239        it("stops spinner on success", async () => {
240          await mainmenu.stopCodex();
241  
242          expect(mockUiService.stopSpinnerSuccess).toHaveBeenCalledWith(
243            mockSpinner,
244          );
245        });
246  
247        it("stops spinner on failure", async () => {
248          mockProcessControl.stopCodexProcess.mockRejectedValueOnce(
249            new Error("A!"),
250          );
251  
252          await mainmenu.stopCodex();
253  
254          expect(mockUiService.stopSpinnerError).toHaveBeenCalledWith(
255            mockSpinner,
256          );
257        });
258      });
259    });
260  });