Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,63 +3,18 @@ import "@testing-library/jest-dom";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

import { useWidgetStore } from "@/stores/widget-store";
import type { SetDirResult } from "@/types/shared";
import { DirectoryPicker } from "@/components/widget/controls/directory-picker";

// ---------------------------------------------------------------------------
// Mocks
// ---------------------------------------------------------------------------

const mockPickDir = vi.fn();
const mockPrepareNewDir = vi.fn<(p: string) => Promise<void>>();

vi.mock("@/hooks/use-darwin-config", () => ({
useDarwinConfig: () => ({
pickDir: async () => {
const result = await mockPickDir();
if (result) {
const { useWidgetStore } = await import("@/stores/widget-store");
const store = useWidgetStore.getState();
store.setConfigDir(result.dir);
store.setHosts(result.hosts ?? []);
}
return result;
},
setDir: async (p: string) => {
await mockSetDir(p);
const { useWidgetStore } = await import("@/stores/widget-store");
const store = useWidgetStore.getState();
store.setConfigDir(p);
store.setHost("");
try {
await mockSetHostAttr("");
} catch {}
try {
const hosts = await mockListHosts();
store.setHosts(hosts);
return { dir: p, evolveState: null, hosts };
} catch {
store.setHosts([]);
return { dir: p, evolveState: null, hosts: [] };
}
},
prepareNewDir: async (p: string) => {
await mockPrepareNewDir(p);
const { useWidgetStore } = await import("@/stores/widget-store");
const store = useWidgetStore.getState();
store.setConfigDir(p);
store.setHost("");
store.setHosts([]);
return { dir: p, evolveState: null, hosts: [] };
},
}),
}));

const mockNormalize = vi.fn<(p: string) => Promise<string | null>>();
const mockExists = vi.fn<(p: string) => Promise<boolean>>();
const mockSetDir = vi.fn<(p: string) => Promise<SetDirResult>>();
const mockSetDir =
vi.fn<(p: string) => Promise<{ dir: string; evolveState: never; hosts: string[] | null }>>();
const mockSetHostAttr = vi.fn<(h: string) => Promise<void>>();
const mockListHosts = vi.fn<() => Promise<string[]>>();
const mockFlakeExistsAt = vi.fn<(p: string) => Promise<boolean>>();
const mockFlakeExists = vi.fn<() => Promise<boolean>>();

Expand All @@ -75,7 +30,6 @@ vi.mock("@/tauri-api", () => ({
setHostAttr: (h: string) => mockSetHostAttr(h),
},
flake: {
listHosts: () => mockListHosts(),
existsAt: (p: string) => mockFlakeExistsAt(p),
exists: () => mockFlakeExists(),
},
Expand All @@ -96,7 +50,6 @@ function resetStore() {

function resetMocks() {
mockPickDir.mockReset();
mockPrepareNewDir.mockReset();
mockNormalize.mockReset();
mockExists.mockReset();
mockSetDir.mockReset();
Expand All @@ -106,7 +59,6 @@ function resetMocks() {

// Sensible "happy-path" defaults; individual tests override as needed.
mockNormalize.mockImplementation(async (p) => p.trim());
mockPrepareNewDir.mockResolvedValue();
mockExists.mockResolvedValue(true);
mockSetDir.mockImplementation(async (p) => ({
dir: p,
Expand All @@ -117,7 +69,6 @@ function resetMocks() {
mockFlakeExistsAt.mockResolvedValue(true);
mockFlakeExists.mockResolvedValue(true);
mockPickDir.mockResolvedValue(null);
mockListHosts.mockResolvedValue([]);
}

/** Type into the input then fire a blur event. Mirrors `userEvent.type` + tab-out
Expand Down Expand Up @@ -183,7 +134,6 @@ describe("<DirectoryPicker>", () => {
evolveState: {} as never,
hosts: ["mbp", "workbook"],
});
mockListHosts.mockResolvedValue(["mbp", "workbook"]);
useWidgetStore.getState().setHost("old-host");

render(<DirectoryPicker label="Config directory" />);
Expand All @@ -200,7 +150,6 @@ describe("<DirectoryPicker>", () => {
expect(mockExists).toHaveBeenCalledWith("/Users/me/.darwin");
expect(mockSetDir).toHaveBeenCalledWith("/Users/me/.darwin");
expect(mockSetHostAttr).toHaveBeenCalledWith("");
expect(mockListHosts).toHaveBeenCalledTimes(1);

const s = useWidgetStore.getState();
expect(s.configDir).toBe("/Users/me/.darwin");
Expand Down Expand Up @@ -264,49 +213,6 @@ describe("<DirectoryPicker>", () => {
expect(mockPickDir).toHaveBeenCalledTimes(1);
});

it("in setup flow, starts with New/Existing choices and creates a named directory", async () => {
const onConfigured = vi.fn();
mockNormalize.mockImplementation(async (p) => p === "~/.nixmac" ? "/Users/me/.nixmac" : p.trim());

render(<DirectoryPicker label="Config directory" flow="setup" onConfigured={onConfigured} />);

expect(screen.getByRole("tab", { name: "New" })).toBeInTheDocument();
expect(screen.getByRole("tab", { name: "Existing" })).toBeInTheDocument();

const nameInput = screen.getByLabelText("Config directory name");
fireEvent.change(nameInput, { target: { value: ".nixmac" } });
fireEvent.click(screen.getByRole("button", { name: /create/i }));

await waitFor(() => expect(mockPrepareNewDir).toHaveBeenCalledWith("/Users/me/.nixmac"));
expect(useWidgetStore.getState().configDir).toBe("/Users/me/.nixmac");
expect(onConfigured).toHaveBeenCalledTimes(1);
});

it("in setup flow, rejects path-like names for new directories", async () => {
render(<DirectoryPicker label="Config directory" flow="setup" />);

fireEvent.change(screen.getByLabelText("Config directory name"), {
target: { value: "configs/darwin" },
});
fireEvent.click(screen.getByRole("button", { name: /create/i }));

expect(await screen.findByText("Use a directory name, not a path")).toBeInTheDocument();
expect(mockPrepareNewDir).not.toHaveBeenCalled();
});

it("in setup flow, Existing keeps the browse-based selection path", async () => {
const onConfigured = vi.fn();
mockPickDir.mockResolvedValue({ dir: "/Users/me/config", evolveState: null, hosts: ["mbp"] });

render(<DirectoryPicker label="Config directory" flow="setup" onConfigured={onConfigured} />);
fireEvent.click(screen.getByRole("tab", { name: "Existing" }));
fireEvent.click(await screen.findByRole("button", { name: /browse/i }));

await waitFor(() => expect(mockPickDir).toHaveBeenCalledTimes(1));
expect(useWidgetStore.getState().configDir).toBe("/Users/me/config");
expect(onConfigured).toHaveBeenCalledTimes(1);
});

it("clears the validation message when configDir changes externally to a valid dir with a flake", async () => {
mockFlakeExistsAt.mockResolvedValue(true);

Expand Down
Loading