import { fireEvent, render, screen } from "@testing-library/react"; import { beforeAll, describe, expect, test, vi } from "vitest"; import { createInitialState, EditorProvider } from "@/components/MemoEditor/state"; import { EditorToolbar } from "@/components/MemoEditor/Toolbar/EditorToolbar"; import InsertMenu from "@/components/MemoEditor/Toolbar/InsertMenu"; vi.mock("@/utils/i18n", async (importOriginal) => ({ ...(await importOriginal()), useTranslate: () => (key: string) => key, })); vi.mock("@/hooks/useCurrentUser", () => ({ default: () => undefined })); vi.mock("@/contexts/AuthContext", () => ({ useAuth: () => ({ userGeneralSetting: undefined }) })); vi.mock("@/contexts/SpaceContext", () => ({ useSpaceContext: () => ({ selectedSpaceName: undefined }) })); vi.mock("@/components/map/useReverseGeocoding", () => ({ useReverseGeocoding: () => ({ data: undefined }) })); beforeAll(() => { Element.prototype.scrollIntoView = vi.fn(); Element.prototype.hasPointerCapture = vi.fn(() => false); Element.prototype.setPointerCapture = vi.fn(); Element.prototype.releasePointerCapture = vi.fn(); }); const viewToggles = { onToggleFocusMode: vi.fn(), isFormattingToolbarVisible: false, onToggleFormattingToolbar: vi.fn(), }; /** `hosted` mirrors an editor whose presentation belongs to a host (the global composer). */ const renderMenu = (onInsertImages = vi.fn(), isSaving = false, hosted = false) => render( , ); describe("InsertMenu", () => { test("shows attachment and inline-image actions in the intended order", () => { renderMenu(); const trigger = screen.getByRole("button", { name: "common.add" }); expect(trigger).toHaveAttribute("tabindex", "0"); fireEvent.click(trigger); expect(screen.getAllByRole("menuitem").map((item) => item.textContent)).toEqual([ "editor.insert-menu.add-attachment", "editor.insert-menu.insert-image", "editor.audio-recorder.trigger", "editor.insert-menu.link-memo", "editor.insert-menu.add-location", "editor.focus-mode", "editor.formatting-toolbar", ]); }); test("drops the view toggles when a host owns the editor's presentation", () => { renderMenu(vi.fn(), false, true); fireEvent.click(screen.getByRole("button", { name: "common.add" })); const labels = screen.getAllByRole("menuitem").map((item) => item.textContent); expect(labels).not.toContain("editor.focus-mode"); expect(labels).not.toContain("editor.formatting-toolbar"); expect(screen.queryByRole("separator")).not.toBeInTheDocument(); }); test("uses separate unrestricted and multi-image file inputs", () => { const onInsertImages = vi.fn(); const { container } = renderMenu(onInsertImages); const inputs = Array.from(container.querySelectorAll('input[type="file"]')); const attachmentInput = inputs.find((input) => input.accept === ""); const inlineImageInput = inputs.find((input) => input.accept === "image/*"); expect(attachmentInput).toBeDefined(); expect(attachmentInput).toHaveAttribute("multiple"); expect(inlineImageInput).toBeDefined(); expect(inlineImageInput).toHaveAttribute("multiple"); const image = new File(["image"], "photo.png", { type: "image/png" }); fireEvent.change(inlineImageInput!, { target: { files: [image] } }); expect(onInsertImages).toHaveBeenCalledWith([image]); }); test("disables insertion controls while saving", () => { const { container } = renderMenu(vi.fn(), true); expect(screen.getByRole("button", { name: "common.add" })).toBeDisabled(); for (const input of container.querySelectorAll('input[type="file"]')) { expect(input).toBeDisabled(); } }); test("exposes a localized save-blocking reason from a focusable wrapper", () => { const state = createInitialState(); state.content = "memo"; state.ui.pendingInlineImageInsertions = 1; render( , ); expect(screen.getByRole("button", { name: "editor.save" })).toBeDisabled(); expect(screen.getByLabelText("editor.validation.resolve-image-uploads")).toHaveAttribute("tabindex", "0"); }); });