]> BookStack Code Mirror - bookstack/blob - resources/js/wysiwyg/lexical/list/__tests__/unit/LexicalListItemNode.test.ts
581db0294f5b2af97da705d762611e8c147f9cf3
[bookstack] / resources / js / wysiwyg / lexical / list / __tests__ / unit / LexicalListItemNode.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 import {
10   $createParagraphNode,
11   $createRangeSelection,
12   $getRoot,
13   TextNode,
14 } from 'lexical';
15 import {
16   expectHtmlToBeEqual,
17   html,
18   initializeUnitTest,
19 } from 'lexical/__tests__/utils';
20
21 import {
22   $createListItemNode,
23   $isListItemNode,
24   ListItemNode,
25   ListNode,
26 } from '../..';
27
28 const editorConfig = Object.freeze({
29   namespace: '',
30   theme: {
31     list: {
32       listitem: 'my-listItem-item-class',
33       nested: {
34         listitem: 'my-nested-list-listItem-class',
35       },
36     },
37   },
38 });
39
40 describe('LexicalListItemNode tests', () => {
41   initializeUnitTest((testEnv) => {
42     test('ListItemNode.constructor', async () => {
43       const {editor} = testEnv;
44
45       await editor.update(() => {
46         const listItemNode = new ListItemNode();
47
48         expect(listItemNode.getType()).toBe('listitem');
49
50         expect(listItemNode.getTextContent()).toBe('');
51       });
52
53       expect(() => new ListItemNode()).toThrow();
54     });
55
56     test('ListItemNode.createDOM()', async () => {
57       const {editor} = testEnv;
58
59       await editor.update(() => {
60         const listItemNode = new ListItemNode();
61
62         expectHtmlToBeEqual(
63           listItemNode.createDOM(editorConfig).outerHTML,
64           html`
65             <li value="1" class="my-listItem-item-class"></li>
66           `,
67         );
68
69         expectHtmlToBeEqual(
70           listItemNode.createDOM({
71             namespace: '',
72             theme: {},
73           }).outerHTML,
74           html`
75             <li value="1"></li>
76           `,
77         );
78       });
79     });
80
81     describe('ListItemNode.updateDOM()', () => {
82       test('base', async () => {
83         const {editor} = testEnv;
84
85         await editor.update(() => {
86           const listItemNode = new ListItemNode();
87
88           const domElement = listItemNode.createDOM(editorConfig);
89
90           expectHtmlToBeEqual(
91             domElement.outerHTML,
92             html`
93               <li value="1" class="my-listItem-item-class"></li>
94             `,
95           );
96           const newListItemNode = new ListItemNode();
97
98           const result = newListItemNode.updateDOM(
99             listItemNode,
100             domElement,
101             editorConfig,
102           );
103
104           expect(result).toBe(false);
105
106           expectHtmlToBeEqual(
107             domElement.outerHTML,
108             html`
109               <li value="1" class="my-listItem-item-class"></li>
110             `,
111           );
112         });
113       });
114
115       test('nested list', async () => {
116         const {editor} = testEnv;
117
118         await editor.update(() => {
119           const parentListNode = new ListNode('bullet', 1);
120           const parentlistItemNode = new ListItemNode();
121
122           parentListNode.append(parentlistItemNode);
123           const domElement = parentlistItemNode.createDOM(editorConfig);
124
125           expectHtmlToBeEqual(
126             domElement.outerHTML,
127             html`
128               <li value="1" class="my-listItem-item-class"></li>
129             `,
130           );
131           const nestedListNode = new ListNode('bullet', 1);
132           nestedListNode.append(new ListItemNode());
133           parentlistItemNode.append(nestedListNode);
134           const result = parentlistItemNode.updateDOM(
135             parentlistItemNode,
136             domElement,
137             editorConfig,
138           );
139
140           expect(result).toBe(false);
141
142           expectHtmlToBeEqual(
143             domElement.outerHTML,
144             html`
145               <li value="1" class="my-listItem-item-class my-nested-list-listItem-class"></li>
146             `,
147           );
148         });
149       });
150     });
151
152     describe('ListItemNode.replace()', () => {
153       let listNode: ListNode;
154       let listItemNode1: ListItemNode;
155       let listItemNode2: ListItemNode;
156       let listItemNode3: ListItemNode;
157
158       beforeEach(async () => {
159         const {editor} = testEnv;
160
161         await editor.update(() => {
162           const root = $getRoot();
163           listNode = new ListNode('bullet', 1);
164           listItemNode1 = new ListItemNode();
165
166           listItemNode1.append(new TextNode('one'));
167           listItemNode2 = new ListItemNode();
168
169           listItemNode2.append(new TextNode('two'));
170           listItemNode3 = new ListItemNode();
171
172           listItemNode3.append(new TextNode('three'));
173           root.append(listNode);
174           listNode.append(listItemNode1, listItemNode2, listItemNode3);
175         });
176
177         expectHtmlToBeEqual(
178           testEnv.outerHTML,
179           html`
180             <div
181               contenteditable="true"
182               style="user-select: text; white-space: pre-wrap; word-break: break-word;"
183               data-lexical-editor="true">
184               <ul>
185                 <li value="1">
186                   <span data-lexical-text="true">one</span>
187                 </li>
188                 <li value="2">
189                   <span data-lexical-text="true">two</span>
190                 </li>
191                 <li value="3">
192                   <span data-lexical-text="true">three</span>
193                 </li>
194               </ul>
195             </div>
196           `,
197         );
198       });
199
200       test('another list item node', async () => {
201         const {editor} = testEnv;
202
203         await editor.update(() => {
204           const newListItemNode = new ListItemNode();
205
206           newListItemNode.append(new TextNode('bar'));
207           listItemNode1.replace(newListItemNode);
208         });
209
210         expectHtmlToBeEqual(
211           testEnv.outerHTML,
212           html`
213             <div
214               contenteditable="true"
215               style="user-select: text; white-space: pre-wrap; word-break: break-word;"
216               data-lexical-editor="true">
217               <ul>
218                 <li value="1">
219                   <span data-lexical-text="true">bar</span>
220                 </li>
221                 <li value="2">
222                   <span data-lexical-text="true">two</span>
223                 </li>
224                 <li value="3">
225                   <span data-lexical-text="true">three</span>
226                 </li>
227               </ul>
228             </div>
229           `,
230         );
231       });
232
233       test('first list item with a non list item node', async () => {
234         const {editor} = testEnv;
235
236         await editor.update(() => {
237           return;
238         });
239
240         expectHtmlToBeEqual(
241           testEnv.outerHTML,
242           html`
243             <div
244               contenteditable="true"
245               style="user-select: text; white-space: pre-wrap; word-break: break-word;"
246               data-lexical-editor="true">
247               <ul>
248                 <li value="1">
249                   <span data-lexical-text="true">one</span>
250                 </li>
251                 <li value="2">
252                   <span data-lexical-text="true">two</span>
253                 </li>
254                 <li value="3">
255                   <span data-lexical-text="true">three</span>
256                 </li>
257               </ul>
258             </div>
259           `,
260         );
261
262         await editor.update(() => {
263           const paragraphNode = $createParagraphNode();
264           listItemNode1.replace(paragraphNode);
265         });
266
267         expectHtmlToBeEqual(
268           testEnv.outerHTML,
269           html`
270             <div
271               contenteditable="true"
272               style="user-select: text; white-space: pre-wrap; word-break: break-word;"
273               data-lexical-editor="true">
274               <p><br></p>
275               <ul>
276                 <li value="1">
277                   <span data-lexical-text="true">two</span>
278                 </li>
279                 <li value="2">
280                   <span data-lexical-text="true">three</span>
281                 </li>
282               </ul>
283             </div>
284           `,
285         );
286       });
287
288       test('last list item with a non list item node', async () => {
289         const {editor} = testEnv;
290
291         await editor.update(() => {
292           const paragraphNode = $createParagraphNode();
293           listItemNode3.replace(paragraphNode);
294         });
295
296         expectHtmlToBeEqual(
297           testEnv.outerHTML,
298           html`
299             <div
300               contenteditable="true"
301               style="user-select: text; white-space: pre-wrap; word-break: break-word;"
302               data-lexical-editor="true">
303               <ul>
304                 <li value="1">
305                   <span data-lexical-text="true">one</span>
306                 </li>
307                 <li value="2">
308                   <span data-lexical-text="true">two</span>
309                 </li>
310               </ul>
311               <p><br></p>
312             </div>
313           `,
314         );
315       });
316
317       test('middle list item with a non list item node', async () => {
318         const {editor} = testEnv;
319
320         await editor.update(() => {
321           const paragraphNode = $createParagraphNode();
322           listItemNode2.replace(paragraphNode);
323         });
324
325         expectHtmlToBeEqual(
326           testEnv.outerHTML,
327           html`
328             <div
329               contenteditable="true"
330               style="user-select: text; white-space: pre-wrap; word-break: break-word;"
331               data-lexical-editor="true">
332               <ul>
333                 <li value="1">
334                   <span data-lexical-text="true">one</span>
335                 </li>
336               </ul>
337               <p><br></p>
338               <ul>
339                 <li value="1">
340                   <span data-lexical-text="true">three</span>
341                 </li>
342               </ul>
343             </div>
344           `,
345         );
346       });
347
348       test('the only list item with a non list item node', async () => {
349         const {editor} = testEnv;
350
351         await editor.update(() => {
352           listItemNode2.remove();
353           listItemNode3.remove();
354         });
355
356         expectHtmlToBeEqual(
357           testEnv.outerHTML,
358           html`
359             <div
360               contenteditable="true"
361               style="user-select: text; white-space: pre-wrap; word-break: break-word;"
362               data-lexical-editor="true">
363               <ul>
364                 <li value="1">
365                   <span data-lexical-text="true">one</span>
366                 </li>
367               </ul>
368             </div>
369           `,
370         );
371
372         await editor.update(() => {
373           const paragraphNode = $createParagraphNode();
374           listItemNode1.replace(paragraphNode);
375         });
376
377         expectHtmlToBeEqual(
378           testEnv.outerHTML,
379           html`
380             <div
381               contenteditable="true"
382               style="user-select: text; white-space: pre-wrap; word-break: break-word;"
383               data-lexical-editor="true">
384               <p><br></p>
385             </div>
386           `,
387         );
388       });
389     });
390
391     describe('ListItemNode.remove()', () => {
392       // - A
393       // - x
394       // - B
395       test('siblings are not nested', async () => {
396         const {editor} = testEnv;
397         let x: ListItemNode;
398
399         await editor.update(() => {
400           const root = $getRoot();
401           const parent = new ListNode('bullet', 1);
402
403           const A_listItem = new ListItemNode();
404           A_listItem.append(new TextNode('A'));
405
406           x = new ListItemNode();
407           x.append(new TextNode('x'));
408
409           const B_listItem = new ListItemNode();
410           B_listItem.append(new TextNode('B'));
411
412           parent.append(A_listItem, x, B_listItem);
413           root.append(parent);
414         });
415
416         expectHtmlToBeEqual(
417           testEnv.outerHTML,
418           html`
419             <div
420               contenteditable="true"
421               style="user-select: text; white-space: pre-wrap; word-break: break-word;"
422               data-lexical-editor="true">
423               <ul>
424                 <li value="1">
425                   <span data-lexical-text="true">A</span>
426                 </li>
427                 <li value="2">
428                   <span data-lexical-text="true">x</span>
429                 </li>
430                 <li value="3">
431                   <span data-lexical-text="true">B</span>
432                 </li>
433               </ul>
434             </div>
435           `,
436         );
437
438         await editor.update(() => x.remove());
439
440         expectHtmlToBeEqual(
441           testEnv.outerHTML,
442           html`
443             <div
444               contenteditable="true"
445               style="user-select: text; white-space: pre-wrap; word-break: break-word;"
446               data-lexical-editor="true">
447               <ul>
448                 <li value="1">
449                   <span data-lexical-text="true">A</span>
450                 </li>
451                 <li value="2">
452                   <span data-lexical-text="true">B</span>
453                 </li>
454               </ul>
455             </div>
456           `,
457         );
458       });
459
460       //   - A
461       // - x
462       // - B
463       test('the previous sibling is nested', async () => {
464         const {editor} = testEnv;
465         let x: ListItemNode;
466
467         await editor.update(() => {
468           const root = $getRoot();
469           const parent = new ListNode('bullet', 1);
470
471           const A_listItem = new ListItemNode();
472           const A_nestedList = new ListNode('bullet', 1);
473           const A_nestedListItem = new ListItemNode();
474           A_listItem.append(A_nestedList);
475           A_nestedList.append(A_nestedListItem);
476           A_nestedListItem.append(new TextNode('A'));
477
478           x = new ListItemNode();
479           x.append(new TextNode('x'));
480
481           const B_listItem = new ListItemNode();
482           B_listItem.append(new TextNode('B'));
483
484           parent.append(A_listItem, x, B_listItem);
485           root.append(parent);
486         });
487
488         expectHtmlToBeEqual(
489           testEnv.outerHTML,
490           html`
491             <div
492               contenteditable="true"
493               style="user-select: text; white-space: pre-wrap; word-break: break-word;"
494               data-lexical-editor="true">
495               <ul>
496                 <li value="1">
497                   <ul>
498                     <li value="1">
499                       <span data-lexical-text="true">A</span>
500                     </li>
501                   </ul>
502                 </li>
503                 <li value="1">
504                   <span data-lexical-text="true">x</span>
505                 </li>
506                 <li value="2">
507                   <span data-lexical-text="true">B</span>
508                 </li>
509               </ul>
510             </div>
511           `,
512         );
513
514         await editor.update(() => x.remove());
515
516         expectHtmlToBeEqual(
517           testEnv.outerHTML,
518           html`
519             <div
520               contenteditable="true"
521               style="user-select: text; white-space: pre-wrap; word-break: break-word;"
522               data-lexical-editor="true">
523               <ul>
524                 <li value="1">
525                   <ul>
526                     <li value="1">
527                       <span data-lexical-text="true">A</span>
528                     </li>
529                   </ul>
530                 </li>
531                 <li value="1">
532                   <span data-lexical-text="true">B</span>
533                 </li>
534               </ul>
535             </div>
536           `,
537         );
538       });
539
540       // - A
541       // - x
542       //   - B
543       test('the next sibling is nested', async () => {
544         const {editor} = testEnv;
545         let x: ListItemNode;
546
547         await editor.update(() => {
548           const root = $getRoot();
549           const parent = new ListNode('bullet', 1);
550
551           const A_listItem = new ListItemNode();
552           A_listItem.append(new TextNode('A'));
553
554           x = new ListItemNode();
555           x.append(new TextNode('x'));
556
557           const B_listItem = new ListItemNode();
558           const B_nestedList = new ListNode('bullet', 1);
559           const B_nestedListItem = new ListItemNode();
560           B_listItem.append(B_nestedList);
561           B_nestedList.append(B_nestedListItem);
562           B_nestedListItem.append(new TextNode('B'));
563
564           parent.append(A_listItem, x, B_listItem);
565           root.append(parent);
566         });
567
568         expectHtmlToBeEqual(
569           testEnv.outerHTML,
570           html`
571             <div
572               contenteditable="true"
573               style="user-select: text; white-space: pre-wrap; word-break: break-word;"
574               data-lexical-editor="true">
575               <ul>
576                 <li value="1">
577                   <span data-lexical-text="true">A</span>
578                 </li>
579                 <li value="2">
580                   <span data-lexical-text="true">x</span>
581                 </li>
582                 <li value="3">
583                   <ul>
584                     <li value="1">
585                       <span data-lexical-text="true">B</span>
586                     </li>
587                   </ul>
588                 </li>
589               </ul>
590             </div>
591           `,
592         );
593
594         await editor.update(() => x.remove());
595
596         expectHtmlToBeEqual(
597           testEnv.outerHTML,
598           html`
599             <div
600               contenteditable="true"
601               style="user-select: text; white-space: pre-wrap; word-break: break-word;"
602               data-lexical-editor="true">
603               <ul>
604                 <li value="1">
605                   <span data-lexical-text="true">A</span>
606                 </li>
607                 <li value="2">
608                   <ul>
609                     <li value="1">
610                       <span data-lexical-text="true">B</span>
611                     </li>
612                   </ul>
613                 </li>
614               </ul>
615             </div>
616           `,
617         );
618       });
619
620       //   - A
621       // - x
622       //   - B
623       test('both siblings are nested', async () => {
624         const {editor} = testEnv;
625         let x: ListItemNode;
626
627         await editor.update(() => {
628           const root = $getRoot();
629           const parent = new ListNode('bullet', 1);
630
631           const A_listItem = new ListItemNode();
632           const A_nestedList = new ListNode('bullet', 1);
633           const A_nestedListItem = new ListItemNode();
634           A_listItem.append(A_nestedList);
635           A_nestedList.append(A_nestedListItem);
636           A_nestedListItem.append(new TextNode('A'));
637
638           x = new ListItemNode();
639           x.append(new TextNode('x'));
640
641           const B_listItem = new ListItemNode();
642           const B_nestedList = new ListNode('bullet', 1);
643           const B_nestedListItem = new ListItemNode();
644           B_listItem.append(B_nestedList);
645           B_nestedList.append(B_nestedListItem);
646           B_nestedListItem.append(new TextNode('B'));
647
648           parent.append(A_listItem, x, B_listItem);
649           root.append(parent);
650         });
651
652         expectHtmlToBeEqual(
653           testEnv.outerHTML,
654           html`
655             <div
656               contenteditable="true"
657               style="user-select: text; white-space: pre-wrap; word-break: break-word;"
658               data-lexical-editor="true">
659               <ul>
660                 <li value="1">
661                   <ul>
662                     <li value="1">
663                       <span data-lexical-text="true">A</span>
664                     </li>
665                   </ul>
666                 </li>
667                 <li value="1">
668                   <span data-lexical-text="true">x</span>
669                 </li>
670                 <li value="2">
671                   <ul>
672                     <li value="1">
673                       <span data-lexical-text="true">B</span>
674                     </li>
675                   </ul>
676                 </li>
677               </ul>
678             </div>
679           `,
680         );
681
682         await editor.update(() => x.remove());
683
684         expectHtmlToBeEqual(
685           testEnv.outerHTML,
686           html`
687             <div
688               contenteditable="true"
689               style="user-select: text; white-space: pre-wrap; word-break: break-word;"
690               data-lexical-editor="true">
691               <ul>
692                 <li value="1">
693                   <ul>
694                     <li value="1">
695                       <span data-lexical-text="true">A</span>
696                     </li>
697                     <li value="2">
698                       <span data-lexical-text="true">B</span>
699                     </li>
700                   </ul>
701                 </li>
702               </ul>
703             </div>
704           `,
705         );
706       });
707
708       //  - A1
709       //     - A2
710       // - x
711       //   - B
712       test('the previous sibling is nested deeper than the next sibling', async () => {
713         const {editor} = testEnv;
714         let x: ListItemNode;
715
716         await editor.update(() => {
717           const root = $getRoot();
718           const parent = new ListNode('bullet', 1);
719
720           const A_listItem = new ListItemNode();
721           const A_nestedList = new ListNode('bullet', 1);
722           const A_nestedListItem1 = new ListItemNode();
723           const A_nestedListItem2 = new ListItemNode();
724           const A_deeplyNestedList = new ListNode('bullet', 1);
725           const A_deeplyNestedListItem = new ListItemNode();
726           A_listItem.append(A_nestedList);
727           A_nestedList.append(A_nestedListItem1);
728           A_nestedList.append(A_nestedListItem2);
729           A_nestedListItem1.append(new TextNode('A1'));
730           A_nestedListItem2.append(A_deeplyNestedList);
731           A_deeplyNestedList.append(A_deeplyNestedListItem);
732           A_deeplyNestedListItem.append(new TextNode('A2'));
733
734           x = new ListItemNode();
735           x.append(new TextNode('x'));
736
737           const B_listItem = new ListItemNode();
738           const B_nestedList = new ListNode('bullet', 1);
739           const B_nestedlistItem = new ListItemNode();
740           B_listItem.append(B_nestedList);
741           B_nestedList.append(B_nestedlistItem);
742           B_nestedlistItem.append(new TextNode('B'));
743
744           parent.append(A_listItem, x, B_listItem);
745           root.append(parent);
746         });
747
748         expectHtmlToBeEqual(
749           testEnv.outerHTML,
750           html`
751             <div
752               contenteditable="true"
753               style="user-select: text; white-space: pre-wrap; word-break: break-word;"
754               data-lexical-editor="true">
755               <ul>
756                 <li value="1">
757                   <ul>
758                     <li value="1">
759                       <span data-lexical-text="true">A1</span>
760                     </li>
761                     <li value="2">
762                       <ul>
763                         <li value="1">
764                           <span data-lexical-text="true">A2</span>
765                         </li>
766                       </ul>
767                     </li>
768                   </ul>
769                 </li>
770                 <li value="1">
771                   <span data-lexical-text="true">x</span>
772                 </li>
773                 <li value="2">
774                   <ul>
775                     <li value="1">
776                       <span data-lexical-text="true">B</span>
777                     </li>
778                   </ul>
779                 </li>
780               </ul>
781             </div>
782           `,
783         );
784
785         await editor.update(() => x.remove());
786
787         expectHtmlToBeEqual(
788           testEnv.outerHTML,
789           html`
790             <div
791               contenteditable="true"
792               style="user-select: text; white-space: pre-wrap; word-break: break-word;"
793               data-lexical-editor="true">
794               <ul>
795                 <li value="1">
796                   <ul>
797                     <li value="1">
798                       <span data-lexical-text="true">A1</span>
799                     </li>
800                     <li value="2">
801                       <ul>
802                         <li value="1">
803                           <span data-lexical-text="true">A2</span>
804                         </li>
805                       </ul>
806                     </li>
807                     <li value="2">
808                       <span data-lexical-text="true">B</span>
809                     </li>
810                   </ul>
811                 </li>
812               </ul>
813             </div>
814           `,
815         );
816       });
817
818       //   - A
819       // - x
820       //     - B1
821       //   - B2
822       test('the next sibling is nested deeper than the previous sibling', async () => {
823         const {editor} = testEnv;
824         let x: ListItemNode;
825
826         await editor.update(() => {
827           const root = $getRoot();
828           const parent = new ListNode('bullet', 1);
829
830           const A_listItem = new ListItemNode();
831           const A_nestedList = new ListNode('bullet', 1);
832           const A_nestedListItem = new ListItemNode();
833           A_listItem.append(A_nestedList);
834           A_nestedList.append(A_nestedListItem);
835           A_nestedListItem.append(new TextNode('A'));
836
837           x = new ListItemNode();
838           x.append(new TextNode('x'));
839
840           const B_listItem = new ListItemNode();
841           const B_nestedList = new ListNode('bullet', 1);
842           const B_nestedListItem1 = new ListItemNode();
843           const B_nestedListItem2 = new ListItemNode();
844           const B_deeplyNestedList = new ListNode('bullet', 1);
845           const B_deeplyNestedListItem = new ListItemNode();
846           B_listItem.append(B_nestedList);
847           B_nestedList.append(B_nestedListItem1);
848           B_nestedList.append(B_nestedListItem2);
849           B_nestedListItem1.append(B_deeplyNestedList);
850           B_nestedListItem2.append(new TextNode('B2'));
851           B_deeplyNestedList.append(B_deeplyNestedListItem);
852           B_deeplyNestedListItem.append(new TextNode('B1'));
853
854           parent.append(A_listItem, x, B_listItem);
855           root.append(parent);
856         });
857
858         expectHtmlToBeEqual(
859           testEnv.outerHTML,
860           html`
861             <div
862               contenteditable="true"
863               style="user-select: text; white-space: pre-wrap; word-break: break-word;"
864               data-lexical-editor="true">
865               <ul>
866                 <li value="1">
867                   <ul>
868                     <li value="1">
869                       <span data-lexical-text="true">A</span>
870                     </li>
871                   </ul>
872                 </li>
873                 <li value="1">
874                   <span data-lexical-text="true">x</span>
875                 </li>
876                 <li value="2">
877                   <ul>
878                     <li value="1">
879                       <ul>
880                         <li value="1">
881                           <span data-lexical-text="true">B1</span>
882                         </li>
883                       </ul>
884                     </li>
885                     <li value="1">
886                       <span data-lexical-text="true">B2</span>
887                     </li>
888                   </ul>
889                 </li>
890               </ul>
891             </div>
892           `,
893         );
894
895         await editor.update(() => x.remove());
896
897         expectHtmlToBeEqual(
898           testEnv.outerHTML,
899           html`
900             <div
901               contenteditable="true"
902               style="user-select: text; white-space: pre-wrap; word-break: break-word;"
903               data-lexical-editor="true">
904               <ul>
905                 <li value="1">
906                   <ul>
907                     <li value="1">
908                       <span data-lexical-text="true">A</span>
909                     </li>
910                     <li value="2">
911                       <ul>
912                         <li value="1">
913                           <span data-lexical-text="true">B1</span>
914                         </li>
915                       </ul>
916                     </li>
917                     <li value="2">
918                       <span data-lexical-text="true">B2</span>
919                     </li>
920                   </ul>
921                 </li>
922               </ul>
923             </div>
924           `,
925         );
926       });
927
928       //   - A1
929       //     - A2
930       // - x
931       //     - B1
932       //   - B2
933       test('both siblings are deeply nested', async () => {
934         const {editor} = testEnv;
935         let x: ListItemNode;
936
937         await editor.update(() => {
938           const root = $getRoot();
939           const parent = new ListNode('bullet', 1);
940
941           const A_listItem = new ListItemNode();
942           const A_nestedList = new ListNode('bullet', 1);
943           const A_nestedListItem1 = new ListItemNode();
944           const A_nestedListItem2 = new ListItemNode();
945           const A_deeplyNestedList = new ListNode('bullet', 1);
946           const A_deeplyNestedListItem = new ListItemNode();
947           A_listItem.append(A_nestedList);
948           A_nestedList.append(A_nestedListItem1);
949           A_nestedList.append(A_nestedListItem2);
950           A_nestedListItem1.append(new TextNode('A1'));
951           A_nestedListItem2.append(A_deeplyNestedList);
952           A_deeplyNestedList.append(A_deeplyNestedListItem);
953           A_deeplyNestedListItem.append(new TextNode('A2'));
954
955           x = new ListItemNode();
956           x.append(new TextNode('x'));
957
958           const B_listItem = new ListItemNode();
959           const B_nestedList = new ListNode('bullet', 1);
960           const B_nestedListItem1 = new ListItemNode();
961           const B_nestedListItem2 = new ListItemNode();
962           const B_deeplyNestedList = new ListNode('bullet', 1);
963           const B_deeplyNestedListItem = new ListItemNode();
964           B_listItem.append(B_nestedList);
965           B_nestedList.append(B_nestedListItem1);
966           B_nestedList.append(B_nestedListItem2);
967           B_nestedListItem1.append(B_deeplyNestedList);
968           B_nestedListItem2.append(new TextNode('B2'));
969           B_deeplyNestedList.append(B_deeplyNestedListItem);
970           B_deeplyNestedListItem.append(new TextNode('B1'));
971
972           parent.append(A_listItem, x, B_listItem);
973           root.append(parent);
974         });
975
976         expectHtmlToBeEqual(
977           testEnv.outerHTML,
978           html`
979             <div
980               contenteditable="true"
981               style="user-select: text; white-space: pre-wrap; word-break: break-word;"
982               data-lexical-editor="true">
983               <ul>
984                 <li value="1">
985                   <ul>
986                     <li value="1">
987                       <span data-lexical-text="true">A1</span>
988                     </li>
989                     <li value="2">
990                       <ul>
991                         <li value="1">
992                           <span data-lexical-text="true">A2</span>
993                         </li>
994                       </ul>
995                     </li>
996                   </ul>
997                 </li>
998                 <li value="1">
999                   <span data-lexical-text="true">x</span>
1000                 </li>
1001                 <li value="2">
1002                   <ul>
1003                     <li value="1">
1004                       <ul>
1005                         <li value="1">
1006                           <span data-lexical-text="true">B1</span>
1007                         </li>
1008                       </ul>
1009                     </li>
1010                     <li value="1">
1011                       <span data-lexical-text="true">B2</span>
1012                     </li>
1013                   </ul>
1014                 </li>
1015               </ul>
1016             </div>
1017           `,
1018         );
1019
1020         await editor.update(() => x.remove());
1021
1022         expectHtmlToBeEqual(
1023           testEnv.outerHTML,
1024           html`
1025             <div
1026               contenteditable="true"
1027               style="user-select: text; white-space: pre-wrap; word-break: break-word;"
1028               data-lexical-editor="true">
1029               <ul>
1030                 <li value="1">
1031                   <ul>
1032                     <li value="1">
1033                       <span data-lexical-text="true">A1</span>
1034                     </li>
1035                     <li value="2">
1036                       <ul>
1037                         <li value="1">
1038                           <span data-lexical-text="true">A2</span>
1039                         </li>
1040                         <li value="2">
1041                           <span data-lexical-text="true">B1</span>
1042                         </li>
1043                       </ul>
1044                     </li>
1045                     <li value="2">
1046                       <span data-lexical-text="true">B2</span>
1047                     </li>
1048                   </ul>
1049                 </li>
1050               </ul>
1051             </div>
1052           `,
1053         );
1054       });
1055     });
1056
1057     describe('ListItemNode.insertNewAfter(): non-empty list items', () => {
1058       let listNode: ListNode;
1059       let listItemNode1: ListItemNode;
1060       let listItemNode2: ListItemNode;
1061       let listItemNode3: ListItemNode;
1062
1063       beforeEach(async () => {
1064         const {editor} = testEnv;
1065
1066         await editor.update(() => {
1067           const root = $getRoot();
1068           listNode = new ListNode('bullet', 1);
1069           listItemNode1 = new ListItemNode();
1070
1071           listItemNode2 = new ListItemNode();
1072
1073           listItemNode3 = new ListItemNode();
1074
1075           root.append(listNode);
1076           listNode.append(listItemNode1, listItemNode2, listItemNode3);
1077           listItemNode1.append(new TextNode('one'));
1078           listItemNode2.append(new TextNode('two'));
1079           listItemNode3.append(new TextNode('three'));
1080         });
1081
1082         expectHtmlToBeEqual(
1083           testEnv.outerHTML,
1084           html`
1085             <div
1086               contenteditable="true"
1087               style="user-select: text; white-space: pre-wrap; word-break: break-word;"
1088               data-lexical-editor="true">
1089               <ul>
1090                 <li value="1">
1091                   <span data-lexical-text="true">one</span>
1092                 </li>
1093                 <li value="2">
1094                   <span data-lexical-text="true">two</span>
1095                 </li>
1096                 <li value="3">
1097                   <span data-lexical-text="true">three</span>
1098                 </li>
1099               </ul>
1100             </div>
1101           `,
1102         );
1103       });
1104
1105       test('first list item', async () => {
1106         const {editor} = testEnv;
1107
1108         await editor.update(() => {
1109           listItemNode1.insertNewAfter($createRangeSelection());
1110         });
1111
1112         expectHtmlToBeEqual(
1113           testEnv.outerHTML,
1114           html`
1115             <div
1116               contenteditable="true"
1117               style="user-select: text; white-space: pre-wrap; word-break: break-word;"
1118               data-lexical-editor="true">
1119               <ul>
1120                 <li value="1">
1121                   <span data-lexical-text="true">one</span>
1122                 </li>
1123                 <li value="2"><br></li>
1124                 <li value="3">
1125                   <span data-lexical-text="true">two</span>
1126                 </li>
1127                 <li value="4">
1128                   <span data-lexical-text="true">three</span>
1129                 </li>
1130               </ul>
1131             </div>
1132           `,
1133         );
1134       });
1135
1136       test('last list item', async () => {
1137         const {editor} = testEnv;
1138
1139         await editor.update(() => {
1140           listItemNode3.insertNewAfter($createRangeSelection());
1141         });
1142
1143         expectHtmlToBeEqual(
1144           testEnv.outerHTML,
1145           html`
1146             <div
1147               contenteditable="true"
1148               style="user-select: text; white-space: pre-wrap; word-break: break-word;"
1149               data-lexical-editor="true">
1150               <ul>
1151                 <li value="1">
1152                   <span data-lexical-text="true">one</span>
1153                 </li>
1154                 <li value="2">
1155                   <span data-lexical-text="true">two</span>
1156                 </li>
1157                 <li value="3">
1158                   <span data-lexical-text="true">three</span>
1159                 </li>
1160                 <li value="4"><br></li>
1161               </ul>
1162             </div>
1163           `,
1164         );
1165       });
1166
1167       test('middle list item', async () => {
1168         const {editor} = testEnv;
1169
1170         await editor.update(() => {
1171           listItemNode3.insertNewAfter($createRangeSelection());
1172         });
1173
1174         expectHtmlToBeEqual(
1175           testEnv.outerHTML,
1176           html`
1177             <div
1178               contenteditable="true"
1179               style="user-select: text; white-space: pre-wrap; word-break: break-word;"
1180               data-lexical-editor="true">
1181               <ul>
1182                 <li value="1">
1183                   <span data-lexical-text="true">one</span>
1184                 </li>
1185                 <li value="2">
1186                   <span data-lexical-text="true">two</span>
1187                 </li>
1188                 <li value="3">
1189                   <span data-lexical-text="true">three</span>
1190                 </li>
1191                 <li value="4"><br></li>
1192               </ul>
1193             </div>
1194           `,
1195         );
1196       });
1197
1198       test('the only list item', async () => {
1199         const {editor} = testEnv;
1200
1201         await editor.update(() => {
1202           listItemNode2.remove();
1203           listItemNode3.remove();
1204         });
1205
1206         expectHtmlToBeEqual(
1207           testEnv.outerHTML,
1208           html`
1209             <div
1210               contenteditable="true"
1211               style="user-select: text; white-space: pre-wrap; word-break: break-word;"
1212               data-lexical-editor="true">
1213               <ul>
1214                 <li value="1">
1215                   <span data-lexical-text="true">one</span>
1216                 </li>
1217               </ul>
1218             </div>
1219           `,
1220         );
1221
1222         await editor.update(() => {
1223           listItemNode1.insertNewAfter($createRangeSelection());
1224         });
1225
1226         expectHtmlToBeEqual(
1227           testEnv.outerHTML,
1228           html`
1229             <div
1230               contenteditable="true"
1231               style="user-select: text; white-space: pre-wrap; word-break: break-word;"
1232               data-lexical-editor="true">
1233               <ul>
1234                 <li value="1">
1235                   <span data-lexical-text="true">one</span>
1236                 </li>
1237                 <li value="2"><br></li>
1238               </ul>
1239             </div>
1240           `,
1241         );
1242       });
1243     });
1244
1245     test('$createListItemNode()', async () => {
1246       const {editor} = testEnv;
1247
1248       await editor.update(() => {
1249         const listItemNode = new ListItemNode();
1250
1251         const createdListItemNode = $createListItemNode();
1252
1253         expect(listItemNode.__type).toEqual(createdListItemNode.__type);
1254         expect(listItemNode.__parent).toEqual(createdListItemNode.__parent);
1255         expect(listItemNode.__key).not.toEqual(createdListItemNode.__key);
1256       });
1257     });
1258
1259     test('$isListItemNode()', async () => {
1260       const {editor} = testEnv;
1261
1262       await editor.update(() => {
1263         const listItemNode = new ListItemNode();
1264
1265         expect($isListItemNode(listItemNode)).toBe(true);
1266       });
1267     });
1268
1269     describe('ListItemNode.setIndent()', () => {
1270       let listNode: ListNode;
1271       let listItemNode1: ListItemNode;
1272       let listItemNode2: ListItemNode;
1273
1274       beforeEach(async () => {
1275         const {editor} = testEnv;
1276
1277         await editor.update(() => {
1278           const root = $getRoot();
1279           listNode = new ListNode('bullet', 1);
1280           listItemNode1 = new ListItemNode();
1281
1282           listItemNode2 = new ListItemNode();
1283
1284           root.append(listNode);
1285           listNode.append(listItemNode1, listItemNode2);
1286           listItemNode1.append(new TextNode('one'));
1287           listItemNode2.append(new TextNode('two'));
1288         });
1289       });
1290       it('indents and outdents list item', async () => {
1291         const {editor} = testEnv;
1292
1293         await editor.update(() => {
1294           listItemNode1.setIndent(3);
1295         });
1296
1297         await editor.update(() => {
1298           expect(listItemNode1.getIndent()).toBe(3);
1299         });
1300
1301         expectHtmlToBeEqual(
1302           editor.getRootElement()!.innerHTML,
1303           html`
1304             <ul>
1305               <li value="1">
1306                 <ul>
1307                   <li value="1">
1308                     <ul>
1309                       <li value="1">
1310                         <ul>
1311                           <li value="1">
1312                             <span data-lexical-text="true">one</span>
1313                           </li>
1314                         </ul>
1315                       </li>
1316                     </ul>
1317                   </li>
1318                 </ul>
1319               </li>
1320               <li value="1">
1321                 <span data-lexical-text="true">two</span>
1322               </li>
1323             </ul>
1324           `,
1325         );
1326
1327         await editor.update(() => {
1328           listItemNode1.setIndent(0);
1329         });
1330
1331         await editor.update(() => {
1332           expect(listItemNode1.getIndent()).toBe(0);
1333         });
1334
1335         expectHtmlToBeEqual(
1336           editor.getRootElement()!.innerHTML,
1337           html`
1338             <ul>
1339               <li value="1">
1340                 <span data-lexical-text="true">one</span>
1341               </li>
1342               <li value="2">
1343                 <span data-lexical-text="true">two</span>
1344               </li>
1345             </ul>
1346           `,
1347         );
1348       });
1349
1350       it('handles fractional indent values', async () => {
1351         const {editor} = testEnv;
1352
1353         await editor.update(() => {
1354           listItemNode1.setIndent(0.5);
1355         });
1356
1357         await editor.update(() => {
1358           expect(listItemNode1.getIndent()).toBe(0);
1359         });
1360       });
1361     });
1362   });
1363 });