story-kit: merge 113_story_add_test_coverage_for_usepathcompletion_hook
This commit is contained in:
@@ -157,4 +157,305 @@ describe("usePathCompletion hook", () => {
|
|||||||
|
|
||||||
expect(setPathInput).toHaveBeenCalledWith("/home/user/Documents/");
|
expect(setPathInput).toHaveBeenCalledWith("/home/user/Documents/");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("uses homeDir when input has no slash (bare partial)", async () => {
|
||||||
|
mockListDir.mockResolvedValue([
|
||||||
|
{ name: "Documents", kind: "dir" },
|
||||||
|
{ name: "Downloads", kind: "dir" },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
usePathCompletion({
|
||||||
|
pathInput: "Doc",
|
||||||
|
setPathInput: vi.fn(),
|
||||||
|
homeDir: "/home/user",
|
||||||
|
listDirectoryAbsolute: mockListDir,
|
||||||
|
debounceMs: 0,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(result.current.matchList.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockListDir).toHaveBeenCalledWith("/home/user");
|
||||||
|
expect(result.current.matchList[0].name).toBe("Documents");
|
||||||
|
expect(result.current.matchList[0].path).toBe("/home/user/Documents/");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns early when input has no slash and homeDir is null", async () => {
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
usePathCompletion({
|
||||||
|
pathInput: "Doc",
|
||||||
|
setPathInput: vi.fn(),
|
||||||
|
homeDir: null,
|
||||||
|
listDirectoryAbsolute: mockListDir,
|
||||||
|
debounceMs: 0,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Wait for debounce + effect to fire
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(result.current.matchList).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockListDir).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns empty matchList when no dirs match the fuzzy filter", async () => {
|
||||||
|
mockListDir.mockResolvedValue([
|
||||||
|
{ name: "Documents", kind: "dir" },
|
||||||
|
{ name: "Downloads", kind: "dir" },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
usePathCompletion({
|
||||||
|
pathInput: "/home/user/zzz",
|
||||||
|
setPathInput: vi.fn(),
|
||||||
|
homeDir: "/home/user",
|
||||||
|
listDirectoryAbsolute: mockListDir,
|
||||||
|
debounceMs: 0,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockListDir).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
// No dirs match "zzz" fuzzy filter, so matchList stays empty
|
||||||
|
expect(result.current.matchList).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sets completionError when listDirectoryAbsolute throws an Error", async () => {
|
||||||
|
mockListDir.mockRejectedValue(new Error("Permission denied"));
|
||||||
|
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
usePathCompletion({
|
||||||
|
pathInput: "/root/",
|
||||||
|
setPathInput: vi.fn(),
|
||||||
|
homeDir: null,
|
||||||
|
listDirectoryAbsolute: mockListDir,
|
||||||
|
debounceMs: 0,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(result.current.completionError).toBe("Permission denied");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sets generic completionError when listDirectoryAbsolute throws a non-Error", async () => {
|
||||||
|
mockListDir.mockRejectedValue("some string error");
|
||||||
|
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
usePathCompletion({
|
||||||
|
pathInput: "/root/",
|
||||||
|
setPathInput: vi.fn(),
|
||||||
|
homeDir: null,
|
||||||
|
listDirectoryAbsolute: mockListDir,
|
||||||
|
debounceMs: 0,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(result.current.completionError).toBe(
|
||||||
|
"Failed to compute suggestion.",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("clears suggestionTail when selected match path does not start with input", async () => {
|
||||||
|
mockListDir.mockResolvedValue([{ name: "Documents", kind: "dir" }]);
|
||||||
|
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
usePathCompletion({
|
||||||
|
pathInput: "Doc",
|
||||||
|
setPathInput: vi.fn(),
|
||||||
|
homeDir: "/home/user",
|
||||||
|
listDirectoryAbsolute: mockListDir,
|
||||||
|
debounceMs: 0,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Wait for matches to load (path will be /home/user/Documents/)
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(result.current.matchList.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
// The match path is "/home/user/Documents/" which does NOT start with "Doc"
|
||||||
|
// so suggestionTail should be ""
|
||||||
|
expect(result.current.suggestionTail).toBe("");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("acceptSelectedMatch calls setPathInput with the selected match path", async () => {
|
||||||
|
mockListDir.mockResolvedValue([
|
||||||
|
{ name: "Documents", kind: "dir" },
|
||||||
|
{ name: "Downloads", kind: "dir" },
|
||||||
|
]);
|
||||||
|
const setPathInput = vi.fn();
|
||||||
|
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
usePathCompletion({
|
||||||
|
pathInput: "/home/user/",
|
||||||
|
setPathInput,
|
||||||
|
homeDir: "/home/user",
|
||||||
|
listDirectoryAbsolute: mockListDir,
|
||||||
|
debounceMs: 0,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(result.current.matchList.length).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
result.current.acceptSelectedMatch();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(setPathInput).toHaveBeenCalledWith("/home/user/Documents/");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("acceptSelectedMatch does nothing when matchList is empty", () => {
|
||||||
|
const setPathInput = vi.fn();
|
||||||
|
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
usePathCompletion({
|
||||||
|
pathInput: "",
|
||||||
|
setPathInput,
|
||||||
|
homeDir: "/home/user",
|
||||||
|
listDirectoryAbsolute: mockListDir,
|
||||||
|
debounceMs: 0,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
result.current.acceptSelectedMatch();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(setPathInput).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("closeSuggestions clears matchList, selectedMatch, suggestionTail, and completionError", async () => {
|
||||||
|
mockListDir.mockResolvedValue([{ name: "Documents", kind: "dir" }]);
|
||||||
|
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
usePathCompletion({
|
||||||
|
pathInput: "/home/user/",
|
||||||
|
setPathInput: vi.fn(),
|
||||||
|
homeDir: "/home/user",
|
||||||
|
listDirectoryAbsolute: mockListDir,
|
||||||
|
debounceMs: 0,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(result.current.matchList.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
result.current.closeSuggestions();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.current.matchList).toEqual([]);
|
||||||
|
expect(result.current.selectedMatch).toBe(0);
|
||||||
|
expect(result.current.suggestionTail).toBe("");
|
||||||
|
expect(result.current.completionError).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses homeDir with trailing slash as-is", async () => {
|
||||||
|
mockListDir.mockResolvedValue([{ name: "Projects", kind: "dir" }]);
|
||||||
|
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
usePathCompletion({
|
||||||
|
pathInput: "Pro",
|
||||||
|
setPathInput: vi.fn(),
|
||||||
|
homeDir: "/home/user/",
|
||||||
|
listDirectoryAbsolute: mockListDir,
|
||||||
|
debounceMs: 0,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(result.current.matchList.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockListDir).toHaveBeenCalledWith("/home/user");
|
||||||
|
expect(result.current.matchList[0].path).toBe("/home/user/Projects/");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles root directory listing (dir = '/')", async () => {
|
||||||
|
mockListDir.mockResolvedValue([
|
||||||
|
{ name: "home", kind: "dir" },
|
||||||
|
{ name: "etc", kind: "dir" },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
usePathCompletion({
|
||||||
|
pathInput: "/",
|
||||||
|
setPathInput: vi.fn(),
|
||||||
|
homeDir: null,
|
||||||
|
listDirectoryAbsolute: mockListDir,
|
||||||
|
debounceMs: 0,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(result.current.matchList.length).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockListDir).toHaveBeenCalledWith("/");
|
||||||
|
expect(result.current.matchList[0].name).toBe("etc");
|
||||||
|
expect(result.current.matchList[1].name).toBe("home");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("computes suggestionTail when match path starts with trimmed input", async () => {
|
||||||
|
mockListDir.mockResolvedValue([{ name: "Documents", kind: "dir" }]);
|
||||||
|
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
usePathCompletion({
|
||||||
|
pathInput: "/home/user/",
|
||||||
|
setPathInput: vi.fn(),
|
||||||
|
homeDir: "/home/user",
|
||||||
|
listDirectoryAbsolute: mockListDir,
|
||||||
|
debounceMs: 0,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(result.current.matchList.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
// path is "/home/user/Documents/" and input is "/home/user/"
|
||||||
|
// so tail should be "Documents/"
|
||||||
|
expect(result.current.suggestionTail).toBe("Documents/");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("setSelectedMatch updates the selected index", async () => {
|
||||||
|
mockListDir.mockResolvedValue([
|
||||||
|
{ name: "Documents", kind: "dir" },
|
||||||
|
{ name: "Downloads", kind: "dir" },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
usePathCompletion({
|
||||||
|
pathInput: "/home/user/",
|
||||||
|
setPathInput: vi.fn(),
|
||||||
|
homeDir: "/home/user",
|
||||||
|
listDirectoryAbsolute: mockListDir,
|
||||||
|
debounceMs: 0,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(result.current.matchList.length).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
result.current.setSelectedMatch(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.current.selectedMatch).toBe(1);
|
||||||
|
// After selecting index 1, suggestionTail should reflect "Downloads/"
|
||||||
|
expect(result.current.suggestionTail).toBe("Downloads/");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user