import { describe, expect, test } from "@odoo/hoot"; import { testEditor, setupEditor } from "../_helpers/editor"; import { unformat } from "../_helpers/format"; import { tick } from "@odoo/hoot-mock"; import { deleteForward, insertText, tripleClick } from "../_helpers/user_actions"; import { getContent } from "../_helpers/selection"; import { microTick } from "@odoo/hoot-dom"; /** * content of the "deleteForward" sub suite in editor.test.js */ async function twoDeleteForward(editor) { deleteForward(editor); deleteForward(editor); } describe("Selection collapsed", () => { describe("Basic", () => { test("should do nothing", async () => { // TODO the addition of
"correction" part was judged // unnecessary to enforce, the rest of the test still makes // sense: not removing the unique

and keeping the // cursor at the right place. // await testEditor({ // contentBefore: '

[]

', // stepFunction: deleteForward, // contentAfter: '

[]

', // }); // TODO this cannot actually be tested currently as a // backspace/delete in that case is not even detected // (no input event to rollback) // await testEditor({ // contentBefore: '

[
]

', // stepFunction: deleteForward, // // The
is there only to make the

visible. // // It does not exist in VDocument and selecting it // // has no meaning in the DOM. // contentAfter: '

[]

', // }); await testEditor({ contentBefore: "

abc[]

", stepFunction: deleteForward, contentAfter: "

abc[]

", }); }); test("should delete the first character in a paragraph", async () => { await testEditor({ contentBefore: "

[]abc

", stepFunction: deleteForward, contentAfter: "

[]bc

", }); }); test("should delete a character within a paragraph", async () => { await testEditor({ contentBefore: "

a[]bc

", stepFunction: deleteForward, contentAfter: "

a[]c

", }); }); test("should delete the last character in a paragraph", async () => { await testEditor({ contentBefore: "

ab[]c

", stepFunction: deleteForward, contentAfter: "

ab[]

", }); await testEditor({ contentBefore: "

ab []c

", stepFunction: deleteForward, // The space should be converted to an unbreakable space // so it is visible. contentAfter: "

ab []

", }); }); test("should merge a paragraph into an empty paragraph", async () => { await testEditor({ contentBefore: "

[]

abc

", stepFunction: deleteForward, contentAfter: "

[]abc

", }); }); test("should merge P node correctly ", async () => { await testEditor({ contentBefore: "
a

b[]

c

d
", stepFunction: deleteForward, contentAfter: "
a

b[]c

d
", }); }); test("should merge node correctly", async () => { await testEditor({ contentBefore: '
ab[]

c

d
', stepFunction: deleteForward, contentAfter: '
ab[]c
d
', }); }); test("should merge SPAN node correctly ", async () => { await testEditor({ contentBefore: '
abc[]def
', stepFunction: deleteForward, contentAfter: '
abc[]ef
', }); }); test("should merge diferent element correctly", async () => { await testEditor({ contentBefore: '
ab[]

c

d
', stepFunction: deleteForward, contentAfter: '
ab[]c
d
', }); }); test("should ignore ZWS", async () => { await testEditor({ contentBefore: "

ab[]\u200Bc

", stepFunction: deleteForward, contentAfter: "

ab[]

", }); await testEditor({ contentBefore: "

de[]\u200B

", stepFunction: deleteForward, contentAfter: "

de[]

", }); await testEditor({ contentBefore: "

[]\u200Bf

", stepFunction: deleteForward, contentAfter: "

[]

", }); }); test("should delete through ZWS and Empty Inline", async () => { await testEditor({ contentBefore: '

a[]bcde

', stepFunction: async (editor) => { deleteForward(editor); deleteForward(editor); deleteForward(editor); }, contentAfter: "

a[]e

", }); }); test("ZWS: should delete element content but keep cursor in", async () => { await testEditor({ contentBefore: '

ab[]cdef

', stepFunction: async (editor) => { deleteForward(editor); deleteForward(editor); }, contentAfterEdit: '

ab[]\u200Bef

', contentAfter: '

ab[]\u200Bef

', }); await testEditor({ contentBefore: '

ab[]cdef

', stepFunction: async (editor) => { deleteForward(editor); deleteForward(editor); await insertText(editor, "x"); }, contentAfter: '

abx[]ef

', }); }); test("should ignore ZWS and merge", async () => { await testEditor({ contentBefore: '

[]\u200Bab

', contentBeforeEdit: '

[]\u200Bab

', stepFunction: async (editor) => { deleteForward(editor); await insertText(editor, "x"); }, contentAfter: "

x[]b

", }); await testEditor({ contentBefore: '

[]\u200Bcd

', stepFunction: async (editor) => { deleteForward(editor); await insertText(editor, "x"); }, contentAfter: '

x[]d

', }); await testEditor({ contentBefore: '

[]\u200B
ef

', stepFunction: async (editor) => { deleteForward(editor); await insertText(editor, "x"); }, contentAfter: "

x[]ef

", }); }); test('should remove contenteditable="false"', async () => { await testEditor({ contentBefore: `
[]abcdef
`, stepFunction: async (editor) => { deleteForward(editor); }, contentAfter: `
[]def
`, }); }); test("should wrap an inline sibling into a block", async () => { await testEditor({ contentBefore: `

abc[]

xyz

def

`, stepFunction: async (editor) => { deleteForward(editor); }, contentAfter: `

abc[]xyz

def

`, }); }); test("should not remove an inline contenteditable='false' in a following sibling", async () => { await testEditor({ contentBefore: `

[]

bca

`, stepFunction: async (editor) => { deleteForward(editor); }, contentAfter: `

[]bca

`, }); }); test("should not remove a non editable sibling (inline)", async () => { await testEditor({ contentBefore: unformat(`

[]

a
`), stepFunction: async (editor) => { deleteForward(editor); }, contentAfter: unformat(`

[]

a
`), }); }); test("should not remove a non editable sibling (block)", async () => { await testEditor({ contentBefore: unformat(`

[]

aa
`), stepFunction: async (editor) => { deleteForward(editor); }, contentAfter: unformat(`

[]

aa
`), }); }); test("should remove a hr", async () => { await testEditor({ contentBefore: `

abc[]


def

`, stepFunction: async (editor) => { deleteForward(editor); }, contentAfter: `

abc[]

def

`, }); }); test("should remove emoji", async () => { await testEditor({ contentBefore: `

[]\uD83D\uDE0D def

`, stepFunction: async (editor) => { deleteForward(editor); }, contentAfter: `

[] def

`, }); }); test("should remove invisible empty space at the start", async () => { await testEditor({ contentBefore: `

[] def

`, stepFunction: async (editor) => { deleteForward(editor); }, contentAfter: `

[]ef

`, }); }); test("should remove invisible empty space at the start (2)", async () => { await testEditor({ // The first 3 spaces are invisible : considered // formating by the browser. // The   is visible (space 1). // The last 3 spaces are consider as 1 visble space and // two formating spaces (space 2). contentBefore: `

[]   def

`, stepFunction: async (editor) => { deleteForward(editor); }, // Space 1 is deleted and space 2 should be transformed // to a   to stay visible. contentAfter: `

[] def

`, }); }); test("should merge p elements inside conteneditbale=true inside contenteditable=false", async () => { await testEditor({ contentBefore: `

abc[]

def

`, stepFunction: deleteForward, contentAfter: `

abc[]def

`, }); }); test("should remove a link to uploaded document", async () => { await testEditor({ contentBefore: `

[]abc

`, stepFunction: deleteForward, contentAfter: `

[]abc

`, }); }); test("should remove a link to uploaded document at the end of the editable", async () => { await testEditor({ contentBefore: `

[]

`, stepFunction: deleteForward, contentAfter: `

[]

`, }); }); }); describe("white spaces", () => { describe("no intefering spaces", () => { test("should delete a br line break", async () => { await testEditor({ contentBefore: "

abc[]
def

", stepFunction: deleteForward, contentAfter: "

abc[]def

", }); }); test("should delete a line break and merge the

", async () => { await testEditor({ contentBefore: "

abc[]

def

", stepFunction: deleteForward, contentAfter: "

abc[]def

", }); }); }); describe("intefering spaces", () => { test("should delete a br line break", async () => { await testEditor({ contentBefore: "

abc[]
def

", stepFunction: deleteForward, contentAfter: "

abc[]def

", }); }); test("should merge the two

", async () => { await testEditor({ contentBefore: "

abc[]

def

", stepFunction: deleteForward, contentAfter: "

abc[]def

", }); await testEditor({ contentBefore: '

abc[]

def

orphan node

', stepFunction: deleteForward, contentAfter: '

abc[]def

orphan node

', }); }); test("should delete the space if the second

is display inline", async () => { await testEditor({ contentBefore: '

abc[]

def

', stepFunction: deleteForward, contentAfter: '
abc[]

def

', }); }); test("should delete the space between the two ", async () => { await testEditor({ contentBefore: '
abc[] def
', stepFunction: deleteForward, contentAfter: '
abc[]def
', }); }); test("should delete the space before a ", async () => { await testEditor({ contentBefore: '
abc[] def
', stepFunction: deleteForward, contentAfter: '
abc[]def
', }); }); }); describe("intefering spaces, multiple deleteForward", () => { test("should delete a br line break", async () => { await testEditor({ contentBefore: "

abc[]x
def

", stepFunction: twoDeleteForward, contentAfter: "

abc[]def

", }); }); test("should merge the two

", async () => { await testEditor({ contentBefore: "

abc[]x

def

", stepFunction: twoDeleteForward, contentAfter: "

abc[]def

", }); }); test("should delete the space if the second

is display inline", async () => { await testEditor({ contentBefore: '

abc[]x

def

', stepFunction: twoDeleteForward, contentAfter: '
abc[]

def

', }); }); test("should delete the space between the two ", async () => { await testEditor({ contentBefore: '
abc[]x def
', stepFunction: twoDeleteForward, contentAfter: '
abc[]def
', }); }); test("should delete the space before a ", async () => { await testEditor({ contentBefore: '
abc[]x def
', stepFunction: twoDeleteForward, contentAfter: '
abc[]def
', }); }); }); }); describe("Line breaks", () => { describe("Single", () => { test("should delete a leading line break", async () => { await testEditor({ contentBefore: "

[]
abc

", stepFunction: deleteForward, contentAfter: "

[]abc

", }); await testEditor({ contentBefore: "

[]
abc

", stepFunction: deleteForward, // The space after the
is expected to be parsed // away, like it is in the DOM. contentAfter: "

[]abc

", }); }); test("should delete a line break within a paragraph", async () => { await testEditor({ contentBefore: "

ab[]
cd

", stepFunction: deleteForward, contentAfter: "

ab[]cd

", }); await testEditor({ contentBefore: "

ab []
cd

", stepFunction: deleteForward, contentAfter: "

ab []cd

", }); await testEditor({ contentBefore: "

ab[]
cd

", stepFunction: deleteForward, // The space after the
is expected to be parsed // away, like it is in the DOM. contentAfter: "

ab[]cd

", }); }); test("should delete a trailing line break", async () => { await testEditor({ contentBefore: "

abc[]

", stepFunction: deleteForward, contentAfter: "

abc[]

", }); await testEditor({ contentBefore: "

abc []

", stepFunction: deleteForward, contentAfter: "

abc []

", }); }); test("should delete a character and a line break, emptying a paragraph", async () => { await testEditor({ contentBefore: "

[]a

bcd

", stepFunction: async (editor) => { deleteForward(editor); deleteForward(editor); }, contentAfter: "

[]

bcd

", }); }); test("should delete a character before a trailing line break", async () => { await testEditor({ contentBefore: "

ab[]c

", stepFunction: deleteForward, contentAfter: "

ab[]

", }); }); }); describe("Consecutive", () => { test("should merge a paragraph into a paragraph with 4
", async () => { // 1 await testEditor({ contentBefore: "

ab





[]

cd

", stepFunction: deleteForward, contentAfter: "

ab




[]cd

", }); // 2-1 await testEditor({ contentBefore: "

ab




[]

cd

", stepFunction: deleteForward, // This should be identical to 1 contentAfter: "

ab




[]cd

", }); }); test("should delete a trailing line break", async () => { // 3-1 await testEditor({ contentBefore: "

ab



[]

cd

", stepFunction: deleteForward, contentAfter: "

ab



[]

cd

", }); }); test("should delete a trailing line break, then merge a paragraph into a paragraph with 3
", async () => { // 3-2 await testEditor({ contentBefore: "

ab



[]

cd

", stepFunction: async (editor) => { deleteForward(editor); deleteForward(editor); }, contentAfter: "

ab



[]cd

", }); }); test("should delete a line break (1)", async () => { // 4-1 await testEditor({ contentBefore: "

ab


[]


cd

", stepFunction: deleteForward, contentAfter: "

ab


[]

cd

", }); }); test("should delete two line breaks (1)", async () => { // 4-2 await testEditor({ contentBefore: "

ab


[]


cd

", stepFunction: async (editor) => { deleteForward(editor); deleteForward(editor); }, contentAfter: "

ab


[]

cd

", }); }); test("should delete two line breaks, then merge a paragraph into a paragraph with 2
", async () => { // 4-3 await testEditor({ contentBefore: "

ab


[]


cd

", stepFunction: async (editor) => { deleteForward(editor); deleteForward(editor); deleteForward(editor); }, contentAfter: "

ab


[]cd

", }); }); test("should delete a line break (2)", async () => { // 5-1 await testEditor({ contentBefore: "

ab

[]



cd

", stepFunction: deleteForward, contentAfter: "

ab

[]


cd

", }); }); test("should delete two line breaks (2)", async () => { // 5-2 await testEditor({ contentBefore: "

ab

[]



cd

", stepFunction: async (editor) => { deleteForward(editor); deleteForward(editor); }, contentAfter: "

ab

[]

cd

", }); }); test("should delete three line breaks (emptying a paragraph)", async () => { // 5-3 await testEditor({ contentBefore: "

ab

[]



cd

", stepFunction: async (editor) => { deleteForward(editor); deleteForward(editor); deleteForward(editor); }, contentAfter: "

ab

[]

cd

", }); }); test("should delete three line breaks, then merge a paragraph into an empty parargaph", async () => { // 5-4 await testEditor({ contentBefore: "

ab

[]



cd

", stepFunction: async (editor) => { deleteForward(editor); deleteForward(editor); deleteForward(editor); deleteForward(editor); }, contentAfter: "

ab

[]cd

", }); }); test("should merge a paragraph with 4
into a paragraph with text", async () => { // 6-1 await testEditor({ contentBefore: "

ab[]





cd

", stepFunction: deleteForward, contentAfter: "

ab[]



cd

", }); }); test("should merge a paragraph with 4
into a paragraph with text, then delete a line break", async () => { // 6-2 await testEditor({ contentBefore: "

ab[]





cd

", stepFunction: async (editor) => { deleteForward(editor); deleteForward(editor); }, contentAfter: "

ab[]


cd

", }); }); test("should merge a paragraph with 4
into a paragraph with text, then delete two line breaks", async () => { // 6-3 await testEditor({ contentBefore: "

ab[]





cd

", stepFunction: async (editor) => { deleteForward(editor); deleteForward(editor); deleteForward(editor); }, contentAfter: "

ab[]

cd

", }); }); test("should merge a paragraph with 4
into a paragraph with text, then delete three line breaks", async () => { // 6-4 await testEditor({ contentBefore: "

ab[]





cd

", stepFunction: async (editor) => { deleteForward(editor); deleteForward(editor); deleteForward(editor); deleteForward(editor); }, contentAfter: "

ab[]

cd

", }); }); test("should merge a paragraph with 4
into a paragraph with text, then delete three line breaks, then merge two paragraphs with text", async () => { // 6-5 await testEditor({ contentBefore: "

ab[]





cd

", stepFunction: async (editor) => { deleteForward(editor); deleteForward(editor); deleteForward(editor); deleteForward(editor); deleteForward(editor); }, contentAfter: "

ab[]cd

", }); }); }); }); describe("Pre", () => { test("should delete a character in a pre", async () => { await testEditor({ contentBefore: "
ab[]cd
", stepFunction: deleteForward, contentAfter: "
ab[]d
", }); }); test("should delete a character in a pre (space before)", async () => { await testEditor({ contentBefore: "
     ab[]cd
", stepFunction: deleteForward, contentAfter: "
     ab[]d
", }); }); test("should delete a character in a pre (space after)", async () => { await testEditor({ contentBefore: "
ab[]cd     
", stepFunction: deleteForward, contentAfter: "
ab[]d     
", }); }); test("should delete a character in a pre (space before and after)", async () => { await testEditor({ contentBefore: "
     ab[]cd     
", stepFunction: deleteForward, contentAfter: "
     ab[]d     
", }); }); test("should delete a space in a pre", async () => { await testEditor({ contentBefore: "
  []   ab
", stepFunction: deleteForward, contentAfter: "
  []  ab
", }); }); test("should delete a newline in a pre", async () => { await testEditor({ contentBefore: "
ab[]\ncd
", stepFunction: deleteForward, contentAfter: "
ab[]cd
", }); }); test("should delete all leading space in a pre", async () => { await testEditor({ contentBefore: "
[]     ab
", stepFunction: async (BasicEditor) => { deleteForward(BasicEditor); deleteForward(BasicEditor); deleteForward(BasicEditor); deleteForward(BasicEditor); deleteForward(BasicEditor); }, contentAfter: "
[]ab
", }); }); test("should delete all trailing space in a pre", async () => { await testEditor({ contentBefore: "
ab[]     
", stepFunction: async (BasicEditor) => { deleteForward(BasicEditor); deleteForward(BasicEditor); deleteForward(BasicEditor); deleteForward(BasicEditor); deleteForward(BasicEditor); }, contentAfter: "
ab[]
", }); }); }); describe("Formats", () => { test("should delete a character after a format node", async () => { await testEditor({ contentBefore: "

abc[]def

", stepFunction: deleteForward, contentAfter: "

abc[]ef

", }); await testEditor({ contentBefore: "

abc[]def

", stepFunction: deleteForward, // The selection is normalized so we only have one way // to represent a position. contentAfter: "

abc[]ef

", }); }); }); describe("Merging different types of elements", () => { test("should merge a paragraph with text into a heading1 with text", async () => { await testEditor({ contentBefore: "

ab[]

cd

", stepFunction: deleteForward, contentAfter: "

ab[]cd

", }); }); test("should merge an empty paragraph into a heading1 with text", async () => { await testEditor({ contentBefore: "

ab[]


", stepFunction: deleteForward, contentAfter: "

ab[]

", }); }); test("should remove empty paragraph (keeping the heading)", async () => { await testEditor({ contentBefore: "


[]

ab

", stepFunction: deleteForward, contentAfter: "

[]ab

", }); }); test("should merge a text following a paragraph (keeping the text)", async () => { await testEditor({ contentBefore: '

ab[]

cd

', stepFunction: deleteForward, contentAfter: "

ab[]cd

", }); await testEditor({ contentBefore: '

ab[]

cd

ef

', stepFunction: deleteForward, contentAfter: "

ab[]cd

ef

", }); }); }); describe("With attributes", () => { test("should remove empty paragraph with class", async () => { await testEditor({ contentBefore: '


[]

abc

', stepFunction: deleteForward, contentAfter: "

[]abc

", }); }); test("should merge two paragraphs with spans of same classes", async () => { await testEditor({ contentBefore: '

dom to[]

edit

', stepFunction: deleteForward, contentAfter: '

dom to[]edit

', }); }); test("should merge two paragraphs with spans of different classes without merging the spans", async () => { await testEditor({ contentBefore: '

dom to[]

edit

', stepFunction: deleteForward, contentAfter: '

dom to[]edit

', }); }); test("should merge two paragraphs of different classes, each containing spans of the same class", async () => { await testEditor({ contentBefore: '

ab[]

cd

', stepFunction: deleteForward, contentAfter: '

ab[]cd

', }); }); test("should merge two paragraphs of different classes, each containing spans of different classes without merging the spans", async () => { await testEditor({ contentBefore: '

ab[]

cd

', stepFunction: deleteForward, contentAfter: '

ab[]cd

', }); }); test("should delete a line break between two spans with bold and merge these formats", async () => { await testEditor({ contentBefore: '

ab[]
cd

', stepFunction: deleteForward, contentAfter: '

ab[]cd

', }); }); test("should delete a character in a span with bold, then a line break between two spans with bold and merge these formats", async () => { await testEditor({ contentBefore: '

a[]b

cde

', stepFunction: async (editor) => { deleteForward(editor); deleteForward(editor); }, contentAfter: '

a[]
cde

', }); }); }); describe("Nested editable zone (inside contenteditable=false element)", () => { test("should not remove the uneditable nesting zone nor the editable nested zone if the last element of the nested zone is empty", async () => { await testEditor({ contentBefore: unformat(`

[]

`), stepFunction: deleteForward, contentAfter: unformat(`

[]

`), }); }); test("should not remove the uneditable nesting zone nor the editable nested zone even if there is a paragraph before", async () => { await testEditor({ contentBefore: unformat(`

content

[]

`), stepFunction: deleteForward, contentAfter: unformat(`

content

[]

`), }); }); test("should not remove the uneditable nesting zone nor the editable nested zone if the last element of the nested zone is not empty", async () => { await testEditor({ contentBefore: unformat(`

content[]

`), stepFunction: deleteForward, contentAfter: unformat(`

content[]

`), }); }); test("should remove the uneditable nesting zone from the outside", async () => { await testEditor({ contentBefore: unformat(`

content[]

content

`), stepFunction: deleteForward, contentAfter: unformat(`

content[]

`), }); }); }); describe("POC extra tests", () => { test("should not remove a table without selecting it", async () => { await testEditor({ contentBefore: unformat( `

ab[]

cdef
ghij

kl

` ), stepFunction: deleteForward, contentAfter: unformat( `

ab[]

cdef
ghij

kl

` ), }); }); test("should not merge a table into its next sibling", async () => { await testEditor({ contentBefore: unformat( `

ab

cdef
ghij[]

kl

` ), stepFunction: deleteForward, contentAfter: unformat( `

ab

cdef
ghij[]

kl

` ), }); }); test("should delete the list item", async () => { await testEditor({ contentBefore: unformat( `
  • [a
  • b
  • c]
  • A
  • B
  • C
` ), stepFunction: deleteForward, contentAfter: unformat( `
  • []
  • A
  • B
  • C
` ), }); }); }); }); describe("Selection not collapsed", () => { test("should delete part of the text within a paragraph (forward, forward selection)", async () => { // Forward selection await testEditor({ contentBefore: "

ab[cd]ef

", stepFunction: deleteForward, contentAfter: "

ab[]ef

", }); }); test("should delete part of the text within a paragraph (forward, backward selection)", async () => { // Backward selection await testEditor({ contentBefore: "

ab]cd[ef

", stepFunction: deleteForward, contentAfter: "

ab[]ef

", }); }); test("should merge node correctly", async () => { await testEditor({ contentBefore: '
ab[c

d]e

f
', stepFunction: deleteForward, contentAfter: '
ab[]e
f
', }); }); test("should delete part of the text across two paragraphs (forward, forward selection)", async () => { await testEditor({ contentBefore: "
a

b[c

d]e

f
", stepFunction: deleteForward, contentAfter: "
a

b[]e

f
", }); }); test("should delete part of the text across two paragraphs (forward, backward selection)", async () => { await testEditor({ contentBefore: "
a

b]c

d[e

f
", stepFunction: deleteForward, contentAfter: "
a

b[]e

f
", }); }); test("should not delete single remaining empty inline", async () => { // Forward selection await testEditor({ contentBefore: "

[abcdef]

", stepFunction: deleteForward, // The flagged 200B is there to preserve the font so if we // write now, we still write in the font element's style. contentAfterEdit: '

[]\u200B

', // The flagged 200B is removed by the sanitizer if its // parent remains empty. contentAfter: "

[]

", }); }); test("should not delete styling nodes if not selected", async () => { await testEditor({ contentBefore: '

a[bcde]f

', stepFunction: deleteForward, contentAfter: '

a[]\u200Bf

', }); }); test("should delete styling nodes when delete if empty (forward, with space around inline)", async () => { await testEditor({ contentBefore: '

ab [cd] ef

', stepFunction: async (editor) => { deleteForward(editor); deleteForward(editor); }, contentAfter: "

ab []ef

", }); }); test("should delete styling nodes when delete if empty (forward)", async () => { await testEditor({ contentBefore: '

uv[wx]yz

', stepFunction: async (editor) => { deleteForward(editor); deleteForward(editor); }, contentAfter: "

uv[]z

", }); }); test("should delete across two paragraphs", async () => { // Forward selection await testEditor({ contentBefore: "

ab[cd

ef]gh

", stepFunction: deleteForward, contentAfter: "

ab[]gh

", }); // Backward selection await testEditor({ contentBefore: "

ab]cd

ef[gh

", stepFunction: deleteForward, contentAfter: "

ab[]gh

", }); }); test("should delete all the text in a paragraph", async () => { // Forward selection await testEditor({ contentBefore: "

[abc]

", stepFunction: deleteForward, contentAfter: "

[]

", }); // Backward selection await testEditor({ contentBefore: "

]abc[

", stepFunction: deleteForward, contentAfter: "

[]

", }); }); test("should delete a complex selection accross format nodes and multiple paragraphs", async () => { // Forward selection await testEditor({ contentBefore: "

ab[cd

ef
gh
ijkl]mn

", stepFunction: deleteForward, contentAfter: "

ab[]mn

", }); await testEditor({ contentBefore: "

ab[cd

ef
gh
ijk]lmn

", stepFunction: deleteForward, contentAfter: "

ab[]lmn

", }); // Backward selection await testEditor({ contentBefore: "

ab]cd

ef
gh
ijkl[mn

", stepFunction: deleteForward, contentAfter: "

ab[]mn

", }); await testEditor({ contentBefore: "

ab]cd

ef
gh
ijk[lmn

", stepFunction: deleteForward, contentAfter: "

ab[]lmn

", }); }); test("should delete all contents of a complex DOM with format nodes and multiple paragraphs (forward, forward selection)", async () => { await testEditor({ contentBefore: "

[abcd

ef
gh
ijklmn]

", stepFunction: deleteForward, contentAfter: "

[]

", }); }); test("should delete all contents of a complex DOM with format nodes and multiple paragraphs (forward, backward selection)", async () => { await testEditor({ contentBefore: "

]abcd

ef
gh
ijklmn[

", stepFunction: deleteForward, contentAfter: "

[]

", }); }); test("should delete a selection accross a heading1 and a paragraph", async () => { // Forward selection await testEditor({ contentBefore: "

ab [cd

ef]gh

", stepFunction: deleteForward, contentAfter: "

ab []gh

", }); // Backward selection await testEditor({ contentBefore: "

ab ]cd

ef[gh

", stepFunction: deleteForward, contentAfter: "

ab []gh

", }); }); test("should delete a selection from the beginning of a heading1 with a format to the middle of a paragraph + start of editable", async () => { //Forward selection await testEditor({ contentBefore: "

[abcd

ef]gh1

", stepFunction: deleteForward, contentAfter: "

[]gh1

", }); await testEditor({ contentBefore: "

[abcd

ef]gh2

", stepFunction: deleteForward, contentAfter: "

[]gh2

", }); // Backward selection await testEditor({ contentBefore: "

]abcd

ef[gh3

", stepFunction: deleteForward, contentAfter: "

[]gh3

", }); await testEditor({ contentBefore: "

]abcd

ef[gh4

", stepFunction: deleteForward, contentAfter: "

[]gh4

", }); }); test("should delete a selection from the beginning of a heading1 with a format to the middle of a paragraph + content", async () => { await testEditor({ contentBefore: "

content

[abcd

ef]gh1

", stepFunction: deleteForward, contentAfter: "

content

[]gh1

", }); await testEditor({ contentBefore: "

content

[abcd

ef]gh2

", stepFunction: deleteForward, contentAfter: "

content

[]gh2

", }); }); test("should delete a selection from the beginning of a heading1 to the end of a paragraph", async () => { //Forward selection await testEditor({ contentBefore: "

[abcd

ef]

1

", stepFunction: deleteForward, contentAfter: "

[]

1

", }); await testEditor({ contentBefore: "

[abcd

ef]

2

", stepFunction: deleteForward, contentAfter: "

[]

2

", }); // Backward selection await testEditor({ contentBefore: "

]abcd

ef[

3

", stepFunction: deleteForward, contentAfter: "

[]

3

", }); await testEditor({ contentBefore: "

]abcd

ef[

4

", stepFunction: deleteForward, contentAfter: "

[]

4

", }); }); test("should delete a selection from the beginning of a heading1 with a format to the end of a paragraph", async () => { //Forward selection await testEditor({ contentBefore: "

[abcd

ef]

1

", stepFunction: deleteForward, contentAfterEdit: '

[]\u200B

1

', contentAfter: "

[]

1

", }); await testEditor({ contentBefore: "

[abcd

ef]

2

", stepFunction: deleteForward, contentAfterEdit: '

[]\u200B

2

', contentAfter: "

[]

2

", }); // Backward selection await testEditor({ contentBefore: "

]abcd

ef[

3

", stepFunction: deleteForward, contentAfterEdit: '

[]\u200B

3

', contentAfter: "

[]

3

", }); await testEditor({ contentBefore: "

]abcd

ef[

4

", stepFunction: deleteForward, contentAfterEdit: '

[]\u200B

4

', contentAfter: "

[]

4

", }); }); test("should delete a heading (triple click delete) (1)", async () => { const { editor, el } = await setupEditor("

abc

def

", {}); tripleClick(el.querySelector("h1")); await microTick(); // Chrome puts the cursor at the start of next sibling expect(getContent(el)).toBe("

[abc

]def

"); await tick(); // The Editor corrects it on selection change expect(getContent(el)).toBe("

[abc]

def

"); deleteForward(editor); expect(getContent(el)).toBe( '

[]

def

' ); }); test("should delete a heading (triple click delete) (2)", async () => { const { editor, el } = await setupEditor("

abc


def

", {}); tripleClick(el.querySelector("h1")); await microTick(); // Chrome puts the cursor at the start of next sibling expect(getContent(el)).toBe("

[abc

]

def

"); await tick(); // The Editor corrects it on selection change expect(getContent(el)).toBe("

[abc]


def

"); deleteForward(editor); expect(getContent(el)).toBe( '

[]


def

' ); }); test("should delete last character of paragraph, as well as selected paragraph break", async () => { await testEditor({ contentBefore: "

ab[c

]def

", stepFunction: deleteForward, contentAfter: "

ab[]def

", }); }); test("should delete first character of paragraph, as well as selected paragraph break", async () => { await testEditor({ contentBefore: "

abc[

d]ef

", stepFunction: deleteForward, contentAfter: "

abc[]ef

", }); }); test("should remove a fully selected table", async () => { await testEditor({ contentBefore: unformat( `

a[b

cdef
ghij

k]l

` ), stepFunction: deleteForward, contentAfter: "

a[]l

", }); }); test("should only remove the text content of cells in a partly selected table", async () => { await testEditor({ contentBefore: unformat( `
cde[fgh
ijk]lmn
opqrst
` ), stepFunction: deleteForward, contentAfter: unformat( `
cd[]
gh
ij
mn
opqrst
` ), }); }); test("should remove some text and a table (even if the table is partly selected)", async () => { await testEditor({ contentBefore: unformat( `

a[b

cdef
g]hij

kl

` ), stepFunction: deleteForward, contentAfter: unformat( `

a[]

kl

` ), }); }); test("should remove a table and some text (even if the table is partly selected)", async () => { await testEditor({ contentBefore: unformat( `

ab

cdef
ghi[j

k]l

` ), stepFunction: deleteForward, contentAfter: unformat( `

ab

[]l

` ), }); }); test("should remove some text, a table and some more text", async () => { await testEditor({ contentBefore: unformat( `

a[b

cdef
ghij

k]l

` ), stepFunction: deleteForward, contentAfter: `

a[]l

`, }); }); test("should remove a selection of several tables", async () => { await testEditor({ contentBefore: unformat( `
cde[f
ghij
cdef
ghij
cde]f
ghij
` ), stepFunction: deleteForward, contentAfter: `

[]

`, }); }); test("should remove a selection including several tables", async () => { await testEditor({ contentBefore: unformat( `

0[1

cdef
ghij

23

cdef
ghij

45

cdef
ghij

67]

` ), stepFunction: deleteForward, contentAfter: `

0[]

`, }); }); test("should remove everything, including several tables", async () => { await testEditor({ contentBefore: unformat( `

[01

cdef
ghij

23

cdef
ghij

45

cdef
ghij

67]

` ), stepFunction: deleteForward, contentAfter: `

[]

`, }); }); test("should empty an inline unremovable but remain in it", async () => { await testEditor({ contentBefore: '

ab[cd]ef

', stepFunction: deleteForward, contentAfter: '

ab[]\u200Bef

', }); }); test("should remove element which is contenteditable=true even if their parent is contenteditable=false", async () => { await testEditor({ contentBefore: unformat(`

before[o

intruder

o]after

`), stepFunction: async (editor) => { deleteForward(editor); }, contentAfter: unformat(`

before[]after

`), }); }); test("should extend the range to fully include contenteditable=false that are partially selected at the end of the range", async () => { await testEditor({ contentBefore: unformat(`

before[o

intruder]

after

`), stepFunction: async (editor) => { deleteForward(editor); }, contentAfter: unformat(`

before[]

after

`), }); }); // @todo @phoenix: review this spec. It should not merge, like the test above. test("should extend the range to fully include contenteditable=false that are partially selected at the start of the range", async () => { await testEditor({ contentBefore: unformat(`

before

[intruder

o]after

`), stepFunction: async (editor) => { deleteForward(editor); }, contentAfter: unformat(`

before[]after

`), }); }); test("should remove empty paragraph and content from the second one", async () => { await testEditor({ contentBefore: "

ab

[

d]ef

", stepFunction: deleteForward, contentAfter: "

ab

[]ef

", }); }); });