/ src / utils / pathSelector.test.js
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  });