File
Blob: tests/client/components/editor/controllers/callout.test.ts
| 1 | import { describe, expect, it } from "vitest"; |
| 2 | import { getSchema } from "@tiptap/core"; |
| 3 | import { StarterKit } from "@tiptap/starter-kit"; |
| 4 | import { createCalloutNode, type CalloutBlockAttrs } from "@/client/components/editor/controllers/callout"; |
| 5 | import { CalloutExtension } from "@/client/components/editor/extensions/callout"; |
| 6 | import { |
| 7 | CALLOUT_KINDS, |
| 8 | DEFAULT_CALLOUT_KIND, |
| 9 | normalizeCalloutKind, |
| 10 | } from "@/client/components/editor/extensions/callout/kinds"; |
| 11 | |
| 12 | const schema = getSchema([StarterKit.configure({ undoRedo: false }), CalloutExtension]); |
| 13 | |
| 14 | describe("callout block helpers", () => { |
| 15 | it("defaults to info kind with a single empty paragraph", () => { |
| 16 | expect(createCalloutNode()).toEqual({ |
| 17 | type: "callout", |
| 18 | attrs: { kind: "info" }, |
| 19 | content: [{ type: "paragraph" }], |
| 20 | }); |
| 21 | }); |
| 22 | |
| 23 | it("honors an explicit supported kind", () => { |
| 24 | expect(createCalloutNode({ kind: "warning" })).toEqual({ |
| 25 | type: "callout", |
| 26 | attrs: { kind: "warning" }, |
| 27 | content: [{ type: "paragraph" }], |
| 28 | }); |
| 29 | }); |
| 30 | |
| 31 | it("falls back to the default kind when given an unknown kind", () => { |
| 32 | const attrs = { kind: "bogus" } as unknown as CalloutBlockAttrs; |
| 33 | expect(createCalloutNode(attrs)).toEqual({ |
| 34 | type: "callout", |
| 35 | attrs: { kind: DEFAULT_CALLOUT_KIND }, |
| 36 | content: [{ type: "paragraph" }], |
| 37 | }); |
| 38 | }); |
| 39 | |
| 40 | it("exposes every supported kind through normalizeCalloutKind", () => { |
| 41 | for (const kind of CALLOUT_KINDS) { |
| 42 | expect(normalizeCalloutKind(kind)).toBe(kind); |
| 43 | } |
| 44 | expect(normalizeCalloutKind(null)).toBe(DEFAULT_CALLOUT_KIND); |
| 45 | expect(normalizeCalloutKind(undefined)).toBe(DEFAULT_CALLOUT_KIND); |
| 46 | expect(normalizeCalloutKind("nope")).toBe(DEFAULT_CALLOUT_KIND); |
| 47 | expect(normalizeCalloutKind(42)).toBe(DEFAULT_CALLOUT_KIND); |
| 48 | }); |
| 49 | |
| 50 | it("round-trips through the editor schema without losing content", () => { |
| 51 | const doc = schema.nodeFromJSON({ |
| 52 | type: "doc", |
| 53 | content: [ |
| 54 | createCalloutNode({ kind: "tip" }), |
| 55 | { |
| 56 | type: "callout", |
| 57 | attrs: { kind: "warning" }, |
| 58 | content: [ |
| 59 | { type: "paragraph", content: [{ type: "text", text: "Hello inside a callout" }] }, |
| 60 | { |
| 61 | type: "bulletList", |
| 62 | content: [ |
| 63 | { |
| 64 | type: "listItem", |
| 65 | content: [{ type: "paragraph", content: [{ type: "text", text: "nested list item" }] }], |
| 66 | }, |
| 67 | ], |
| 68 | }, |
| 69 | ], |
| 70 | }, |
| 71 | ], |
| 72 | }); |
| 73 | |
| 74 | expect(doc.childCount).toBe(2); |
| 75 | expect(doc.firstChild?.type.name).toBe("callout"); |
| 76 | expect(doc.firstChild?.attrs.kind).toBe("tip"); |
| 77 | const second = doc.child(1); |
| 78 | expect(second.attrs.kind).toBe("warning"); |
| 79 | expect(second.childCount).toBe(2); |
| 80 | expect(second.textContent).toContain("Hello inside a callout"); |
| 81 | expect(second.textContent).toContain("nested list item"); |
| 82 | }); |
| 83 | }); |