From 8556b6d35eb5386879da84ae6515087e48cde919 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Geelen=20=28sge=29?= Date: Thu, 9 Jun 2022 12:47:37 +0000 Subject: [PATCH] [FIX] web_editor: adapt unit tests * Adapt Unit tests to always have a container block element * Add some tests to ensure the editable root direct child nodes are all blocks * Fix paste HTML unit tests to properly check that we never paste inline elements or text nodes outside the target container Part-of: odoo/odoo#95347 # Conflicts: # addons/web_editor/static/lib/odoo-editor/test/spec/collab.test.js # addons/web_editor/static/lib/odoo-editor/test/spec/copyPaste.test.js # addons/web_editor/static/lib/odoo-editor/test/spec/editor.test.js Part-of: odoo/odoo#95445 --- .../odoo-editor/test/spec/collab.test.js | 58 ++-- .../odoo-editor/test/spec/copyPaste.test.js | 56 ++-- .../odoo-editor/test/spec/editor.test.js | 253 +++++++++++------- .../odoo-editor/test/spec/insertHTML.test.js | 32 +-- 4 files changed, 243 insertions(+), 156 deletions(-) diff --git a/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/collab.test.js b/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/collab.test.js index 72a285849c6..1524b7dea1d 100644 --- a/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/collab.test.js +++ b/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/collab.test.js @@ -56,9 +56,9 @@ const testSameHistory = clientInfos => { window.chai .expect(firstClientInfo.editor._historySteps[i].id) .to.be.equal( - clientInfo.editor._historySteps[i].id, - `History steps are not consistent accross clients.`, - ); + clientInfo.editor._historySteps[i].id, + `History steps are not consistent accross clients.`, + ); } catch (e) { console.log( 'Clients steps:', @@ -233,7 +233,7 @@ describe('Collaboration', () => { it('all client steps should be on the same order', () => { testMultiEditor({ clientIds: ['c1', 'c2', 'c3'], - contentBefore: 'a[c1}{c1]e[c2}{c2]i[c3}{c3]', + contentBefore: '

a[c1}{c1]e[c2}{c2]i[c3}{c3]

', afterCreate: clientInfos => { applyConcurentActions(clientInfos, { c1: editor => { @@ -255,7 +255,7 @@ describe('Collaboration', () => { mergeClientsSteps(clientInfos); testSameHistory(clientInfos); }, - contentAfter: 'abcd[c1}{c1]efgh[c2}{c2]ijkl[c3}{c3]', + contentAfter: '

abcd[c1}{c1]efgh[c2}{c2]ijkl[c3}{c3]

', }); }); it('should 2 client insertText in 2 different paragraph', () => { @@ -301,7 +301,7 @@ describe('Collaboration', () => { it('should insertText with client 1 and deleteBackward with client 2', () => { testMultiEditor({ clientIds: ['c1', 'c2'], - contentBefore: 'ab[c1}{c1][c2}{c2]c', + contentBefore: '

ab[c1}{c1][c2}{c2]c

', afterCreate: clientInfos => { applyConcurentActions(clientInfos, { c1: editor => { @@ -314,13 +314,13 @@ describe('Collaboration', () => { mergeClientsSteps(clientInfos); testSameHistory(clientInfos); }, - contentAfter: 'a[c2}{c2]c[c1}{c1]dc', + contentAfter: '

a[c2}{c2]c[c1}{c1]dc

', }); }); it('should insertText twice with client 1 and deleteBackward twice with client 2', () => { testMultiEditor({ clientIds: ['c1', 'c2'], - contentBefore: 'ab[c1}{c1][c2}{c2]c', + contentBefore: '

ab[c1}{c1][c2}{c2]c

', afterCreate: clientInfos => { applyConcurentActions(clientInfos, { c1: editor => { @@ -335,14 +335,14 @@ describe('Collaboration', () => { mergeClientsSteps(clientInfos); testSameHistory(clientInfos); }, - contentAfter: '[c2}{c2]cd[c1}{c1]ec', + contentAfter: '

[c2}{c2]cd[c1}{c1]ec

', }); }); }); it('should reset from snapshot', () => { testMultiEditor({ clientIds: ['c1', 'c2'], - contentBefore: 'a[c1}{c1]', + contentBefore: '

a[c1}{c1]

