From 7cefa81f7ea780696354ef863f00b24879de792c Mon Sep 17 00:00:00 2001 From: Antoine Guenet Date: Fri, 28 Jul 2023 16:33:34 +0200 Subject: [PATCH] [ADD] web_editor: add some tests for state preservation utils This adds a few tests for `getState`, `restoreState` and `enforceWhitespace`, regarding the handling of space visibility. Each test handles equivalent cases for all three functions. X-original-commit: 2f28f42640a7322eec35a4d48a1ec7ce287b77b3 Part-of: odoo/odoo#138549 --- .../js/editor/odoo-editor/src/utils/utils.js | 3 + .../odoo-editor/test/spec/utils.test.js | 276 ++++++++++++++++++ 2 files changed, 279 insertions(+) diff --git a/addons/web_editor/static/src/js/editor/odoo-editor/src/utils/utils.js b/addons/web_editor/static/src/js/editor/odoo-editor/src/utils/utils.js index d18c9989cc8..7df1708377a 100644 --- a/addons/web_editor/static/src/js/editor/odoo-editor/src/utils/utils.js +++ b/addons/web_editor/static/src/js/editor/odoo-editor/src/utils/utils.js @@ -2409,6 +2409,8 @@ const allRestoreStateRules = (function () { * @param {Object} prevStateData @see getState * @param {boolean} debug=false - if true, adds nicely formatted * console logs to help with debugging. + * @returns {Object|undefined} the rule that was applied to restore the state, + * if any, for testing purposes. */ export function restoreState(prevStateData, debug=false) { const { node, direction, cType: cType1, oldEditableHTML } = prevStateData; @@ -2449,6 +2451,7 @@ export function restoreState(prevStateData, debug=false) { const inverseDirection = direction === DIRECTIONS.LEFT ? DIRECTIONS.RIGHT : DIRECTIONS.LEFT; enforceWhitespace(el, offset, inverseDirection, rule); } + return rule; } /** * Enforces the whitespace and BR visibility in the given direction starting diff --git a/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/utils.test.js b/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/utils.test.js index 0f2f53d384f..31d60229dfd 100644 --- a/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/utils.test.js +++ b/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/utils.test.js @@ -35,6 +35,10 @@ import { DIRECTIONS, isBlock, isVisibleTextNode, + CTYPES, + getState, + restoreState, + enforceWhitespace, } from '../../src/utils/utils.js'; import { BasicEditor, @@ -1692,4 +1696,276 @@ describe('Utils', () => { window.chai.expect(res).to.be.true; }); }); + + //------------------------------------------------------------------------------ + // Prepare / Save / Restore state utilities + //------------------------------------------------------------------------------ + + describe('State preservation utilities', () => { + describe('getState', () => { + it('should recognize invisible space to the right', () => { + // We'll be looking to the right while standing at `a[] `. + const [p] = insertTestHtml('

a

'); + splitTextNode(p.firstChild, 1); // "a"" " + window.chai.expect(p.childNodes.length).to.eql(2); + const position = [p, 1]; // `

"a"[]" "

` + window.chai.expect(getState(...position, DIRECTIONS.RIGHT)).to.eql({ + // We look to the right of "a" (`a[] `): + node: p.firstChild, // "a" + direction: DIRECTIONS.RIGHT, + // The browser strips the space away so we ignore it and see + // `

`: the closing tag from the inside. + cType: CTYPES.BLOCK_INSIDE, + }); + }); + it('should recognize invisible space to the right (among consecutive space within content)', () => { + // We'll be looking to the right while standing at `a [] `. The + // first space is visible, the rest isn't. + const [p] = insertTestHtml('

a b

'); + splitTextNode(p.firstChild, 2); // "a "" b" + window.chai.expect(p.childNodes.length).to.eql(2); + const position = [p, 1]; // `

"a "[]" b"

` + window.chai.expect(getState(...position, DIRECTIONS.RIGHT)).to.eql({ + // We look to the right of "a " (`a []`): + node: p.firstChild, // "a " + direction: DIRECTIONS.RIGHT, + // The browser strips the space away so we ignore it and see + // "b": visible content. + cType: CTYPES.CONTENT, + }); + }); + it('should recognize visible space to the left (followed by consecutive space within content)', () => { + // We'll be looking to the left while standing at `[] b`. The + // first space is visible, the rest isn't. + const [p] = insertTestHtml('

a b

'); + splitTextNode(p.firstChild, 2); // "a "" b" + window.chai.expect(p.childNodes.length).to.eql(2); + const position = [p, 1]; // `

"a "[]" b"

