import { describe, it, expect, vi, beforeEach } from "vitest";
import { render } from "@testing-library/react";
const mockFocus = vi.hoisted(() => vi.fn());
const mockSetContent = vi.hoisted(() => vi.fn());
const mockBlur = vi.hoisted(() => vi.fn());
const editorState = vi.hoisted(() => ({
isFocused: false,
isDestroyed: false,
text: "",
}));
vi.mock("../i18n", () => ({
useT: () => ({ t: (fn: unknown) => (typeof fn === "function" ? "" : "") }),
}));
const editorRef = vi.hoisted<{ current: unknown }>(() => ({ current: null }));
vi.mock("@tiptap/react", () => ({
useEditor: () => {
if (!editorRef.current) {
editorRef.current = {
get isFocused() {
return editorState.isFocused;
},
get isDestroyed() {
return editorState.isDestroyed;
},
commands: {
focus: mockFocus,
blur: mockBlur,
setContent: mockSetContent,
},
getText: () => editorState.text,
};
}
return editorRef.current;
},
EditorContent: () =>
,
}));
import { createShortcutChord } from "@multica/core/shortcuts";
import { TitleEditor, titleShortcutSubmitAllowed } from "./title-editor";
describe("TitleEditor", () => {
beforeEach(() => {
vi.clearAllMocks();
editorState.isFocused = false;
editorState.isDestroyed = false;
editorState.text = "";
editorRef.current = null;
});
it("syncs editor content when defaultValue changes externally and editor is unfocused", () => {
editorState.text = "old title";
const { rerender } = render();
expect(mockSetContent).not.toHaveBeenCalled();
rerender();
expect(mockSetContent).toHaveBeenCalledTimes(1);
expect(mockSetContent).toHaveBeenCalledWith(
{
type: "doc",
content: [
{
type: "paragraph",
content: [{ type: "text", text: "new title from server" }],
},
],
},
{ emitUpdate: false },
);
});
it("does not overwrite the user's in-flight edits when the editor is focused and dirty", () => {
editorState.text = "old title";
const { rerender } = render();
editorState.isFocused = true;
editorState.text = "user typed but not yet blurred";
rerender();
expect(mockSetContent).not.toHaveBeenCalled();
});
// Regression: a focused but clean editor (user clicked in but never typed)
// must still accept external updates, otherwise the subsequent blur would
// compare stale editor text to the new server value and silently roll the
// external update back.
it("syncs to new defaultValue when editor is focused but clean", () => {
editorState.text = "old title";
const { rerender } = render();
// User clicked into the title field but has not typed anything yet:
// editor text still equals the previous defaultValue.
editorState.isFocused = true;
editorState.text = "old title";
rerender();
expect(mockSetContent).toHaveBeenCalledTimes(1);
expect(mockSetContent).toHaveBeenCalledWith(
{
type: "doc",
content: [
{
type: "paragraph",
content: [{ type: "text", text: "new title from server" }],
},
],
},
{ emitUpdate: false },
);
});
it("short-circuits when editor text already equals incoming defaultValue", () => {
editorState.text = "same title";
const { rerender } = render();
// Force the effect to re-run by rendering with a different prop, then
// back to the same value. Even an identity-equal prop should be skipped.
rerender();
expect(mockSetContent).not.toHaveBeenCalled();
});
it("clears the editor when defaultValue transitions to empty", () => {
editorState.text = "old title";
const { rerender } = render();
rerender();
expect(mockSetContent).toHaveBeenCalledTimes(1);
expect(mockSetContent).toHaveBeenCalledWith("", { emitUpdate: false });
});
});
// MUL-4931 — which `send` bindings the title's shortcut-submit path honors.
describe("titleShortcutSubmitAllowed", () => {
it("allows the default Mod+Enter chord", () => {
expect(
titleShortcutSubmitAllowed(createShortcutChord("Enter", { primary: true })),
).toBe(true);
});
it("refuses plain Enter, which the single-line keymap already owns", () => {
// A user may bind `send` to plain Enter for chat. The title must not
// inherit it — Enter there means "done", and creating from a half-typed
// title is the misfire #5532 removed.
expect(titleShortcutSubmitAllowed(createShortcutChord("Enter"))).toBe(false);
});
it("refuses an unbound send action", () => {
expect(titleShortcutSubmitAllowed(null)).toBe(false);
});
});