|
|
@@ -0,0 +1,289 @@
|
|
|
+/**
|
|
|
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
|
|
|
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
|
+ */
|
|
|
+
|
|
|
+/* globals document, setTimeout, Event */
|
|
|
+
|
|
|
+import getResizeObserver from '../../src/dom/getresizeobserver';
|
|
|
+import Rect from '@ckeditor/ckeditor5-utils/src/dom/rect';
|
|
|
+import global from '@ckeditor/ckeditor5-utils/src/dom/global';
|
|
|
+import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
|
|
|
+import DomEmitterMixin from '@ckeditor/ckeditor5-utils/src/dom/emittermixin';
|
|
|
+
|
|
|
+describe.only( 'getResizeObserver()', () => {
|
|
|
+ testUtils.createSinonSandbox();
|
|
|
+
|
|
|
+ it( 'returns the native implementation if available', () => {
|
|
|
+ testUtils.sinon.stub( global.window, 'ResizeObserver' ).callsFake( function( callback ) {
|
|
|
+ this.callback = callback;
|
|
|
+ } );
|
|
|
+
|
|
|
+ expect( getResizeObserver( 'foo' ).callback ).to.equal( 'foo' );
|
|
|
+ } );
|
|
|
+
|
|
|
+ it( 'returns the polyfill when no native implementation available', () => {
|
|
|
+ testUtils.sinon.stub( global.window, 'ResizeObserver' ).value( null );
|
|
|
+
|
|
|
+ expect( getResizeObserver().constructor.name ).to.equal( 'ResizeObserverPolyfill' );
|
|
|
+ } );
|
|
|
+
|
|
|
+ describe( 'ResizeObserverPolyfill', () => {
|
|
|
+ let elementA, elementB, observer, callback, elementRectA, elementRectB;
|
|
|
+
|
|
|
+ beforeEach( () => {
|
|
|
+ testUtils.sinon.stub( global.window, 'ResizeObserver' ).value( null );
|
|
|
+
|
|
|
+ callback = sinon.spy();
|
|
|
+ observer = getResizeObserver( callback );
|
|
|
+
|
|
|
+ elementA = document.createElement( 'div' );
|
|
|
+ elementB = document.createElement( 'div' );
|
|
|
+
|
|
|
+ elementRectA = {
|
|
|
+ top: 10,
|
|
|
+ right: 20,
|
|
|
+ bottom: 20,
|
|
|
+ left: 0,
|
|
|
+ height: 10,
|
|
|
+ width: 20
|
|
|
+ };
|
|
|
+
|
|
|
+ elementRectB = {
|
|
|
+ top: 0,
|
|
|
+ right: 10,
|
|
|
+ bottom: 10,
|
|
|
+ left: 0,
|
|
|
+ height: 10,
|
|
|
+ width: 10
|
|
|
+ };
|
|
|
+
|
|
|
+ elementA.getBoundingClientRect = () => elementRectA;
|
|
|
+ elementB.getBoundingClientRect = () => elementRectB;
|
|
|
+
|
|
|
+ document.body.appendChild( elementA );
|
|
|
+ document.body.appendChild( elementB );
|
|
|
+ } );
|
|
|
+
|
|
|
+ afterEach( () => {
|
|
|
+ observer.disconnect();
|
|
|
+
|
|
|
+ elementA.remove();
|
|
|
+ elementB.remove();
|
|
|
+ } );
|
|
|
+
|
|
|
+ it( 'mixes DomEmitterMixin', () => {
|
|
|
+ expect( testUtils.isMixed( getResizeObserver().constructor, DomEmitterMixin ) ).to.be.true;
|
|
|
+ } );
|
|
|
+
|
|
|
+ describe( 'observe()', () => {
|
|
|
+ it( 'calls the callback immediatelly', () => {
|
|
|
+ observer.observe( elementA );
|
|
|
+
|
|
|
+ sinon.assert.calledOnce( callback );
|
|
|
+
|
|
|
+ const { target, contentRect } = callback.firstCall.args[ 0 ][ 0 ];
|
|
|
+
|
|
|
+ expect( target ).to.equal( elementA );
|
|
|
+ expect( contentRect ).to.be.instanceOf( Rect );
|
|
|
+ expect( contentRect ).to.deep.equal( elementRectA );
|
|
|
+ } );
|
|
|
+
|
|
|
+ it( 'starts periodic check and asynchronously does not execute the callback if the element rect is the same', done => {
|
|
|
+ observer.observe( elementA );
|
|
|
+
|
|
|
+ setTimeout( () => {
|
|
|
+ sinon.assert.calledOnce( callback );
|
|
|
+ done();
|
|
|
+ }, 200 );
|
|
|
+ } );
|
|
|
+
|
|
|
+ it( 'starts periodic check and asynchronously executes the callback if the element rect changed', done => {
|
|
|
+ observer.observe( elementA );
|
|
|
+ sinon.assert.calledOnce( callback );
|
|
|
+
|
|
|
+ const newRect = {
|
|
|
+ top: 30,
|
|
|
+ right: 10,
|
|
|
+ bottom: 40,
|
|
|
+ left: 0,
|
|
|
+ height: 10,
|
|
|
+ width: 10
|
|
|
+ };
|
|
|
+
|
|
|
+ elementA.getBoundingClientRect = () => newRect;
|
|
|
+
|
|
|
+ setTimeout( () => {
|
|
|
+ sinon.assert.calledTwice( callback );
|
|
|
+
|
|
|
+ const { target, contentRect } = callback.secondCall.args[ 0 ][ 0 ];
|
|
|
+
|
|
|
+ expect( target ).to.equal( elementA );
|
|
|
+ expect( contentRect ).to.deep.equal( newRect );
|
|
|
+
|
|
|
+ done();
|
|
|
+ }, 200 );
|
|
|
+ } );
|
|
|
+
|
|
|
+ it( 'starts periodic check and asynchronously executes the callback if multiple element rects changed', done => {
|
|
|
+ observer.observe( elementA );
|
|
|
+ observer.observe( elementB );
|
|
|
+ sinon.assert.calledOnce( callback );
|
|
|
+
|
|
|
+ const newRectA = {
|
|
|
+ top: 30,
|
|
|
+ right: 10,
|
|
|
+ bottom: 40,
|
|
|
+ left: 0,
|
|
|
+ height: 10,
|
|
|
+ width: 10
|
|
|
+ };
|
|
|
+
|
|
|
+ const newRectB = {
|
|
|
+ top: 30,
|
|
|
+ right: 100,
|
|
|
+ bottom: 40,
|
|
|
+ left: 0,
|
|
|
+ height: 10,
|
|
|
+ width: 100
|
|
|
+ };
|
|
|
+
|
|
|
+ elementA.getBoundingClientRect = () => newRectA;
|
|
|
+ elementB.getBoundingClientRect = () => newRectB;
|
|
|
+
|
|
|
+ setTimeout( () => {
|
|
|
+ sinon.assert.calledTwice( callback );
|
|
|
+
|
|
|
+ const { target: targetA, contentRect: contentRectA } = callback.secondCall.args[ 0 ][ 0 ];
|
|
|
+ const { target: targetB, contentRect: contentRectB } = callback.secondCall.args[ 0 ][ 1 ];
|
|
|
+
|
|
|
+ expect( targetA ).to.equal( elementA );
|
|
|
+ expect( contentRectA ).to.deep.equal( newRectA );
|
|
|
+
|
|
|
+ expect( targetB ).to.equal( elementB );
|
|
|
+ expect( contentRectB ).to.deep.equal( newRectB );
|
|
|
+
|
|
|
+ done();
|
|
|
+ }, 200 );
|
|
|
+ } );
|
|
|
+
|
|
|
+ it( 'starts periodic check and synchronously responds to window resize', () => {
|
|
|
+ observer.observe( elementA );
|
|
|
+ sinon.assert.calledOnce( callback );
|
|
|
+
|
|
|
+ const newRectA = {
|
|
|
+ top: 30,
|
|
|
+ right: 10,
|
|
|
+ bottom: 40,
|
|
|
+ left: 0,
|
|
|
+ height: 10,
|
|
|
+ width: 10
|
|
|
+ };
|
|
|
+
|
|
|
+ elementA.getBoundingClientRect = () => newRectA;
|
|
|
+
|
|
|
+ global.window.dispatchEvent( new Event( 'resize' ) );
|
|
|
+
|
|
|
+ sinon.assert.calledTwice( callback );
|
|
|
+
|
|
|
+ const { target: targetA, contentRect: contentRectA } = callback.secondCall.args[ 0 ][ 0 ];
|
|
|
+
|
|
|
+ expect( targetA ).to.equal( elementA );
|
|
|
+ expect( contentRectA ).to.deep.equal( newRectA );
|
|
|
+ } );
|
|
|
+ } );
|
|
|
+
|
|
|
+ describe( 'unobserve()', () => {
|
|
|
+ it( 'removes the element from the observer so no future changes to the element execute the callback', done => {
|
|
|
+ observer.observe( elementA );
|
|
|
+ sinon.assert.calledOnce( callback );
|
|
|
+
|
|
|
+ const newRect = {
|
|
|
+ top: 30,
|
|
|
+ right: 10,
|
|
|
+ bottom: 40,
|
|
|
+ left: 0,
|
|
|
+ height: 10,
|
|
|
+ width: 10
|
|
|
+ };
|
|
|
+
|
|
|
+ observer.unobserve( elementA );
|
|
|
+
|
|
|
+ elementA.getBoundingClientRect = () => newRect;
|
|
|
+
|
|
|
+ setTimeout( () => {
|
|
|
+ sinon.assert.calledOnce( callback );
|
|
|
+
|
|
|
+ done();
|
|
|
+ }, 200 );
|
|
|
+ } );
|
|
|
+
|
|
|
+ it( 'does not affect the callback being executed for other elements in the observer', done => {
|
|
|
+ observer.observe( elementA );
|
|
|
+ observer.observe( elementB );
|
|
|
+ sinon.assert.calledOnce( callback );
|
|
|
+
|
|
|
+ const newRectA = {
|
|
|
+ top: 30,
|
|
|
+ right: 10,
|
|
|
+ bottom: 40,
|
|
|
+ left: 0,
|
|
|
+ height: 10,
|
|
|
+ width: 10
|
|
|
+ };
|
|
|
+
|
|
|
+ const newRectB = {
|
|
|
+ top: 30,
|
|
|
+ right: 100,
|
|
|
+ bottom: 40,
|
|
|
+ left: 0,
|
|
|
+ height: 10,
|
|
|
+ width: 100
|
|
|
+ };
|
|
|
+
|
|
|
+ elementA.getBoundingClientRect = () => newRectA;
|
|
|
+ elementB.getBoundingClientRect = () => newRectB;
|
|
|
+
|
|
|
+ observer.unobserve( elementA );
|
|
|
+
|
|
|
+ setTimeout( () => {
|
|
|
+ sinon.assert.calledTwice( callback );
|
|
|
+
|
|
|
+ const { target: targetB, contentRect: contentRectB } = callback.secondCall.args[ 0 ][ 0 ];
|
|
|
+
|
|
|
+ expect( callback.secondCall.args[ 0 ] ).to.have.length( 1 );
|
|
|
+
|
|
|
+ expect( targetB ).to.equal( elementB );
|
|
|
+ expect( contentRectB ).to.deep.equal( newRectB );
|
|
|
+
|
|
|
+ done();
|
|
|
+ }, 200 );
|
|
|
+ } );
|
|
|
+
|
|
|
+ it( 'disables the Emitter when no elements left in the observer', () => {
|
|
|
+ const stopCheckSpy = testUtils.sinon.spy( observer, '_stopPeriodicCheck' );
|
|
|
+
|
|
|
+ observer.observe( elementA );
|
|
|
+ sinon.assert.calledOnce( callback );
|
|
|
+
|
|
|
+ observer.unobserve( elementA );
|
|
|
+ sinon.assert.calledOnce( stopCheckSpy );
|
|
|
+ } );
|
|
|
+ } );
|
|
|
+
|
|
|
+ describe( 'disconnect()', () => {
|
|
|
+ it( 'calls unobserve() for all observed elements', () => {
|
|
|
+ observer.observe( elementA );
|
|
|
+ observer.observe( elementB );
|
|
|
+
|
|
|
+ const unobserveSpy = testUtils.sinon.spy( observer, 'unobserve' );
|
|
|
+
|
|
|
+ observer.disconnect();
|
|
|
+
|
|
|
+ sinon.assert.calledTwice( unobserveSpy );
|
|
|
+ sinon.assert.calledWith( unobserveSpy.firstCall, elementA );
|
|
|
+ sinon.assert.calledWith( unobserveSpy.secondCall, elementB );
|
|
|
+ } );
|
|
|
+ } );
|
|
|
+ } );
|
|
|
+} );
|