homeio
PublikFork dari doctor-io/homeio
useFiles.test.tsx
/* @vitest-environment jsdom */
import { act, renderHook, waitFor } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import {
buildAssetUrl,
toFilePath,
useCreateFile,
useCreateFolder,
useFileContent,
useFilesDirectory,
usePasteFileEntry,
useSaveFileContent,
} from "@/modules/files/hooks/useFiles";
import { queryKeys } from "@/lib/shared/query-keys";
import { createTestQueryClient, createWrapper } from "@/test/query-client-wrapper";
describe("useFiles hooks", () => {
it("loads directory entries from files API", async () => {
const fetchMock = vi.fn().mockResolvedValueOnce({
ok: true,
json: async () => ({
data: {
root: "/",
cwd: "Documents",
entries: [],
},
meta: {
count: 0,
},
}),
});
vi.stubGlobal("fetch", fetchMock);
const client = createTestQueryClient();
const { result } = renderHook(() => useFilesDirectory(["Documents"]), {
wrapper: createWrapper(client),
});
await waitFor(() => {
expect(result.current.isSuccess).toBe(true);
});
expect(fetchMock).toHaveBeenCalledWith("/api/v1/files?path=Documents", {
cache: "no-store",
});
});
it("loads file content by path", async () => {
const fetchMock = vi.fn().mockResolvedValueOnce({
ok: true,
json: async () => ({
data: {
root: "/",
path: "Documents/notes.txt",
name: "notes.txt",
ext: "txt",
mode: "text",
mimeType: "text/plain; charset=utf-8",
sizeBytes: 6,
modifiedAt: "2026-02-26T12:00:00.000Z",
mtimeMs: 11,
content: "hello!",
},
}),
});
vi.stubGlobal("fetch", fetchMock);
const client = createTestQueryClient();
const { result } = renderHook(
() => useFileContent("Documents/notes.txt"),
{
wrapper: createWrapper(client),
},
);
await waitFor(() => {
expect(result.current.isSuccess).toBe(true);
});
expect(fetchMock).toHaveBeenCalledWith(
"/api/v1/files/content?path=Documents%2Fnotes.txt",
{
cache: "no-store",
},
);
});
it("saves file content and invalidates related queries", async () => {
const fetchMock = vi.fn().mockResolvedValueOnce({
ok: true,
json: async () => ({
data: {
root: "/",
path: "Documents/notes.txt",
sizeBytes: 7,
modifiedAt: "2026-02-26T12:00:05.000Z",
mtimeMs: 22,
},
}),
});
vi.stubGlobal("fetch", fetchMock);
const client = createTestQueryClient();
const invalidateSpy = vi.spyOn(client, "invalidateQueries");
const { result } = renderHook(() => useSaveFileContent(), {
wrapper: createWrapper(client),
});
await act(async () => {
await result.current.mutateAsync({
path: "Documents/notes.txt",
content: "updated",
expectedMtimeMs: 11,
});
});
expect(fetchMock).toHaveBeenCalledWith("/api/v1/files/content", {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
path: "Documents/notes.txt",
content: "updated",
expectedMtimeMs: 11,
}),
});
expect(invalidateSpy).toHaveBeenCalledWith({
queryKey: queryKeys.fileContent("Documents/notes.txt"),
});
expect(invalidateSpy).toHaveBeenCalledWith({
queryKey: ["files", "list", "Documents"],
});
});
it("exports path helpers", () => {
expect(toFilePath(["Documents", "notes.txt"])).toBe("Documents/notes.txt");
expect(toFilePath(["Trash", "notes.txt"])).toBe("Trash/notes.txt");
expect(buildAssetUrl("Media/photo.png")).toBe(
"/api/v1/files/asset?path=Media%2Fphoto.png",
);
});
it("creates a folder via files ops endpoint", async () => {
const fetchMock = vi.fn().mockResolvedValueOnce({
ok: true,
json: async () => ({
data: {
root: "/",
path: "Documents/New Folder",
type: "folder",
},
}),
});
vi.stubGlobal("fetch", fetchMock);
const client = createTestQueryClient();
const invalidateSpy = vi.spyOn(client, "invalidateQueries");
const { result } = renderHook(() => useCreateFolder(), {
wrapper: createWrapper(client),
});
await act(async () => {
await result.current.mutateAsync({
parentPath: "Documents",
name: "New Folder",
});
});
expect(fetchMock).toHaveBeenCalledWith("/api/v1/files/ops", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
action: "create_folder",
parentPath: "Documents",
name: "New Folder",
}),
});
expect(invalidateSpy).toHaveBeenCalledWith({
queryKey: ["files", "list", "Documents"],
});
});
it("creates a file via files ops endpoint", async () => {
const fetchMock = vi.fn().mockResolvedValueOnce({
ok: true,
json: async () => ({
data: {
root: "/",
path: "Documents/notes.txt",
type: "file",
},
}),
});
vi.stubGlobal("fetch", fetchMock);
const client = createTestQueryClient();
const { result } = renderHook(() => useCreateFile(), {
wrapper: createWrapper(client),
});
await act(async () => {
await result.current.mutateAsync({
parentPath: "Documents",
name: "notes.txt",
});
});
expect(fetchMock).toHaveBeenCalledWith("/api/v1/files/ops", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
action: "create_file",
parentPath: "Documents",
name: "notes.txt",
}),
});
});
it("pastes copied entries via files ops endpoint", async () => {
const fetchMock = vi.fn().mockResolvedValueOnce({
ok: true,
json: async () => ({
data: {
root: "/",
path: "Documents/photo.jpg",
type: "file",
},
}),
});
vi.stubGlobal("fetch", fetchMock);
const client = createTestQueryClient();
const invalidateSpy = vi.spyOn(client, "invalidateQueries");
const { result } = renderHook(() => usePasteFileEntry(), {
wrapper: createWrapper(client),
});
await act(async () => {
await result.current.mutateAsync({
sourcePath: "Media/photo.jpg",
destinationPath: "Documents",
operation: "copy",
});
});
expect(fetchMock).toHaveBeenCalledWith("/api/v1/files/ops", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
action: "paste",
sourcePath: "Media/photo.jpg",
destinationPath: "Documents",
operation: "copy",
}),
});
expect(invalidateSpy).toHaveBeenCalledWith({
queryKey: ["files", "list", "Documents"],
});
expect(invalidateSpy).toHaveBeenCalledWith({
queryKey: ["files", "list", "Media"],
});
});
});