', afterCreate: clientInfos => { clientInfos.c1.editor.execCommand('insertText', 'b'); clientInfos.c1.editor._historyMakeSnapshot(); @@ -356,16 +356,16 @@ describe('Collaboration', () => { ]); chai.expect( clientInfos.c2.editor._historySteps[0].mutations.map(x => x.id), - ).to.deep.equal(['fake_id_1', 'fake_concurent_id_1']); + ).to.deep.equal(['fake_id_1']); }, - contentAfter: 'ab[c1}{c1]', + contentAfter: '

ab[c1}{c1]

', }); }); describe('steps whith no parent in history', () => { it('should be able to retreive steps when disconnected from clients that has send step', () => { testMultiEditor({ clientIds: ['c1', 'c2', 'c3'], - contentBefore: 'a[c1}{c1]b[c2}{c2]c[c3}{c3]', + contentBefore: '

a[c1}{c1]b[c2}{c2]c[c3}{c3]

', afterCreate: clientInfos => { clientInfos.c1.editor.execCommand('insertText', 'd'); clientInfos.c2.editor.onExternalHistorySteps([ @@ -384,13 +384,13 @@ describe('Collaboration', () => { ]); testSameHistory(clientInfos); }, - contentAfter: 'ad[c1}{c1]be[c2}{c2]c[c3}{c3]', + contentAfter: '

ad[c1}{c1]be[c2}{c2]c[c3}{c3]

', }); }); it('should receive steps where parent was not received', () => { testMultiEditor({ clientIds: ['c1', 'c2', 'c3'], - contentBefore: 'a[c1}{c1]b[c2}{c2]', + contentBefore: '

a[c1}{c1]b[c2}{c2]

', afterCreate: clientInfos => { clientInfos.c1.editor.execCommand('insertText', 'c'); clientInfos.c2.editor.onExternalHistorySteps([ @@ -425,7 +425,7 @@ describe('Collaboration', () => { clientInfos.c2.editor._historySteps[4], ]); }, - contentAfter: 'ac[c1}{c1]bdef[c2}{c2]', + contentAfter: '

ac[c1}{c1]bdef[c2}{c2]

', }); }); }); @@ -433,7 +433,7 @@ describe('Collaboration', () => { it('should sanitize when adding a node', () => { testMultiEditor({ clientIds: ['c1', 'c2'], - contentBefore: 'a', + contentBefore: '

a

', afterCreate: clientInfos => { const script = document.createElement('script'); script.innerHTML = 'console.log("xss")'; @@ -445,14 +445,14 @@ describe('Collaboration', () => { ]); window.chai .expect(clientInfos.c2.editor.editable.innerHTML) - .to.equal('a'); + .to.equal('

a

'); }, }); }); it('should sanitize when adding a script as descendant', async () => { testMultiEditor({ clientIds: ['c1', 'c2'], - contentBefore: 'a[c1}{c1][c2}{c2]', + contentBefore: '

a[c1}{c1][c2}{c2]

', afterCreate: clientInfos => { const i = document.createElement('i'); i.innerHTML = 'b'; @@ -464,7 +464,7 @@ describe('Collaboration', () => { }, afterCursorInserted: clientInfos => { chai.expect(clientInfos.c2.editable.innerHTML).to.equal( - 'a[c1}{c1][c2}{c2]b', + '

a[c1}{c1][c2}{c2]

b', ); }, }); @@ -472,7 +472,7 @@ describe('Collaboration', () => { it('should sanitize when changing an attribute', () => { testMultiEditor({ clientIds: ['c1', 'c2'], - contentBefore: 'a', + contentBefore: '

a

', afterCreate: clientInfos => { const img = clientInfos.c1.editable.childNodes[0].childNodes[1]; img.setAttribute('class', 'b'); @@ -483,10 +483,10 @@ describe('Collaboration', () => { ]); window.chai .expect(clientInfos.c1.editor.editable.innerHTML) - .to.equal('a'); + .to.equal('

a

'); window.chai .expect(clientInfos.c2.editor.editable.innerHTML) - .to.equal('a'); + .to.equal('

a

'); }, }); }); @@ -494,7 +494,7 @@ describe('Collaboration', () => { it('should sanitize when undo is adding a script node', () => { testMultiEditor({ clientIds: ['c1', 'c2'], - contentBefore: 'a', + contentBefore: '

a

', afterCreate: clientInfos => { const script = document.createElement('script'); script.innerHTML = 'console.log("xss")'; @@ -513,14 +513,14 @@ describe('Collaboration', () => { clientInfos.c2.editor.historyUndo(); window.chai .expect(clientInfos.c2.editor.editable.innerHTML) - .to.equal('a'); + .to.equal('

a

