]> BookStack Code Mirror - bookstack/blob - resources/js/wysiwyg/lexical/html/__tests__/unit/LexicalHtml.test.ts
3dbe5da8b3e10850945250b0496e2cdb0109ef97
[bookstack] / resources / js / wysiwyg / lexical / html / __tests__ / unit / LexicalHtml.test.ts
1 /**
2  * Copyright (c) Meta Platforms, Inc. and affiliates.
3  *
4  * This source code is licensed under the MIT license found in the
5  * LICENSE file in the root directory of this source tree.
6  *
7  */
8
9 //@ts-ignore-next-line
10 import type {RangeSelection} from 'lexical';
11
12 import {createHeadlessEditor} from '@lexical/headless';
13 import {$generateHtmlFromNodes, $generateNodesFromDOM} from '@lexical/html';
14 import {LinkNode} from '@lexical/link';
15 import {ListItemNode, ListNode} from '@lexical/list';
16 import {HeadingNode, QuoteNode} from '@lexical/rich-text';
17 import {
18   $createParagraphNode,
19   $createRangeSelection,
20   $createTextNode,
21   $getRoot,
22 } from 'lexical';
23
24 describe('HTML', () => {
25   type Input = Array<{
26     name: string;
27     html: string;
28     initializeEditorState: () => void;
29   }>;
30
31   const HTML_SERIALIZE: Input = [
32     {
33       html: '<p><br></p>',
34       initializeEditorState: () => {
35         $getRoot().append($createParagraphNode());
36       },
37       name: 'Empty editor state',
38     },
39   ];
40   for (const {name, html, initializeEditorState} of HTML_SERIALIZE) {
41     test(`[Lexical -> HTML]: ${name}`, () => {
42       const editor = createHeadlessEditor({
43         nodes: [
44           HeadingNode,
45           ListNode,
46           ListItemNode,
47           QuoteNode,
48           LinkNode,
49         ],
50       });
51
52       editor.update(initializeEditorState, {
53         discrete: true,
54       });
55
56       expect(
57         editor.getEditorState().read(() => $generateHtmlFromNodes(editor)),
58       ).toBe(html);
59     });
60   }
61
62   test(`[Lexical -> HTML]: Use provided selection`, () => {
63     const editor = createHeadlessEditor({
64       nodes: [
65         HeadingNode,
66         ListNode,
67         ListItemNode,
68         QuoteNode,
69         LinkNode,
70       ],
71     });
72
73     let selection: RangeSelection | null = null;
74
75     editor.update(
76       () => {
77         const root = $getRoot();
78         const p1 = $createParagraphNode();
79         const text1 = $createTextNode('Hello');
80         p1.append(text1);
81         const p2 = $createParagraphNode();
82         const text2 = $createTextNode('World');
83         p2.append(text2);
84         root.append(p1).append(p2);
85         // Root
86         // - ParagraphNode
87         // -- TextNode "Hello"
88         // - ParagraphNode
89         // -- TextNode "World"
90         p1.select(0, text1.getTextContentSize());
91         selection = $createRangeSelection();
92         selection.setTextNodeRange(text2, 0, text2, text2.getTextContentSize());
93       },
94       {
95         discrete: true,
96       },
97     );
98
99     let html = '';
100
101     editor.update(() => {
102       html = $generateHtmlFromNodes(editor, selection);
103     });
104
105     expect(html).toBe('<span style="white-space: pre-wrap;">World</span>');
106   });
107
108   test(`[Lexical -> HTML]: Default selection (undefined) should serialize entire editor state`, () => {
109     const editor = createHeadlessEditor({
110       nodes: [
111         HeadingNode,
112         ListNode,
113         ListItemNode,
114         QuoteNode,
115         LinkNode,
116       ],
117     });
118
119     editor.update(
120       () => {
121         const root = $getRoot();
122         const p1 = $createParagraphNode();
123         const text1 = $createTextNode('Hello');
124         p1.append(text1);
125         const p2 = $createParagraphNode();
126         const text2 = $createTextNode('World');
127         p2.append(text2);
128         root.append(p1).append(p2);
129         // Root
130         // - ParagraphNode
131         // -- TextNode "Hello"
132         // - ParagraphNode
133         // -- TextNode "World"
134         p1.select(0, text1.getTextContentSize());
135       },
136       {
137         discrete: true,
138       },
139     );
140
141     let html = '';
142
143     editor.update(() => {
144       html = $generateHtmlFromNodes(editor);
145     });
146
147     expect(html).toBe(
148       '<p><span style="white-space: pre-wrap;">Hello</span></p><p><span style="white-space: pre-wrap;">World</span></p>',
149     );
150   });
151
152   test(`If alignment is set on the paragraph, don't overwrite from parent empty format`, () => {
153     const editor = createHeadlessEditor();
154     const parser = new DOMParser();
155     const rightAlignedParagraphInDiv =
156       '<div><p style="text-align: center;">Hello world!</p></div>';
157
158     editor.update(
159       () => {
160         const root = $getRoot();
161         const dom = parser.parseFromString(
162           rightAlignedParagraphInDiv,
163           'text/html',
164         );
165         const nodes = $generateNodesFromDOM(editor, dom);
166         root.append(...nodes);
167       },
168       {discrete: true},
169     );
170
171     let html = '';
172
173     editor.update(() => {
174       html = $generateHtmlFromNodes(editor);
175     });
176
177     expect(html).toBe(
178       '<p style="text-align: center;"><span style="white-space: pre-wrap;">Hello world!</span></p>',
179     );
180   });
181
182   test(`If alignment is set on the paragraph, it should take precedence over its parent block alignment`, () => {
183     const editor = createHeadlessEditor();
184     const parser = new DOMParser();
185     const rightAlignedParagraphInDiv =
186       '<div style="text-align: right;"><p style="text-align: center;">Hello world!</p></div>';
187
188     editor.update(
189       () => {
190         const root = $getRoot();
191         const dom = parser.parseFromString(
192           rightAlignedParagraphInDiv,
193           'text/html',
194         );
195         const nodes = $generateNodesFromDOM(editor, dom);
196         root.append(...nodes);
197       },
198       {discrete: true},
199     );
200
201     let html = '';
202
203     editor.update(() => {
204       html = $generateHtmlFromNodes(editor);
205     });
206
207     expect(html).toBe(
208       '<p style="text-align: center;"><span style="white-space: pre-wrap;">Hello world!</span></p>',
209     );
210   });
211 });