memos/web/tests/insert-menu-mode-toggle.test.tsx
boojack 1e3ec38fa1 refactor(editor): modularize formatting and simplify editor state
Behavior-preserving restructure of the MemoEditor subsystem for simplicity
and extensibility:

- Add a data-driven command catalog (editorCommands.ts) as the single source
  for formatting verbs; the toolbar, active-state hook, and WYSIWYG handle all
  derive from it, so adding a verb is a one-file change.
- Add a createSuggestionExtension factory; TagSuggestion consumes it, so new
  `/` or `@` triggers reuse the shared popup in ~10 lines.
- Collapse FormattingController into a single EditorController with an optional
  `formatting` capability; PlainEditor is an honest textarea fallback (no faked
  formatting); replace stringly-typed isActive with typed getActiveFormats.
- Move audio-recorder state out of the reducer into useAudioRecorder, keeping a
  single recorderBusy flag in the store.
- Convert the editor context to an external store (useSyncExternalStore) with
  per-slice subscriptions, so typing no longer re-renders the toolbar, insert
  menu, or metadata.
- Extract the shared #tag lexing grammar (tag-grammar.ts) used by both the
  editor tokenizer and the remark renderer.
- Remove dead reducer actions/cases and fix a preview blob-URL leak on the
  upload path.

Add tests for the command catalog, suggestion factory, and autosave.
2026-06-22 23:51:59 +08:00

71 lines
2.9 KiB
TypeScript

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { fireEvent, render, screen } from "@testing-library/react";
import { beforeAll, describe, expect, it, vi } from "vitest";
import { EditorProvider, useEditorSelector } from "@/components/MemoEditor/state";
import InsertMenu from "@/components/MemoEditor/Toolbar/InsertMenu";
// useTranslate returns the i18n key directly (no i18next backend in tests).
vi.mock("@/utils/i18n", () => ({ useTranslate: () => (key: string) => key }));
// The link/location dialogs drag in maps + heavy deps; they're closed here.
vi.mock("@/components/MemoMetadata", () => ({ LinkMemoDialog: () => null, LocationDialog: () => null }));
// useLinkMemo → useCurrentUser → useAuth would need an AuthProvider; stub the user.
vi.mock("@/hooks/useCurrentUser", () => ({ default: () => ({ name: "users/test" }) }));
// Radix DropdownMenu reaches for layout/pointer APIs jsdom doesn't implement.
beforeAll(() => {
Element.prototype.scrollIntoView = vi.fn();
Element.prototype.hasPointerCapture = vi.fn(() => false);
Element.prototype.setPointerCapture = vi.fn();
Element.prototype.releasePointerCapture = vi.fn();
});
function ModeProbe() {
const editorMode = useEditorSelector((s) => s.ui.editorMode);
return <span data-testid="mode">{editorMode}</span>;
}
function renderInsertMenu() {
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
render(
<QueryClientProvider client={client}>
<EditorProvider>
<InsertMenu onLocationChange={() => {}} />
<ModeProbe />
</EditorProvider>
</QueryClientProvider>,
);
}
// Opens the "+" dropdown, then its "More" submenu, and returns the
// "WYSIWYG Editor" checkbox item.
function openWysiwygCheckbox() {
// Open the dropdown (keyboard open is the most reliable path in jsdom).
fireEvent.keyDown(screen.getByRole("button"), { key: "Enter" });
// Open the "More" submenu (controlled open state toggles on trigger click).
fireEvent.click(screen.getByText("common.more"));
return screen.getByText("editor.wysiwyg-editor");
}
describe("InsertMenu editor-mode toggle", () => {
it("unchecking the WYSIWYG item switches to raw and persists the preference", () => {
localStorage.clear();
renderInsertMenu();
expect(screen.getByTestId("mode").textContent).toBe("wysiwyg");
fireEvent.click(openWysiwygCheckbox());
expect(screen.getByTestId("mode").textContent).toBe("raw");
expect(localStorage.getItem("memos-editor-mode")).toBe("raw");
});
it("checking the WYSIWYG item switches back to rich text from raw mode", () => {
localStorage.setItem("memos-editor-mode", "raw");
renderInsertMenu();
expect(screen.getByTestId("mode").textContent).toBe("raw");
fireEvent.click(openWysiwygCheckbox());
expect(screen.getByTestId("mode").textContent).toBe("wysiwyg");
expect(localStorage.getItem("memos-editor-mode")).toBe("wysiwyg");
});
});