'); }, }); }); it('should sanitize when undo is adding a descendant script node', () => { testMultiEditor({ clientIds: ['c1', 'c2'], - contentBefore: 'a', + contentBefore: '

a

', afterCreate: clientInfos => { const div = document.createElement('div'); div.innerHTML = 'b'; @@ -539,14 +539,14 @@ describe('Collaboration', () => { clientInfos.c2.editor.historyUndo(); window.chai .expect(clientInfos.c2.editor.editable.innerHTML) - .to.equal('a
b
'); + .to.equal('

a

b
'); }, }); }); it('should sanitize when undo is changing an attribute', () => { testMultiEditor({ clientIds: ['c1', 'c2'], - contentBefore: 'a', + contentBefore: '

a

', afterCreate: clientInfos => { const img = clientInfos.c1.editable.childNodes[0].childNodes[1]; img.setAttribute('class', 'b'); @@ -566,7 +566,7 @@ describe('Collaboration', () => { clientInfos.c2.editor.historyUndo(); window.chai .expect(clientInfos.c2.editor.editable.innerHTML) - .to.equal('a'); + .to.equal('

a

'); }, }); }); diff --git a/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/copyPaste.test.js b/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/copyPaste.test.js index 98c8243f9b9..71ba3e39669 100644 --- a/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/copyPaste.test.js +++ b/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/copyPaste.test.js @@ -26,70 +26,90 @@ describe('Copy and paste', () => { describe('Html Paste cleaning', () => { describe('whitelist', async () => { it('should keep whitelisted Tags tag', async () => { + for (const node of CLIPBOARD_WHITELISTS.nodes) { + if (!['TABLE', 'THEAD', 'TH', 'TBODY', 'TR', 'TD', 'IMG', 'BR', 'LI', '.fa'].includes(node)) { + const isInline = ['I', 'B', 'U', 'EM', 'STRONG', 'IMG', 'BR', 'A'].includes(node) + const html = isInline ? `a<${node.toLowerCase()}>bc` : `a

<${node.toLowerCase()}>b

