pathSelector.test.js
1 import { describe, beforeEach, it, expect, vi } from "vitest"; 2 import { PathSelector } from "./pathSelector.js"; 3 import { mockUiService, mockFsService } from "../__mocks__/service.mocks.js"; 4 import { mockMenuLoop } from "../__mocks__/utils.mocks.js"; 5 6 describe("PathSelector", () => { 7 let pathSelector; 8 const mockRoots = ["/", "/home"]; 9 const mockStartPath = "/home/user"; 10 11 beforeEach(() => { 12 vi.resetAllMocks(); 13 mockFsService.getAvailableRoots.mockReturnValue(mockRoots); 14 mockFsService.pathJoin.mockImplementation((parts) => parts.join("/")); 15 mockFsService.isDir.mockReturnValue(true); 16 mockFsService.readDir.mockReturnValue(["dir1", "dir2"]); 17 18 pathSelector = new PathSelector(mockUiService, mockMenuLoop, mockFsService); 19 }); 20 21 describe("initialization", () => { 22 it("initializes the menu loop", () => { 23 expect(mockMenuLoop.initialize).toHaveBeenCalledWith( 24 pathSelector.showPathSelector, 25 ); 26 }); 27 }); 28 29 describe("show()", () => { 30 it("initializes path selection with given path", async () => { 31 await pathSelector.show(mockStartPath, true); 32 expect(mockFsService.getAvailableRoots).toHaveBeenCalled(); 33 expect(pathSelector.currentPath).toEqual(["/", "home", "user"]); 34 }); 35 36 it("uses first root if starting path is invalid", async () => { 37 await pathSelector.show("invalid/path", true); 38 expect(pathSelector.currentPath).toEqual([mockRoots[0]]); 39 }); 40 41 it("starts the menu loop", async () => { 42 await pathSelector.show(mockStartPath, true); 43 expect(mockMenuLoop.showLoop).toHaveBeenCalled(); 44 }); 45 46 it("returns the resulting path after selection", async () => { 47 pathSelector.resultingPath = mockStartPath; 48 const result = await pathSelector.show(mockStartPath, true); 49 expect(result).toBe(mockStartPath); 50 }); 51 }); 52 53 describe("path operations", () => { 54 beforeEach(async () => { 55 await pathSelector.show(mockStartPath, true); 56 }); 57 58 it("splits paths correctly", () => { 59 const result = pathSelector.splitPath("C:\\path\\to\\dir"); 60 expect(result).toEqual(["C:", "path", "to", "dir"]); 61 }); 62 63 it("drops empty path parts", () => { 64 const result = pathSelector.dropEmptyParts(["", "path", "", "dir", ""]); 65 expect(result).toEqual(["path", "dir"]); 66 }); 67 68 it("combines path parts correctly", () => { 69 const result = pathSelector.combine(["C:", "user", "docs"]); 70 expect(result).toBe("C:/user/docs"); 71 }); 72 73 it("combines path including root correctly", () => { 74 const result = pathSelector.combine(["/", "home", "user", "docs"]); 75 expect(result).toBe("/home/user/docs"); 76 }); 77 78 it("handles single part paths in combine", () => { 79 const result = pathSelector.combine(["root"]); 80 expect(result).toBe("root"); 81 }); 82 }); 83 84 describe("navigation", () => { 85 beforeEach(async () => { 86 await pathSelector.show(mockStartPath, true); 87 }); 88 89 it("moves up one directory", () => { 90 pathSelector.upOne(); 91 expect(pathSelector.currentPath).toEqual(["/", "home"]); 92 }); 93 94 it("shows down directory navigation", async () => { 95 mockFsService.readDir.mockReturnValue(["subdir1", "subdir2"]); 96 mockFsService.isDir.mockReturnValue(true); 97 98 await pathSelector.downOne(); 99 100 expect(mockUiService.askMultipleChoice).toHaveBeenCalled(); 101 expect(mockFsService.readDir).toHaveBeenCalledWith(mockStartPath); 102 }); 103 104 it("handles non-existing paths", async () => { 105 mockFsService.readDir.mockImplementationOnce(() => { 106 throw new Error("A!"); 107 }); 108 109 await pathSelector.downOne(); 110 111 expect(mockUiService.showInfoMessage).toHaveBeenCalledWith( 112 "There are no subdirectories here.", 113 ); 114 }); 115 116 it("can navigate to a subdirectory", async () => { 117 const subdir = "subdir1"; 118 mockFsService.readDir.mockReturnValue([subdir]); 119 mockUiService.askMultipleChoice.mockImplementation((_, options) => { 120 options[0].action(); // Select the first option 121 }); 122 await pathSelector.downOne(); 123 124 expect(pathSelector.currentPath).toEqual(["/", "home", "user", subdir]); 125 }); 126 127 it("creates new subdirectory", async () => { 128 const newDir = "newdir"; 129 mockUiService.askPrompt.mockResolvedValue(newDir); 130 await pathSelector.createSubDir(); 131 132 expect(mockUiService.askPrompt).toHaveBeenCalledWith("Enter name:"); 133 expect(mockFsService.makeDir).toHaveBeenCalled( 134 mockStartPath + "/" + newDir, 135 ); 136 expect(pathSelector.currentPath).toEqual(["/", "home", "user", newDir]); 137 }); 138 }); 139 140 describe("path validation", () => { 141 beforeEach(async () => { 142 await pathSelector.show(mockStartPath, true); 143 }); 144 145 it("validates root paths", () => { 146 expect(pathSelector.hasValidRoot(["/home"])).toBe(true); 147 expect(pathSelector.hasValidRoot([])).toBe(false); 148 expect(pathSelector.hasValidRoot(["invalid"])).toBe(false); 149 }); 150 151 it("validates full paths", () => { 152 mockFsService.isDir.mockReturnValue(false); 153 pathSelector.updateCurrentIfValidFull("/invalid/path"); 154 expect(mockUiService.showErrorMessage).toHaveBeenCalledWith( 155 "The path does not exist.", 156 ); 157 }); 158 }); 159 160 describe("selection and cancellation", () => { 161 beforeEach(async () => { 162 await pathSelector.show(mockStartPath, true); 163 }); 164 165 it("selects current path", async () => { 166 pathSelector.upOne(); 167 await pathSelector.selectThisPath(); 168 expect(pathSelector.resultingPath).toBe("/home"); 169 expect(mockMenuLoop.stopLoop).toHaveBeenCalled(); 170 }); 171 172 it("cancels and returns to starting path", async () => { 173 pathSelector.upOne(); 174 await pathSelector.cancel(); 175 expect(pathSelector.resultingPath).toBe(mockStartPath); 176 expect(mockMenuLoop.stopLoop).toHaveBeenCalled(); 177 }); 178 }); 179 });