Skip to content

Commit

Permalink
Add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Mati365 committed Aug 9, 2024
1 parent 9de95e4 commit c24f4d7
Show file tree
Hide file tree
Showing 2 changed files with 146 additions and 7 deletions.
16 changes: 10 additions & 6 deletions packages/ckeditor5-editor-multi-root/src/multirooteditorui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ export default class MultiRootEditorUI extends EditorUI {
/**
* It's workaround for issue with block selection in Chrome. Chrome doesn't fire selection change event when user
* clicks element with single block element (for example table). It causes the editor to rollback selection (and focus)
* to previously selected editable element.
* to previously selected editable element. This method forces Chrome to select the focused element.
*
* The timeout is used because selection change event is async event and it's fired shortly after focus event.
* Make sure it's lower than the timeout used in {@link module:engine/view/observer/focusobserver~FocusObserver#_handleFocus}
Expand All @@ -252,10 +252,15 @@ export default class MultiRootEditorUI extends EditorUI {
this._domEmitter.listenTo( editableElement, 'focus', () => {
// Selection changes shortly after focus event so run the fix after a short delay.
setTimeout( () => {
const domSelection = global.document.defaultView!.getSelection()!;
const selection = global.document.defaultView!.getSelection();

// Cancel fix if the anchor node is inside the editable element. It happens from time to time on Chrome.
if ( editableElement !== domSelection.anchorNode && editableElement.contains( domSelection.anchorNode ) ) {
// If there is no selection, don't activate fix.
if ( !selection ) {
return;
}

// If selection anchor is somewhere in editable element, don't force select the focused element.
if ( editableElement !== selection.anchorNode && editableElement.contains( selection.anchorNode ) ) {
return;
}

Expand All @@ -270,9 +275,8 @@ export default class MultiRootEditorUI extends EditorUI {

// If there's no contenteditable element, force select the focused element.
const { activeElement } = global.document;
const selection = window.getSelection();

if ( activeElement && selection ) {
if ( activeElement ) {
selection.selectAllChildren( activeElement );
}
}, 20 );
Expand Down
137 changes: 136 additions & 1 deletion packages/ckeditor5-editor-multi-root/tests/multirooteditorui.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@
import MultiRootEditor from '../src/multirooteditor.js';
import EditorUI from '@ckeditor/ckeditor5-ui/src/editorui/editorui.js';
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph.js';
import Table from '@ckeditor/ckeditor5-table/src/table.js';
import Image from '@ckeditor/ckeditor5-image/src/image.js';

import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils.js';
import { viewTable } from '@ckeditor/ckeditor5-table/tests/_utils/utils.js';

import env from '@ckeditor/ckeditor5-utils/src/env.js';
import global from '@ckeditor/ckeditor5-utils/src/dom/global.js';

import View from '@ckeditor/ckeditor5-ui/src/view.js';

Expand All @@ -32,7 +39,9 @@ describe( 'MultiRootEditorUI', () => {
} );

afterEach( () => {
editor.destroy();
if ( editor ) {
editor.destroy();
}
} );

describe( 'constructor()', () => {
Expand Down Expand Up @@ -424,4 +433,130 @@ describe( 'MultiRootEditorUI', () => {
sinon.assert.callOrder( parentDestroySpy, viewDestroySpy );
} );
} );

describe( 'Blink quirks', () => {
let model, editables, clock;

beforeEach( async () => {
await editor.destroy();

editables = {
first: document.body.appendChild( document.createElement( 'div' ) ),
second: document.body.appendChild( document.createElement( 'div' ) )
};

editor = await MultiRootEditor.create(
editables,
{
extraPlugins: [ Paragraph, Table, Image ]
}
);

clock = sinon.useFakeTimers();
ui = editor.ui;
model = editor.model;
view = ui.view;

sinon.stub( env, 'isBlink' ).returns( true );
} );

afterEach( async () => {
clock.restore();

await editor.destroy();
editor = null;

Object.values( editables ).forEach( element => {
element.remove();
} );
} );

describe( 'block selection workaround', () => {
it( 'should focus first contenteditable table element after focusing editable', () => {
editor.setData( {
first: '<p>First</p>',
second: viewTable( [
[ '00', '01' ],
[ '02', '03' ]
] )
} );

// First editable is selected.
editor.model.change( writer => {
writer.setSelection( writer.createRangeIn( model.document.getRoot( 'first' ).getChild( 0 ) ) );
} );

// Lets trigger focus on second editable to see if the first cell of the table is focused.
editables.second.focus();
clock.tick( 20 );

// Expect that first table cell is focused.
expect( document.querySelector( 'td' ).matches( ':focus' ) ).to.be.true;
} );

it( 'should select first image element after focusing editable', () => {
editor.setData( {
first: '<p>First</p>',
second: '<img src="/assets/sample.png">'
} );

// First editable is selected.
editor.model.change( writer => {
writer.setSelection( writer.createRangeIn( model.document.getRoot( 'first' ).getChild( 0 ) ) );
} );

// Let's check if the image will be forced to be selected.
const spySelectAllChildren = sinon.spy();

sinon.stub( global.document.defaultView, 'getSelection' ).returns( {
anchorNode: null,
selectAllChildren: spySelectAllChildren
} );

editables.second.focus();
clock.tick( 20 );

expect( spySelectAllChildren ).to.be.calledOnce;
} );

it( 'should not crash when there is no selection', () => {
editor.setData( {
first: '<p>First</p>',
second: '<p>Second</p>'
} );

sinon.stub( global.document.defaultView, 'getSelection' ).returns( null );

expect( () => {
editables.second.focus();
clock.tick( 20 );
} ).not.to.throw();
} );

it( 'should not activate fix when anchor node is inside editable', () => {
editor.setData( {
first: '<p>First</p>',
second: '<img src="/assets/sample.png">'
} );

// First editable is selected.
editor.model.change( writer => {
writer.setSelection( writer.createRangeIn( model.document.getRoot( 'first' ).getChild( 0 ) ) );
} );

// Let's check if the image will be forced to be selected.
const spySelectAllChildren = sinon.spy();

sinon.stub( global.document.defaultView, 'getSelection' ).returns( {
anchorNode: document.querySelector( 'img[src="/assets/sample.png"]' ),
selectAllChildren: spySelectAllChildren
} );

editables.second.focus();
clock.tick( 20 );

expect( spySelectAllChildren ).not.to.be.called;
} );
} );
} );
} );

0 comments on commit c24f4d7

Please sign in to comment.