From c2f1f7cc7ba1acc82ef30d3d6595720c65d6fc07 Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 16 Apr 2025 10:18:38 +0200 Subject: [PATCH] Adds spinners and error handling for process control in main menu --- src/ui/mainMenu.js | 26 +++++++++- src/ui/mainMenu.test.js | 110 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 132 insertions(+), 4 deletions(-) diff --git a/src/ui/mainMenu.js b/src/ui/mainMenu.js index bb399e7..8dcb365 100644 --- a/src/ui/mainMenu.js +++ b/src/ui/mainMenu.js @@ -60,7 +60,7 @@ export class MainMenu { }, { label: "Stop Codex", - action: this.processControl.stopCodexProcess, + action: this.stopCodex, }, { label: "Exit", @@ -73,7 +73,7 @@ export class MainMenu { await this.ui.askMultipleChoice("Codex is installed but not running", [ { label: "Start Codex", - action: this.processControl.startCodexProcess, + action: this.startCodex, }, { label: "Edit Codex config", @@ -89,4 +89,26 @@ export class MainMenu { }, ]); }; + + startCodex = async () => { + const spinner = this.ui.createAndStartSpinner("Starting..."); + try { + await this.processControl.startCodexProcess(); + this.ui.stopSpinnerSuccess(spinner); + } catch (exception) { + this.ui.stopSpinnerError(spinner); + this.ui.showErrorMessage(`Failed to start Codex. "${exception}"`); + } + }; + + stopCodex = async () => { + const spinner = this.ui.createAndStartSpinner("Stopping..."); + try { + await this.processControl.stopCodexProcess(); + this.ui.stopSpinnerSuccess(spinner); + } catch (exception) { + this.ui.stopSpinnerError(spinner); + this.ui.showErrorMessage(`Failed to stop Codex. "${exception}"`); + } + }; } diff --git a/src/ui/mainMenu.test.js b/src/ui/mainMenu.test.js index f31de9a..1f717e9 100644 --- a/src/ui/mainMenu.test.js +++ b/src/ui/mainMenu.test.js @@ -115,7 +115,7 @@ describe("mainmenu", () => { "Codex is running", [ { label: "Open Codex app", action: mockCodexApp.openCodexApp }, - { label: "Stop Codex", action: mockProcessControl.stopCodexProcess }, + { label: "Stop Codex", action: mainmenu.stopCodex }, { label: "Exit", action: mockMenuLoop.stopLoop }, ], ); @@ -131,7 +131,7 @@ describe("mainmenu", () => { [ { label: "Start Codex", - action: mockProcessControl.startCodexProcess, + action: mainmenu.startCodex, }, { label: "Edit Codex config", action: mockConfigMenu.show }, { label: "Uninstall Codex", action: mockInstallMenu.show }, @@ -140,4 +140,110 @@ describe("mainmenu", () => { ); }); }); + + describe("process control", () => { + const mockSpinner = { + isMock: "yes", + }; + + beforeEach(() => { + mockUiService.createAndStartSpinner.mockReturnValue(mockSpinner); + }); + + describe("startCodex", () => { + it("starts codex", async () => { + await mainmenu.startCodex(); + + expect(mockProcessControl.startCodexProcess).toHaveBeenCalled(); + }); + + it("shows error message when process control throws", async () => { + mockProcessControl.startCodexProcess.mockRejectedValueOnce( + new Error("A!"), + ); + + await mainmenu.startCodex(); + + expect(mockUiService.showErrorMessage).toHaveBeenCalledWith( + 'Failed to start Codex. "Error: A!"', + ); + }); + + it("starts spinner", async () => { + await mainmenu.startCodex(); + + expect(mockUiService.createAndStartSpinner).toHaveBeenCalledWith( + "Starting...", + ); + }); + + it("stops spinner on success", async () => { + await mainmenu.startCodex(); + + expect(mockUiService.stopSpinnerSuccess).toHaveBeenCalledWith( + mockSpinner, + ); + }); + + it("stops spinner on failure", async () => { + mockProcessControl.startCodexProcess.mockRejectedValueOnce( + new Error("A!"), + ); + + await mainmenu.startCodex(); + + expect(mockUiService.stopSpinnerError).toHaveBeenCalledWith( + mockSpinner, + ); + }); + }); + + describe("stopCodex", () => { + it("stops codex", async () => { + await mainmenu.stopCodex(); + + expect(mockProcessControl.stopCodexProcess).toHaveBeenCalled(); + }); + + it("shows error message when process control throws", async () => { + mockProcessControl.stopCodexProcess.mockRejectedValueOnce( + new Error("A!"), + ); + + await mainmenu.stopCodex(); + + expect(mockUiService.showErrorMessage).toHaveBeenCalledWith( + 'Failed to stop Codex. "Error: A!"', + ); + }); + + it("starts spinner", async () => { + await mainmenu.stopCodex(); + + expect(mockUiService.createAndStartSpinner).toHaveBeenCalledWith( + "Stopping...", + ); + }); + + it("stops spinner on success", async () => { + await mainmenu.stopCodex(); + + expect(mockUiService.stopSpinnerSuccess).toHaveBeenCalledWith( + mockSpinner, + ); + }); + + it("stops spinner on failure", async () => { + mockProcessControl.stopCodexProcess.mockRejectedValueOnce( + new Error("A!"), + ); + + await mainmenu.stopCodex(); + + expect(mockUiService.stopSpinnerError).toHaveBeenCalledWith( + mockSpinner, + ); + }); + }); + }); });