import { create } from "@bufbuild/protobuf";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { MemoryRouter } from "react-router-dom";
import { beforeEach, describe, expect, it, vi } from "vitest";
import MemoViewIcon from "@/components/MemoViewIcon";
import MemoViews from "@/pages/MemoViews";
import { MemoView_IconSchema, MemoViewSchema } from "@/types/proto/api/v1/user_service_pb";
const mocks = vi.hoisted(() => ({ create: vi.fn(), update: vi.fn() }));
vi.mock("@/connect", () => ({ userServiceClient: { createMemoView: mocks.create, updateMemoView: mocks.update } }));
vi.mock("@/hooks/useCurrentUser", () => ({ default: () => ({ name: "users/steven" }) }));
vi.mock("@/hooks/useUserQueries", () => ({ useMemoViews: () => ({ data: [] }), userKeys: { memoViews: () => ["views"] } }));
vi.mock("@/contexts/MemoFilterContext", () => ({ useMemoFilterContext: () => ({ setMemoView: vi.fn() }) }));
vi.mock("@/utils/i18n", () => ({ useTranslate: () => (key: string) => key }));
const emoji = create(MemoView_IconSchema, { value: { case: "emoji", value: "🌱" } });
const leaf = create(MemoView_IconSchema, { value: { case: "lucide", value: "leaf" } });
const existingView = create(MemoViewSchema, { name: "users/steven/views/garden", title: "Garden", filter: "pinned", icon: emoji });
const iconMask = expect.objectContaining({ paths: ["title", "filter", "icon"] });
function renderPage(edit = false) {
return render(
,
);
}
beforeEach(() => {
vi.clearAllMocks();
mocks.create.mockResolvedValue({});
mocks.update.mockResolvedValue({});
});
describe("memo view icons", () => {
it("renders emoji and symbols with a view-specific fallback", () => {
const view = render();
expect(view.container.querySelector(".lucide-parentheses")).not.toBeNull();
view.rerender();
expect(view.container).toHaveTextContent("🌱");
view.rerender();
expect(view.container.querySelector(".lucide-leaf")).not.toBeNull();
view.rerender();
expect(view.container.querySelector(".lucide-parentheses")).not.toBeNull();
});
it("creates and validates a view with a selected icon", async () => {
renderPage();
fireEvent.change(screen.getByLabelText("common.title"), { target: { value: "Garden" } });
fireEvent.change(screen.getByLabelText("common.filter"), { target: { value: "pinned" } });
fireEvent.click(screen.getByRole("button", { name: "setting.memo-view.change-icon" }));
const search = await screen.findByRole("textbox", { name: "space.icon.search-icons" });
fireEvent.change(search, { target: { value: "leaf" } });
fireEvent.keyDown(search, { key: "Enter" });
fireEvent.click(screen.getByRole("button", { name: "Validate" }));
await waitFor(() =>
expect(mocks.create).toHaveBeenCalledWith(
expect.objectContaining({
validateOnly: true,
memoView: expect.objectContaining({ icon: leaf }),
}),
),
);
await waitFor(() => expect(screen.getByRole("button", { name: "common.save" })).toBeEnabled());
fireEvent.click(screen.getByRole("button", { name: "common.save" }));
await waitFor(() =>
expect(mocks.create).toHaveBeenLastCalledWith({
parent: "users/steven",
memoView: { name: "", title: "Garden", filter: "pinned", icon: leaf },
}),
);
});
it("preserves the icon when editing from the sidebar and applying an example", async () => {
renderPage(true);
expect(screen.getByRole("button", { name: "setting.memo-view.change-icon" })).toHaveTextContent("🌱");
fireEvent.click(screen.getByRole("button", { name: /Recent notes/ }));
fireEvent.click(screen.getByRole("button", { name: "common.save" }));
await waitFor(() =>
expect(mocks.update).toHaveBeenCalledWith(
expect.objectContaining({
memoView: expect.objectContaining({
name: existingView.name,
title: "Garden",
icon: emoji,
filter: 'created_ts >= now - duration("1h")',
}),
updateMask: iconMask,
}),
),
);
});
it("resets an existing icon using the icon update mask", async () => {
renderPage(true);
fireEvent.click(screen.getByRole("button", { name: "setting.memo-view.change-icon" }));
fireEvent.click(await screen.findByRole("button", { name: "space.icon.reset" }));
expect(screen.getByRole("button", { name: "setting.memo-view.change-icon" }).querySelector(".lucide-parentheses")).not.toBeNull();
fireEvent.click(screen.getByRole("button", { name: "common.save" }));
await waitFor(() =>
expect(mocks.update).toHaveBeenCalledWith(
expect.objectContaining({
memoView: expect.objectContaining({ icon: undefined }),
updateMask: iconMask,
}),
),
);
});
});