` + window.chai.expect(getState(...position, DIRECTIONS.LEFT)).to.eql({ + // We look to the left of " b" (`[] b`): + node: p.lastChild, // "a" + direction: DIRECTIONS.LEFT, + // Left of " b" we see visible space that we should + // preserve. + cType: CTYPES.SPACE, + }); + }); + it('should recognize invisible space to the left (nothing after)', () => { + // We'll be looking to the left while standing at ` [] `. + const [p] = insertTestHtml('

'); + p.append(document.createTextNode('')); // " """ + window.chai.expect(getState(p, 1, DIRECTIONS.LEFT)).to.eql({ + // We look to the left of " " (` []`): + node: p.lastChild, // "" + direction: DIRECTIONS.LEFT, + // The browser strips the space away so we ignore it and see + // `

`: the opening tag from the inside. + cType: CTYPES.BLOCK_INSIDE, + }); + }); + it('should recognize invisible space to the left (more space after)', () => { + // We'll be looking to the left while standing at ` [] `. + const [p] = insertTestHtml('

'); + splitTextNode(p.firstChild, 1); // " "" " + window.chai.expect(getState(p, 1, DIRECTIONS.LEFT)).to.eql({ + // We look to the left of " " (` [] `): + node: p.lastChild, // " ". + direction: DIRECTIONS.LEFT, + // The browser strips the space away so we ignore it and see + // `

`: the opening tag from the inside. + cType: CTYPES.BLOCK_INSIDE, + }); + }); + it('should recognize invisible space to the left (br after)', () => { + // We'll be looking to the left while standing at ` [] `. + const [p] = insertTestHtml('


'); + window.chai.expect(getState(p, 1, DIRECTIONS.LEFT)).to.eql({ + // We look to the left of the br element (` []
`): + node: p.lastChild, // `
`. + direction: DIRECTIONS.LEFT, + // The browser strips the space away so we ignore it and see + // `

`: the opening tag from the inside. + cType: CTYPES.BLOCK_INSIDE, + }); + }); + }); + describe('restoreState', () => { + it('should restore invisible space to the left (looking right)', () => { + // We'll be restoring the state of "a []" in `

a

`. + const [p] = insertTestHtml('

a b

'); + splitTextNode(p.firstChild, 2); // "a ""b" + const rule = restoreState({ + // We look to the right of "a " (`a []b`) to see if we need + // to preserve the space at the end of "a ": + node: p.firstChild, // "a " + direction: DIRECTIONS.RIGHT, + // The DOM used to be `

a

` so to the right of "a " we + // used to see `

`: the closing tag from the inside. + cType: CTYPES.BLOCK_INSIDE, + }); + // Now looking to the right of "a " we see "b", which is content + // and makes the formerly invisible space visible. We should get + // back a rule that will enforce the invisibility of the space. + window.chai.expect(rule.spaceVisibility).to.be.false; + }); + it('should restore visible space to the left (looking right) (among consecutive space within content)', () => { + // We'll be restoring the state of "a []" in `

a b

`. + // The first space is visible, the rest isn't. + const [p] = insertTestHtml('

a

'); + splitTextNode(p.firstChild, 2); // "a "" " + const rule = restoreState({ + // We look to the right of "a " (`a []`) to see if we need + // to preserve the space at the end of "a ": + node: p.firstChild, // "a " + direction: DIRECTIONS.RIGHT, + // The DOM used to be `

a b

` so to the right of "a " we + // used to see "b" which is visible content. + cType: CTYPES.CONTENT, + }); + // Now looking to the right of "a " we see `

`: the closing + // tag, from the inside. This makes the formerly visible space + // invisible. We should get back a rule that will enforce the + // visibility of the space. + window.chai.expect(rule.spaceVisibility).to.be.true; + }); + it('should restore visible space to the right (looking left) (followed by consecutive space within content)', () => { + // We'll be restoring the state of "[] b" in `

a b

`. + // The first space is visible, the rest isn't. + const [p] = insertTestHtml('

a

'); + splitTextNode(p.firstChild, 2); // "a "" " + const rule = restoreState({ + // We look to the left of " " (`[] `) to see if we need + // to preserve the space of " ": + node: p.lastChild, // " " + direction: DIRECTIONS.LEFT, + // The DOM used to be `

a b

` so to the left of " b" we + // used to see " " which is visible space. + cType: CTYPES.SPACE, + }); + // Now looking to the left of " " we see " " which is now + // invisible. This means the space we're examining is also still + // invisible. Since it should be invisible, we should get back a + // rule that will enforce the invisibility of the space (but no + // rule would work as well). + window.chai.expect(rule.spaceVisibility).not.to.be.true; + }); + it('should restore invisible space to the right (looking left) (nothing after)', () => { + // We'll be restoring the state of " []" in `

`. + const [p] = insertTestHtml('

a

'); + splitTextNode(p.firstChild, 1); // "a"" " + const rule = restoreState({ + // We look to the left of " " (`a[] `) to see if we need + // to preserve the space of " ": + node: p.lastChild, // " " + direction: DIRECTIONS.LEFT, + // The DOM used to be `

` so to the left of " " we + // used to see `

