Skip to content
This repository has been archived by the owner on Jun 26, 2020. It is now read-only.

t/ckeditor5/1599: Feature: Made FocusTracker#focusedElement observable to brin... #276

Merged
merged 2 commits into from
Mar 26, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions src/focustracker.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,17 @@ export default class FocusTracker {
this.set( 'isFocused', false );

/**
* Currently focused element.
* The currently focused element.
*
* While {@link #isFocused `isFocused`} remains `true`, the focus can
* move between different UI elements. This property tracks those
* elements and tells which one is currently focused.
*
* @readonly
* @member {HTMLElement}
* @observable
* @member {HTMLElement|null}
*/
this.focusedElement = null;
this.set( 'focusedElement', null );

/**
* List of registered elements.
Expand Down
42 changes: 37 additions & 5 deletions tests/focustracker.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import FocusTracker from '../src/focustracker';
import CKEditorError from '../src/ckeditorerror';
import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
import global from '@ckeditor/ckeditor5-utils/src/dom/global';

describe( 'FocusTracker', () => {
let focusTracker, container, containerFirstInput, containerSecondInput;
Expand Down Expand Up @@ -43,6 +44,22 @@ describe( 'FocusTracker', () => {
expect( observableSpy.calledOnce ).to.true;
} );
} );

describe( 'focusedElement', () => {
it( 'should be null at default', () => {
expect( focusTracker.focusedElement ).to.be.null;
} );

it( 'should be observable', () => {
const observableSpy = testUtils.sinon.spy();

focusTracker.listenTo( focusTracker, 'change:focusedElement', observableSpy );

focusTracker.focusedElement = global.document.body;

expect( observableSpy.calledOnce ).to.true;
} );
} );
} );

describe( 'add', () => {
Expand All @@ -63,16 +80,20 @@ describe( 'FocusTracker', () => {
containerFirstInput.dispatchEvent( new Event( 'focus' ) );

expect( focusTracker.isFocused ).to.true;
expect( focusTracker.focusedElement ).to.equal( containerFirstInput );
} );

it( 'should start listening on element blur and update `isFocused` property', () => {
focusTracker.add( containerFirstInput );
focusTracker.isFocused = true;
containerFirstInput.dispatchEvent( new Event( 'focus' ) );

expect( focusTracker.focusedElement ).to.equal( containerFirstInput );

containerFirstInput.dispatchEvent( new Event( 'blur' ) );
testUtils.sinon.clock.tick( 0 );

expect( focusTracker.isFocused ).to.false;
expect( focusTracker.focusedElement ).to.be.null;
} );
} );

Expand All @@ -85,16 +106,20 @@ describe( 'FocusTracker', () => {
containerFirstInput.dispatchEvent( new Event( 'focus' ) );

expect( focusTracker.isFocused ).to.true;
expect( focusTracker.focusedElement ).to.equal( container );
} );

it( 'should start listening on element blur using event capturing and update `isFocused` property', () => {
focusTracker.add( container );
focusTracker.isFocused = true;
containerFirstInput.dispatchEvent( new Event( 'focus' ) );

expect( focusTracker.focusedElement ).to.equal( container );

containerFirstInput.dispatchEvent( new Event( 'blur' ) );
testUtils.sinon.clock.tick( 0 );

expect( focusTracker.isFocused ).to.false;
expect( focusTracker.focusedElement ).to.be.null;
} );

it( 'should not change `isFocused` property when focus is going between child elements', () => {
Expand All @@ -103,30 +128,34 @@ describe( 'FocusTracker', () => {
focusTracker.add( container );

containerFirstInput.dispatchEvent( new Event( 'focus' ) );
expect( focusTracker.focusedElement ).to.equal( container );
expect( focusTracker.isFocused ).to.true;

focusTracker.listenTo( focusTracker, 'change:isFocused', changeSpy );

expect( focusTracker.isFocused ).to.true;

containerFirstInput.dispatchEvent( new Event( 'blur' ) );
containerSecondInput.dispatchEvent( new Event( 'focus' ) );
testUtils.sinon.clock.tick( 0 );

expect( focusTracker.focusedElement ).to.equal( container );
expect( focusTracker.isFocused ).to.true;
expect( changeSpy.notCalled ).to.true;
} );

// https://github.com/ckeditor/ckeditor5-utils/issues/159
it( 'should keep `isFocused` synced when multiple blur events are followed by the focus', () => {
focusTracker.add( container );
focusTracker.isFocused = true;
container.dispatchEvent( new Event( 'focus' ) );

expect( focusTracker.focusedElement ).to.equal( container );

container.dispatchEvent( new Event( 'blur' ) );
containerFirstInput.dispatchEvent( new Event( 'blur' ) );
containerSecondInput.dispatchEvent( new Event( 'focus' ) );
testUtils.sinon.clock.tick( 0 );

expect( focusTracker.isFocused ).to.be.true;
expect( focusTracker.focusedElement ).to.equal( container );
} );
} );
} );
Expand All @@ -145,6 +174,7 @@ describe( 'FocusTracker', () => {
containerFirstInput.dispatchEvent( new Event( 'focus' ) );

expect( focusTracker.isFocused ).to.false;
expect( focusTracker.focusedElement ).to.be.null;
} );

it( 'should stop listening on element blur', () => {
Expand All @@ -161,13 +191,15 @@ describe( 'FocusTracker', () => {
it( 'should blur element before removing when is focused', () => {
focusTracker.add( containerFirstInput );
containerFirstInput.dispatchEvent( new Event( 'focus' ) );
expect( focusTracker.focusedElement ).to.equal( containerFirstInput );

expect( focusTracker.isFocused ).to.true;

focusTracker.remove( containerFirstInput );
testUtils.sinon.clock.tick( 0 );

expect( focusTracker.isFocused ).to.false;
expect( focusTracker.focusedElement ).to.be.null;
} );
} );

Expand Down