Something went wrong. Try again.
[mirror] A local-first desktop markdown writer with grammar checking, at proto integration, and more github.com/stormlightlabs/writer/releases
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335import { useWorkspaceController } from "$hooks/controllers/useWorkspaceController";import { dirList, dirMove, docDelete, docList, docMove, docRename, runCmd, sessionDropDoc, sessionGet, sessionPruneLocations, sessionUpdateTabDoc,} from "$ports";import { resetAppStore, useAppStore } from "$state/stores/app";import { useTabsStore } from "$state/stores/tabs";import { useWorkspaceStore } from "$state/stores/workspace";import { act, renderHook } from "@testing-library/react";import { beforeEach, describe, expect, it, vi } from "vitest";
vi.mock( "$ports", () => ({ runCmd: vi.fn(async () => {}), docList: vi.fn((_locationId: number, _onOk: (docs: unknown[]) => void, _onErr: (error: unknown) => void) => ({ type: "None", })), dirList: vi.fn((_locationId: number, _onOk: (dirs: string[]) => void, _onErr: (error: unknown) => void) => ({ type: "None", })), dirMove: vi.fn(() => ({ type: "None" })), docDelete: vi.fn(() => ({ type: "None" })), docMove: vi.fn(() => ({ type: "None" })), docRename: vi.fn(() => ({ type: "None" })), docSave: vi.fn(() => ({ type: "None" })), locationAddViaDialog: vi.fn(() => ({ type: "None" })), locationList: vi.fn(() => ({ type: "None" })), locationRemove: vi.fn(() => ({ type: "None" })), sessionGet: vi.fn((_onOk: (session: { tabs: unknown[]; activeTabId: string | null }) => void) => ({ type: "None", })), sessionPruneLocations: vi.fn(() => ({ type: "None" })), sessionOpenTab: vi.fn(() => ({ type: "None" })), sessionSelectTab: vi.fn(() => ({ type: "None" })), sessionCloseTab: vi.fn(() => ({ type: "None" })), sessionReorderTabs: vi.fn(() => ({ type: "None" })), sessionMarkTabModified: vi.fn(() => ({ type: "None" })), sessionUpdateTabDoc: vi.fn(() => ({ type: "None" })), sessionDropDoc: vi.fn(() => ({ type: "None" })), }),);
describe("useWorkspaceController", () => { beforeEach(() => { vi.clearAllMocks(); resetAppStore(); useAppStore.getState().setLocations([{ id: 1, name: "Workspace", root_path: "/workspace", added_at: "2024-01-01", }]); });
it("ignores non-numeric locationId values in handleCreateNewDocument", () => { const { result } = renderHook(() => useWorkspaceController());
let createdRef; act(() => { createdRef = result.current.handleCreateNewDocument({ type: "click" } as unknown as number); });
expect(createdRef).toMatchObject({ location_id: 1 }); expect(sessionGet).toHaveBeenCalledOnce(); expect(sessionPruneLocations).not.toHaveBeenCalled(); });
it("ignores non-numeric locationId values in handleRefreshSidebar", () => { const { result } = renderHook(() => useWorkspaceController());
act(() => { result.current.handleRefreshSidebar({ type: "click" } as unknown as number); });
expect(docList).toHaveBeenCalledWith(1, expect.any(Function), expect.any(Function)); expect(dirList).toHaveBeenCalledWith(1, expect.any(Function), expect.any(Function)); expect(runCmd).toHaveBeenCalled(); });
it("seeds directory cache when refresh returns no directories", () => { vi.mocked(dirList).mockImplementation((_locationId, onOk) => { onOk([]); return { type: "None" }; }); vi.mocked(docList).mockImplementation((locationId, onOk) => { onOk([{ location_id: locationId, rel_path: "notes.md", title: "Notes", updated_at: "2024-01-01T00:00:00Z", word_count: 1, }]); return { type: "None" }; });
const { result } = renderHook(() => useWorkspaceController());
act(() => { result.current.handleRefreshSidebar(1); });
const state = useWorkspaceStore.getState(); expect(Object.prototype.hasOwnProperty.call(state.directoriesByLocation, 1)).toBeTruthy(); expect(state.directoriesByLocation[1]).toStrictEqual([]); });
it("does not prune hydrated session tabs until locations finish loading", () => { useTabsStore.setState({ tabs: [{ id: "tab-1", docRef: { location_id: 1, rel_path: "notes/today.md" }, title: "Today", isModified: false, }], activeTabId: "tab-1", isSessionHydrated: true, }); useWorkspaceStore.setState({ locations: [useAppStore.getState().locations[0]], selectedLocationId: 1, isLoadingLocations: true, });
renderHook(() => useWorkspaceController());
expect(sessionPruneLocations).not.toHaveBeenCalled(); });
it("prunes hydrated session tabs after locations finish loading", () => { useTabsStore.setState({ tabs: [{ id: "tab-1", docRef: { location_id: 1, rel_path: "notes/today.md" }, title: "Today", isModified: false, }], activeTabId: "tab-1", isSessionHydrated: true, }); useWorkspaceStore.setState({ locations: [useAppStore.getState().locations[0]], selectedLocationId: 1, isLoadingLocations: false, });
renderHook(() => useWorkspaceController());
expect(sessionPruneLocations).toHaveBeenCalledWith([1], expect.any(Function), expect.any(Function)); });
it("does not patch documents in JS after rename", async () => { const originalDoc = { location_id: 1, rel_path: "draft.md", title: "Draft", updated_at: "2024-01-01T00:00:00Z", word_count: 10, }; useAppStore.getState().setDocuments([originalDoc]); vi.mocked(docRename).mockImplementation((_locationId, _relPath, _newName, onOk) => { onOk({ ...originalDoc, rel_path: "renamed.md", title: "Renamed" }); return { type: "None" }; });
const { result } = renderHook(() => useWorkspaceController());
const renamed = await act(async () => { return await result.current.handleRenameDocument(1, "draft.md", "renamed.md"); });
expect(renamed).toBeTruthy(); expect(useAppStore.getState().documents).toStrictEqual([originalDoc]); expect(sessionUpdateTabDoc).toHaveBeenCalledWith( 1, "draft.md", { location_id: 1, rel_path: "renamed.md" }, "Renamed", expect.any(Function), expect.any(Function), ); });
it("does not patch documents in JS after delete", async () => { const originalDoc = { location_id: 1, rel_path: "delete-me.md", title: "Delete me", updated_at: "2024-01-01T00:00:00Z", word_count: 8, }; useAppStore.getState().setDocuments([originalDoc]); vi.mocked(docDelete).mockImplementation((_locationId, _relPath, onOk) => { onOk(true); return { type: "None" }; });
const { result } = renderHook(() => useWorkspaceController());
const deleted = await act(async () => { return await result.current.handleDeleteDocument(1, "delete-me.md"); });
expect(deleted).toBeTruthy(); expect(useAppStore.getState().documents).toStrictEqual([originalDoc]); expect(sessionDropDoc).toHaveBeenCalledWith(1, "delete-me.md", expect.any(Function), expect.any(Function)); });
it("updates session tab location when moving a document across locations", async () => { vi.mocked(docMove).mockImplementation((_locationId, _relPath, _newRelPath, onOk) => { onOk({ location_id: 2, rel_path: "archive/moved.md", title: "Moved", updated_at: "2024-01-01T00:00:00Z", word_count: 3, }); return { type: "None" }; });
const { result } = renderHook(() => useWorkspaceController());
const moved = await act(async () => { return await result.current.handleMoveDocument(1, "draft.md", "archive/moved.md", 2); });
expect(moved).toBeTruthy(); expect(docMove).toHaveBeenCalledWith( 1, "draft.md", "archive/moved.md", expect.any(Function), expect.any(Function), 2, ); expect(sessionUpdateTabDoc).toHaveBeenCalledWith( 1, "draft.md", { location_id: 2, rel_path: "archive/moved.md" }, "Moved", expect.any(Function), expect.any(Function), ); });
it("updates open tabs under a moved directory", async () => { useTabsStore.setState({ tabs: [{ id: "tab-1", docRef: { location_id: 1, rel_path: "Samples/some-file.md" }, title: "Some File", isModified: false, }, { id: "tab-2", docRef: { location_id: 1, rel_path: "Samples/nested/deeper.md" }, title: "Deeper", isModified: false, }], activeTabId: "tab-1", isSessionHydrated: true, }); vi.mocked(dirMove).mockImplementation((_locationId, _relPath, _newRelPath, onOk) => { onOk("Archive/Samples"); return { type: "None" }; });
const { result } = renderHook(() => useWorkspaceController());
const moved = await act(async () => { return await result.current.handleMoveDirectory(1, "Samples", "Archive/Samples"); });
expect(moved).toBeTruthy(); expect(sessionUpdateTabDoc).toHaveBeenCalledWith( 1, "Samples/some-file.md", { location_id: 1, rel_path: "Archive/Samples/some-file.md" }, "Some File", expect.any(Function), expect.any(Function), ); expect(sessionUpdateTabDoc).toHaveBeenCalledWith( 1, "Samples/nested/deeper.md", { location_id: 1, rel_path: "Archive/Samples/nested/deeper.md" }, "Deeper", expect.any(Function), expect.any(Function), ); });
it("updates open tabs with target location for cross-location directory moves", async () => { useTabsStore.setState({ tabs: [{ id: "tab-1", docRef: { location_id: 1, rel_path: "Samples/some-file.md" }, title: "Some File", isModified: false, }], activeTabId: "tab-1", isSessionHydrated: true, }); vi.mocked(dirMove).mockImplementation((_locationId, _relPath, _newRelPath, onOk, _onErr, targetLocationId) => { expect(targetLocationId).toBe(2); onOk("Imported/Samples"); return { type: "None" }; });
const { result } = renderHook(() => useWorkspaceController());
const moved = await act(async () => { return await result.current.handleMoveDirectory(1, "Samples", "Imported/Samples", 2); });
expect(moved).toBeTruthy(); expect(sessionUpdateTabDoc).toHaveBeenCalledWith( 1, "Samples/some-file.md", { location_id: 2, rel_path: "Imported/Samples/some-file.md" }, "Some File", expect.any(Function), expect.any(Function), ); });});