`: the opening tag from the inside. + cType: CTYPES.BLOCK_INSIDE, + }); + // Now looking to the left of " " we see "a", which is content + // but since it's to the left of our space it has no incidence + // on its visibility. Either way it should be invisible so we + // should get back a rule that will enforce the invisibility of + // the space (but no rule would work as well). + window.chai.expect(rule.spaceVisibility).not.to.be.true; + }); + it('should restore invisible space to the right (looking left) (more space after)', () => { + // We'll be restoring the state of " [] " in `

`. + const [p] = insertTestHtml('

a

'); + splitTextNode(p.firstChild, 2); // "a "" " + const rule = restoreState({ + // We look to the left of " " (`a [] `) to see if we need + // to preserve the space of " ": + node: p.lastChild, // " " + direction: DIRECTIONS.LEFT, + // The DOM used to be `

` so to the left of " " + // we used to see `

`: the opening tag from the inside. + cType: CTYPES.BLOCK_INSIDE, + }); + // Now looking to the left of " " we see "a", which is content + // but since it's to the left of our space it has no incidence + // on its visibility. Either way it should be invisible so we + // should get back a rule that will enforce the invisibility of + // the space (but no rule would work as well). + window.chai.expect(rule.spaceVisibility).not.to.be.true; + }); + it('should restore invisible space to the right (looking left) (br after)', () => { + // We'll be restoring the state of " []
" in `

[]

`. + const [p] = insertTestHtml('

a

'); + const rule = restoreState({ + // We look to the left of `
` (`a []
`): + node: p.lastChild, // `
` + direction: DIRECTIONS.LEFT, + // The DOM used to be `


` so to the left of + // `
` we used to see `

`: the opening tag from the + // inside. + cType: CTYPES.BLOCK_INSIDE, + }); + // Now looking to the left of `
` we see "a", which is + // content but since it's to the left of our space it has no + // incidence on its visibility. Either way it should be + // invisible so we should get back a rule that will enforce the + // invisibility of the space (but no rule would work as well). + window.chai.expect(rule.spaceVisibility).not.to.be.true; + }); + }); + describe('enforceWhitespace', () => { + it('should enforce invisible space to the left', () => { + // We'll be making the space between "a" and "b" invisible. + const [p] = insertTestHtml('

a b

'); + splitTextNode(p.firstChild, 2); // "a ""b" + // We look to the left while standing at "a []": + enforceWhitespace(p, 1, DIRECTIONS.LEFT, { spaceVisibility: false }); + window.chai.expect(p.innerHTML).to.eql('ab'); + }); + it('should restore visible space to the left (among consecutive space within content)', () => { + // We'll be making the first space after "a" visible. + const [p] = insertTestHtml('

a

'); + splitTextNode(p.firstChild, 2); // "a "" " + // We look to the left while standing at "a []": + enforceWhitespace(p, 1, DIRECTIONS.LEFT, { spaceVisibility: true }); + window.chai.expect(p.innerHTML).to.eql('a  '); + }); + it('should not enforce already invisible space to the right (followed by consecutive space within content)', () => { + // We'll be keeping the last (invisible) space after "a" (we + // could remove it but we don't need to - mostly we should not + // make it visible). + const [p] = insertTestHtml('

a

'); + splitTextNode(p.firstChild, 2); // "a "" " + // We look to the left while standing at "a []": + enforceWhitespace(p, 0, DIRECTIONS.RIGHT, { spaceVisibility: false }); + window.chai.expect(p.innerHTML).to.eql('a '); + }); + it('should not enforce already invisible space to the right (nothing after)', () => { + // We'll be keeping the invisible space after "a" (we could + // remove it but we don't need to - mostly we should not make it + // visible). + const [p] = insertTestHtml('

a

'); + splitTextNode(p.firstChild, 1); // "a"" " + // We look to the right while standing at "a[]": + enforceWhitespace(p, 0, DIRECTIONS.RIGHT, { spaceVisibility: false }); + window.chai.expect(p.innerHTML).to.eql('a '); + }); + it('should not enforce already invisible space to the left (more space after)', () => { + // We'll be keeping the invisible space after "a" (we could + // remove it but we don't need to - mostly we should not make it + // visible). + const [p] = insertTestHtml('

a

'); + splitTextNode(p.firstChild, 1); // "a"" " + // We look to the right while standing at "a[]": + enforceWhitespace(p, 0, DIRECTIONS.RIGHT, { spaceVisibility: false }); + window.chai.expect(p.innerHTML).to.eql('a '); + }); + it('should not enforce already invisible space to the left (br after)', () => { + // We'll be keeping the invisible space after "a" (we could + // remove it but we don't need to - mostly we should not make it + // visible). + const [p] = insertTestHtml('

a

'); + splitTextNode(p.firstChild, 1); // "a"" " + // We look to the right while standing at "a[]": + enforceWhitespace(p, 0, DIRECTIONS.RIGHT, { spaceVisibility: false }); + window.chai.expect(p.innerHTML).to.eql('a
'); + }); + }); + }); });