Skip to content
File

Blob: tests/client/components/editor/lib/page-mention/can-insert.test.ts

typescript83 lines
1import { describe, expect, it, vi } from "vitest";
2import type { Editor } from "@tiptap/core";
3import { Schema } from "@tiptap/pm/model";
4import { EditorState, TextSelection } from "@tiptap/pm/state";
5import { schema as basicSchema } from "prosemirror-schema-basic";
6import {
7 canInsertPageMentionAtRange,
8 canInsertPageMentions,
9} from "@/client/components/editor/lib/page-mention/can-insert";
10import { getSlashMenuItems, filterItems } from "@/client/components/editor/controllers/slash/items";
11 
12const schema = new Schema({
13 nodes: basicSchema.spec.nodes.addBefore("text", "pageMention", {
14 inline: true,
15 group: "inline",
16 atom: true,
17 selectable: true,
18 attrs: { pageId: { default: null } },
19 toDOM(node) {
20 return ["span", { "data-page-id": node.attrs.pageId ?? "" }];
21 },
22 parseDOM: [{ tag: "span[data-page-id]" }],
23 }),
24 marks: basicSchema.spec.marks,
25});
26 
27function createEditor(blockType: "paragraph" | "code_block"): Editor {
28 const content = schema.node(blockType, null, [schema.text("[[roadmap")]);
29 const doc = schema.node("doc", null, [content]);
30 const state = EditorState.create({
31 schema,
32 doc,
33 selection: TextSelection.create(doc, content.content.size + 1),
34 });
35 
36 return {
37 isEditable: true,
38 schema,
39 state,
40 } as unknown as Editor;
41}
42 
43function fullTextRange(editor: Editor) {
44 const contentSize = editor.state.doc.firstChild?.content.size ?? 0;
45 return { from: 1, to: contentSize + 1 };
46}
47 
48describe("page mention insertion availability", () => {
49 it("allows mention insertion in inline text blocks", () => {
50 const editor = createEditor("paragraph");
51 
52 expect(canInsertPageMentionAtRange(editor, fullTextRange(editor))).toBe(true);
53 });
54 
55 it("blocks mention insertion inside code blocks", () => {
56 const editor = createEditor("code_block");
57 
58 expect(canInsertPageMentionAtRange(editor, fullTextRange(editor))).toBe(false);
59 });
60 
61 it("hides the slash-menu Link page item when the current selection cannot accept a mention", () => {
62 const paragraphEditor = createEditor("paragraph");
63 const codeBlockEditor = createEditor("code_block");
64 const items = getSlashMenuItems({
65 pageMention: { openPicker: vi.fn() },
66 image: { insertImage: vi.fn() },
67 emoji: { openPicker: vi.fn() },
68 ai: null,
69 });
70 
71 const paragraphTitles = filterItems(items, "", { editor: paragraphEditor }).map((item) => item.title);
72 const codeBlockTitles = filterItems(items, "", { editor: codeBlockEditor }).map((item) => item.title);
73 
74 expect(paragraphTitles).toContain("Link page");
75 expect(codeBlockTitles).not.toContain("Link page");
76 });
77 
78 it("keeps the shared-editor insertion gate unchanged", () => {
79 expect(canInsertPageMentions({ editable: true, workspaceId: "ws-1", shareToken: undefined })).toBe(true);
80 expect(canInsertPageMentions({ editable: true, workspaceId: "ws-1", shareToken: "share-token" })).toBe(false);
81 });
82});