c`; + + await testEditor(BasicEditor, { + contentBefore: '

123[]4

', + stepFunction: async editor => { + await pasteHtml(editor, `a<${node.toLowerCase()}>bc`); + }, + contentAfter: '

123' + html + '[]4

', + }); + } + } + + }); + it('should keep whitelisted Tags tag (2)', async () => { const tagsToKeep = [ 'ad', // img tag 'a
b' // br tags ]; - for (const node of CLIPBOARD_WHITELISTS.nodes) { - if (!['TABLE', 'THEAD', 'TH', 'TBODY', 'TR', 'TD', 'IMG', 'BR', 'LI', '.fa'].includes(node)) { - tagsToKeep.push(`a<${node.toLowerCase()}>bc`); - } - } - for (const tagToKeep of tagsToKeep) { await testEditor(BasicEditor, { - contentBefore: '123[]', + contentBefore: '

123[]

', stepFunction: async editor => { await pasteHtml(editor, tagToKeep); }, - contentAfter: '123' + tagToKeep + '[]', + contentAfter: '

123' + tagToKeep + '[]

', }); } }); it('should keep tables Tags tag and add classes', async () => { await testEditor(BasicEditor, { - contentBefore: '123[]', + contentBefore: '

123[]

', stepFunction: async editor => { await pasteHtml(editor, 'a
h
b
d'); }, - contentAfter: '123a
h
b
d[]', + contentAfter: '

123a

h
b

d[]

', }); }); it('should not keep span', async () => { await testEditor(BasicEditor, { - contentBefore: '123[]', + contentBefore: '

123[]

', stepFunction: async editor => { await pasteHtml(editor, 'abcd'); }, - contentAfter: '123abcd[]', + contentAfter: '

123abcd[]

', }); }); it('should not keep orphan LI', async () => { await testEditor(BasicEditor, { - contentBefore: '123[]', + contentBefore: '

123[]

', stepFunction: async editor => { await pasteHtml(editor, 'a
  • bc
  • d'); }, - contentAfter: '123a

    bc

    d[]', + contentAfter: '

    123a

    bc

    d[]

    ', }); }); it('should keep LI in UL', async () => { await testEditor(BasicEditor, { - contentBefore: '123[]', + contentBefore: '

    123[]

    ', stepFunction: async editor => { await pasteHtml(editor, 'ad'); }, - contentAfter: '123ad[]', + contentAfter: '

    123a

    d[]

    ', + }); + }); + it('should keep P and B and not span', async () => { + await testEditor(BasicEditor, { + contentBefore: '

    123[]xx

    ', + stepFunction: async editor => { + await pasteHtml(editor, 'a

    bc

    defgh'); + }, + contentAfter: '

    123a

    bc

    defgh[]xx

    ', }); }); it('should keep styled span', async () => { await testEditor(BasicEditor, { - contentBefore: '123[]', + contentBefore: '

    123[]

    ', stepFunction: async editor => { await pasteHtml(editor, 'abcd'); }, - contentAfter: '123abcd[]', + contentAfter: '

    123abcd[]

    ', }); }); }); diff --git a/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/editor.test.js b/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/editor.test.js index a55b460fc6a..3a214752a63 100644 --- a/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/editor.test.js +++ b/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/editor.test.js @@ -21,6 +21,70 @@ async function twoDeleteForward(editor) { } describe('Editor', () => { + describe('init', () => { + describe('No orphan inline elements compatibility mode', () => { + it('should transform root
    into

    ', async () => { + await testEditor(BasicEditor, { + contentBefore: 'ab
    c', + contentAfter: '

    ab

    c

    ', + }); + }); + it('should keep
    if necessary', async () => { + await testEditor(BasicEditor, { + contentBefore: 'ab

    c', + contentAfter: '

    ab


    c

    ', + }); + }); + it('should keep multiple conecutive
    if necessary', async () => { + await testEditor(BasicEditor, { + contentBefore: 'ab



    c', + contentAfter: '

    ab




    c

    ', + }); + }); + it('should transform complex
    ', async () => { + await testEditor(BasicEditor, { + contentBefore: 'ab
    c
    dxxxe
    f', + contentAfter: '

    ab

    c

    dxxxe

    f

    ', + }); + }); + it('should transform complex
    + keep li ', async () => { + await testEditor(BasicEditor, { + contentBefore: 'ab
    c f
    g', + contentAfter: '

    ab

    c

    f

    g

    ', + }); + }); + it('should not transform
    inside

    ', async () => { + await testEditor(BasicEditor, { + contentBefore: '

    ab
    c

    ', + contentAfter: '

    ab
    c

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

    ab
    c

    d

    ', + contentAfter: '

    ab
    c

    d

    ', + }); + await testEditor(BasicEditor, { + contentBefore: 'xx

    ab
    c

    d
    yy', + contentAfter: '

    xx

    ab
    c

    d

    yy

    ', + }); + }); + it('should not transform indentation', async () => { + await testEditor(BasicEditor, { + contentBefore: ` +

    ab

    +

    c

    `, + contentAfter: ` +

    ab

    +

    c

    `, + }); + }); + it('should transform root .fa', async () => { + await testEditor(BasicEditor, { + contentBefore: '

    ab

    c

    ', + contentAfter: '

    ab

    c

    ', + }); + }); + }); + }); describe('deleteForward', () => { describe('Selection collapsed', () => { describe('Basic', () => { @@ -310,6 +374,11 @@ X[] stepFunction: deleteForward, contentAfter: '

    abc[]def

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

    abc[]

    def

    orphan node', + stepFunction: deleteForward, + contentAfter: '

    abc[]def

    orphan node

    ', + }); }); it('should delete the space if the second

    is display inline', async () => { await testEditor(BasicEditor, { @@ -1367,11 +1436,11 @@ X[] }); it('should remove a fontawesome', async () => { await testEditor(BasicEditor, { - contentBefore: `

    abc

    []def

    `, + contentBefore: `

    abc

    []def

    `, stepFunction: async editor => { await deleteBackward(editor); }, - contentAfter: `

    abc

    []def

    `, + contentAfter: `

    abc

    []def

    `, }); }); it('should remove a media element', async () => { @@ -1826,14 +1895,14 @@ X[] }); it('should merge a text preceding a paragraph (removing the paragraph)', async () => { await testEditor(BasicEditor, { - contentBefore: 'ab

    []cd

    ', + contentBefore: '
    ab

    []cd

    ', stepFunction: deleteBackward, - contentAfter: 'ab[]cd', + contentAfter: '
    ab[]cd
    ', }); await testEditor(BasicEditor, { - contentBefore: 'ab

    []cd

    ef', + contentBefore: '
    ab

    []cd

    ef
    ', stepFunction: deleteBackward, - contentAfter: 'ab[]cd
    ef', + contentAfter: '
    ab[]cd
    ef
    ', }); }); }); @@ -1985,9 +2054,9 @@ X[] }); it('should ignore empty inline node between blocks being merged', async () => { await testEditor(BasicEditor, { - contentBefore: '

    abc

    []def

    ', + contentBefore: '

    abc

    []def

    ', stepFunction: deleteBackward, - contentAfter: '

    abc[]def

    ', + contentAfter: '

    abc[]def

    ', }); }); it('should merge in nested paragraphs and remove invisible inline content', async () => { @@ -3363,7 +3432,7 @@ X[] contentAfter: '

    a http://test.com b http://test.com  [] c http://test.com d

    ', }); }); - }) + }); describe('history', () => { describe('undo', () => { @@ -3537,252 +3606,250 @@ X[] describe('ArrowRight', () => { it('should move past a zws (collapsed)', async () => { await testEditor(BasicEditor, { - contentBefore: 'ab[]\u200Bcd', + contentBefore: '

    ab[]\u200Bcd

    ', stepFunction: async editor => { triggerEvent(editor.editable, 'keydown', { key: 'ArrowRight'}); }, - contentAfter: 'ab\u200B[]cd', - // Final state: 'ab\u200Bc[]d' + contentAfter: '

    ab\u200B[]cd

    ', + // Final state: '

    ab\u200Bc[]d

    ' }); await testEditor(BasicEditor, { - contentBefore: 'ab[]\u200Bcd', + contentBefore: '

    ab[]\u200Bcd

    ', stepFunction: async editor => { triggerEvent(editor.editable, 'keydown', { key: 'ArrowRight'}); }, - contentAfter: 'ab\u200B[]cd', - // Final state: 'ab\u200Bc[]d' + contentAfter: '

    ab\u200B[]cd

    ', + // Final state: '

    ab\u200Bc[]d

    ' }); }); it('should select a zws', async () => { await testEditor(BasicEditor, { - contentBefore: '[ab]\u200Bcd', + contentBefore: '

    [ab]\u200Bcd

    ', stepFunction: async editor => { triggerEvent(editor.editable, 'keydown', { key: 'ArrowRight', shiftKey: true }); }, - contentAfter: '[ab\u200B]cd', - // Final state: '[ab\u200Bc]d' + contentAfter: '

    [ab\u200B]cd

    ', + // Final state: '

    [ab\u200Bc]d

    ' }); await testEditor(BasicEditor, { - contentBefore: '[ab]\u200Bcd', + contentBefore: '

    [ab]\u200Bcd

    ', stepFunction: async editor => { triggerEvent(editor.editable, 'keydown', { key: 'ArrowRight', shiftKey: true }); }, - contentAfter: '[ab\u200B]cd', - // Final state: '[ab\u200Bc]d' + contentAfter: '

    [ab\u200B]cd

    ', + // Final state: '

    [ab\u200Bc]d

    ' }); }); it('should select a zws (2)', async () => { await testEditor(BasicEditor, { - contentBefore: 'a[b]\u200Bcd', + contentBefore: '

    a[b]\u200Bcd

    ', stepFunction: async editor => { triggerEvent(editor.editable, 'keydown', { key: 'ArrowRight', shiftKey: true }); }, - contentAfter: 'a[b\u200B]cd', - // Final state: 'a[b\u200Bc]d' + contentAfter: '

    a[b\u200B]cd

    ', + // Final state: '

    a[b\u200Bc]d

    ' }); await testEditor(BasicEditor, { - contentBefore: 'a[b]\u200Bcd', + contentBefore: '

    a[b]\u200Bcd

    ', stepFunction: async editor => { triggerEvent(editor.editable, 'keydown', { key: 'ArrowRight', shiftKey: true }); }, - contentAfter: 'a[b\u200B]cd', - // Final state: 'a[b\u200Bc]d' + contentAfter: '

    a[b\u200B]cd

    ', + // Final state: '

    a[b\u200Bc]d

    ' }); }); it('should select a zws (3)', async () => { await testEditor(BasicEditor, { - contentBefore: 'ab[]\u200Bcd', + contentBefore: '

    ab[]\u200Bcd

    ', stepFunction: async editor => { triggerEvent(editor.editable, 'keydown', { key: 'ArrowRight', shiftKey: true }); }, - contentAfter: 'ab[\u200B]cd', - // Final state: 'ab[\u200Bc]d' + contentAfter: '

    ab[\u200B]cd

    ', + // Final state: '

    ab[\u200Bc]d

    ' }); await testEditor(BasicEditor, { - contentBefore: 'ab[]\u200Bcd', + contentBefore: '

    ab[]\u200Bcd

    ', stepFunction: async editor => { triggerEvent(editor.editable, 'keydown', { key: 'ArrowRight', shiftKey: true }); }, - contentAfter: 'ab[\u200B]cd', - // Final state: 'ab[\u200Bc]d' + contentAfter: '

    ab[\u200B]cd

    ', + // Final state: '

    ab[\u200Bc]d

    ' }); }); it('should select a zws backwards', async () => { await testEditor(BasicEditor, { - contentBefore: 'ab]\u200B[cd', + contentBefore: '

    ab]\u200B[cd

    ', stepFunction: async editor => { triggerEvent(editor.editable, 'keydown', { key: 'ArrowRight', shiftKey: true }); }, - contentAfter: 'ab\u200B[]cd', - // Final state: 'ab\u200B[c]d' + contentAfter: '

    ab\u200B[]cd

    ', + // Final state: '

    ab\u200B[c]d

    ' }); await testEditor(BasicEditor, { - contentBefore: 'ab]\u200B[cd', + contentBefore: '

    ab]\u200B[cd

    ', stepFunction: async editor => { triggerEvent(editor.editable, 'keydown', { key: 'ArrowRight', shiftKey: true }); }, - contentAfter: 'ab\u200B[]cd', - // Final state: 'ab\u200B[c]d' + contentAfter: '

    ab\u200B[]cd

    ', + // Final state: '

    ab\u200B[c]d

    ' }); await testEditor(BasicEditor, { - contentBefore: 'ab]\u200B[cd', + contentBefore: '

    ab]\u200B[cd

    ', stepFunction: async editor => { triggerEvent(editor.editable, 'keydown', { key: 'ArrowRight', shiftKey: true }); }, - contentAfter: 'ab\u200B[]cd', - // Final state: 'ab\u200B[c]d' + contentAfter: '

    ab\u200B[]cd

    ', + // Final state: '

    ab\u200B[c]d

    ' }); await testEditor(BasicEditor, { - contentBefore: 'ab]\u200B[cd', + contentBefore: '

    ab]\u200B[cd

    ', stepFunction: async editor => { triggerEvent(editor.editable, 'keydown', { key: 'ArrowRight', shiftKey: true }); }, - contentAfter: 'ab\u200B[]cd', - // Final state: 'ab\u200B[c]d' + contentAfter: '

    ab\u200B[]cd

    ', + // Final state: '

    ab\u200B[c]d

    ' }); }); it('should select a zws backwards (2)', async () => { await testEditor(BasicEditor, { - contentBefore: 'ab]\u200Bc[d', + contentBefore: '

    ab]\u200Bc[d

    ', stepFunction: async editor => { triggerEvent(editor.editable, 'keydown', { key: 'ArrowRight', shiftKey: true }); }, - contentAfter: 'ab\u200B]c[d', - // Final state: 'ab\u200Bc[]d' + contentAfter: '

    ab\u200B]c[d

    ', + // Final state: '

    ab\u200Bc[]d

    ' }); await testEditor(BasicEditor, { - contentBefore: 'ab]\u200Bc[d', + contentBefore: '

    ab]\u200Bc[d

    ', stepFunction: async editor => { triggerEvent(editor.editable, 'keydown', { key: 'ArrowRight', shiftKey: true }); }, - contentAfter: 'ab\u200B]c[d', - // Final state: 'ab\u200Bc[]d' + contentAfter: '

    ab\u200B]c[d

    ', + // Final state: '

    ab\u200Bc[]d

    ' }); }); }); describe('ArrowLeft', () => { it('should move past a zws (collapsed)', async () => { await testEditor(BasicEditor, { - contentBefore: 'ab\u200B[]cd', + contentBefore: '

    ab\u200B[]cd

    ', stepFunction: async editor => { triggerEvent(editor.editable, 'keydown', { key: 'ArrowLeft'}); }, - contentAfter: 'ab[]\u200Bcd', - // Final state: 'a[]b\u200Bcd' + contentAfter: '

    ab[]\u200Bcd

    ', }); await testEditor(BasicEditor, { - contentBefore: 'ab\u200B[]cd', + contentBefore: '

    ab\u200B[]cd

    ', stepFunction: async editor => { triggerEvent(editor.editable, 'keydown', { key: 'ArrowLeft'}); }, - contentAfter: 'ab[]\u200Bcd', - // Final state: 'a[]b\u200Bcd' + contentAfter: '

    ab[]\u200Bcd

    ', }); }); it('should select a zws backwards', async () => { await testEditor(BasicEditor, { - contentBefore: 'ab\u200B[]cd', + contentBefore: '

    ab\u200B[]cd

    ', stepFunction: async editor => { triggerEvent(editor.editable, 'keydown', { key: 'ArrowLeft', shiftKey: true }); }, - contentAfter: 'ab]\u200B[cd', - // Final state: 'a]b\u200B[cd' + contentAfter: '

    ab]\u200B[cd

    ', + // Final state: '

    a]b\u200B[cd

    ' }); await testEditor(BasicEditor, { - contentBefore: 'ab\u200B[]cd', + contentBefore: '

    ab\u200B[]cd

    ', stepFunction: async editor => { triggerEvent(editor.editable, 'keydown', { key: 'ArrowLeft', shiftKey: true }); }, - contentAfter: 'ab]\u200B[cd', - // Final state: 'a]b\u200B[cd' + contentAfter: '

    ab]\u200B[cd

    ', + // Final state: '

    a]b\u200B[cd

    ' }); }); it('should select a zws backwards (2)', async () => { await testEditor(BasicEditor, { - contentBefore: 'ab\u200B]cd[', + contentBefore: '

    ab\u200B]cd[

    ', stepFunction: async editor => { triggerEvent(editor.editable, 'keydown', { key: 'ArrowLeft', shiftKey: true }); }, - contentAfter: 'ab]\u200Bcd[', - // Final state: 'a]b\u200Bcd[' + contentAfter: '

    ab]\u200Bcd[

    ', + // Final state: '

    a]b\u200Bcd[

    ' }); await testEditor(BasicEditor, { - contentBefore: 'ab\u200B]cd[', + contentBefore: '

    ab\u200B]cd[

    ', stepFunction: async editor => { triggerEvent(editor.editable, 'keydown', { key: 'ArrowLeft', shiftKey: true }); }, - contentAfter: 'ab]\u200Bcd[', - // Final state: 'a]b\u200Bcd[' + contentAfter: '

    ab]\u200Bcd[

    ', + // Final state: '

    a]b\u200Bcd[

    ' }); }); it('should select a zws backwards (3)', async () => { await testEditor(BasicEditor, { - contentBefore: 'ab\u200B]c[d', + contentBefore: '

    ab\u200B]c[d

    ', stepFunction: async editor => { triggerEvent(editor.editable, 'keydown', { key: 'ArrowLeft', shiftKey: true }); }, - contentAfter: 'ab]\u200Bc[d', - // Final state: 'a]b\u200Bc[d' + contentAfter: '

    ab]\u200Bc[d

    ', + // Final state: '

    a]b\u200Bc[d

    ' }); await testEditor(BasicEditor, { - contentBefore: 'ab\u200B]c[d', + contentBefore: '

    ab\u200B]c[d

    ', stepFunction: async editor => { triggerEvent(editor.editable, 'keydown', { key: 'ArrowLeft', shiftKey: true }); }, - contentAfter: 'ab]\u200Bc[d', - // Final state: 'a]b\u200Bc[d' + contentAfter: '

    ab]\u200Bc[d

    ', + // Final state: '

    a]b\u200Bc[d

    ' }); }); it('should deselect a zws', async () => { await testEditor(BasicEditor, { - contentBefore: 'ab[\u200B]cd', + contentBefore: '

    ab[\u200B]cd

    ', stepFunction: async editor => { triggerEvent(editor.editable, 'keydown', { key: 'ArrowLeft', shiftKey: true }); }, - contentAfter: 'ab[]\u200Bcd', - // Final state: 'a]b[\u200Bcd' + contentAfter: '

    ab[]\u200Bcd

    ', + // Final state: '

    a]b[\u200Bcd

    ' }); await testEditor(BasicEditor, { - contentBefore: 'ab[\u200B]cd', + contentBefore: '

    ab[\u200B]cd

    ', stepFunction: async editor => { triggerEvent(editor.editable, 'keydown', { key: 'ArrowLeft', shiftKey: true }); }, - contentAfter: 'ab[]\u200Bcd', - // Final state: 'a]b[\u200Bcd' + contentAfter: '

    ab[]\u200Bcd

    ', + // Final state: '

    a]b[\u200Bcd

    ' }); await testEditor(BasicEditor, { - contentBefore: 'ab[\u200B]cd', + contentBefore: '

    ab[\u200B]cd

    ', stepFunction: async editor => { triggerEvent(editor.editable, 'keydown', { key: 'ArrowLeft', shiftKey: true }); }, - contentAfter: 'ab[]\u200Bcd', - // Final state: 'a]b[\u200Bcd' + contentAfter: '

    ab[]\u200Bcd

    ', + // Final state: '

    a]b[\u200Bcd

    ' }); await testEditor(BasicEditor, { - contentBefore: 'ab[\u200B]cd', + contentBefore: '

    ab[\u200B]cd

    ', stepFunction: async editor => { triggerEvent(editor.editable, 'keydown', { key: 'ArrowLeft', shiftKey: true }); }, - contentAfter: 'ab[]\u200Bcd', - // Final state: 'a]b[\u200Bcd' + contentAfter: '

    ab[]\u200Bcd

    ', + // Final state: '

    a]b[\u200Bcd

    ' }); }); it('should deselect a zws (2)', async () => { await testEditor(BasicEditor, { - contentBefore: 'a[b\u200B]cd', + contentBefore: '

    a[b\u200B]cd

    ', stepFunction: async editor => { triggerEvent(editor.editable, 'keydown', { key: 'ArrowLeft', shiftKey: true }); }, - contentAfter: 'a[b]\u200Bcd', - // Final state: 'a[]b\u200Bcd' + contentAfter: '

    a[b]\u200Bcd

    ', + // Final state: '

    a[]b\u200Bcd

    ' }); await testEditor(BasicEditor, { - contentBefore: 'a[b\u200B]cd', + contentBefore: '

    a[b\u200B]cd

    ', stepFunction: async editor => { triggerEvent(editor.editable, 'keydown', { key: 'ArrowLeft', shiftKey: true }); }, - contentAfter: 'a[b]\u200Bcd', - // Final state: 'a[]b\u200Bcd' + contentAfter: '

    a[b]\u200Bcd

    ', + // Final state: '

    a[]b\u200Bcd

    ' }); }); }); diff --git a/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/insertHTML.test.js b/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/insertHTML.test.js index c82ae3d2b1f..dc382438aec 100644 --- a/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/insertHTML.test.js +++ b/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/insertHTML.test.js @@ -1,6 +1,6 @@ import { BasicEditor, testEditor } from '../utils.js'; -describe('insetHTML', () => { +describe('insert HTML', () => { describe('collapsed selection', () => { it('should insert html in an empty paragraph', async () => { await testEditor(BasicEditor, { @@ -37,55 +37,55 @@ describe('insetHTML', () => { }); it('should insert html in an empty editable', async () => { await testEditor(BasicEditor, { - contentBefore: '[]
    ', + contentBefore: '

    []

    ', stepFunction: async editor => { await editor.execCommand('insertHTML', ''); }, - contentAfterEdit: '\u200b[]
    ', - contentAfter: '[]
    ', + contentAfterEdit: '

    \u200b[]

    ', + contentAfter: '

    []

    ', }); }); it('should insert html in between naked text in the editable', async () => { await testEditor(BasicEditor, { - contentBefore: 'a[]b
    ', + contentBefore: '

    a[]b

    ', stepFunction: async editor => { await editor.execCommand('insertHTML', ''); }, contentAfterEdit: - 'a\u200b[]b
    ', - contentAfter: 'a[]b
    ', + '

    a\u200b[]b

    ', + contentAfter: '

    a[]b

    ', }); }); it('should insert several html nodes in between naked text in the editable', async () => { await testEditor(BasicEditor, { - contentBefore: 'a[]e
    ', + contentBefore: '

    a[]e

    ', stepFunction: async editor => { await editor.execCommand('insertHTML', '

    b

    c

    d

    '); }, - contentAfter: 'ab

    c

    d[]e
    ', + contentAfter: '

    ab

    c

    d[]e

    ', }); }); }); describe('not collapsed selection', () => { it('should delete selection and insert html in its place', async () => { await testEditor(BasicEditor, { - contentBefore: '[a]
    ', + contentBefore: '

    [a]

    ', stepFunction: async editor => { await editor.execCommand('insertHTML', ''); }, - contentAfterEdit: '\u200b[]
    ', - contentAfter: '[]
    ', + contentAfterEdit: '

    \u200b[]

    ', + contentAfter: '

    []

    ', }); }); - it('should delete selection and insert html in its place', async () => { + it('should delete selection and insert html in its place (2)', async () => { await testEditor(BasicEditor, { - contentBefore: 'a[b]c
    ', + contentBefore: '

    a[b]c

    ', stepFunction: async editor => { await editor.execCommand('insertHTML', ''); }, contentAfterEdit: - 'a\u200b[]c
    ', - contentAfter: 'a[]c
    ', + '

    a\u200b[]c

    ', + contentAfter: '

    a[]c

    ', }); }); });