diff --git a/addon/modifiers/did-resize.js b/addon/modifiers/did-resize.js index 280aae7d..4b4f43ee 100644 --- a/addon/modifiers/did-resize.js +++ b/addon/modifiers/did-resize.js @@ -6,23 +6,46 @@ export default class DidResizeModifier extends Modifier { options = {}; // Private API - observer = null; + static observer = null; + static handlers = null; - observe() { - if (this.observer) { - this.observer.observe(this.element, this.options); + constructor() { + super(...arguments); + + if (!('ResizeObserver' in window)) { + return; + } + + if (!DidResizeModifier.observer) { + DidResizeModifier.handlers = new WeakMap(); + DidResizeModifier.observer = new ResizeObserver((entries, observer) => { + for (let entry of entries) { + const handler = DidResizeModifier.handlers.get(entry.target); + if (handler) handler(entry, observer); + } + }); } } - unobserve() { - if (this.observer) { - this.observer.unobserve(); + addHandler() { + DidResizeModifier.handlers.set(this.element, this.handler); + } + + removeHandler() { + DidResizeModifier.handlers.delete(this.element); + } + + observe() { + if (DidResizeModifier.observer) { + this.addHandler(); + DidResizeModifier.observer.observe(this.element, this.options); } } - disconnect() { - if (this.observer) { - this.observer.disconnect(); + unobserve() { + if (DidResizeModifier.observer) { + DidResizeModifier.observer.unobserve(this.element); + this.removeHandler(); } } @@ -41,19 +64,7 @@ export default class DidResizeModifier extends Modifier { this.observe(); } - didInstall() { - if (!('ResizeObserver' in window)) { - return; - } - - this.observer = new ResizeObserver((entries, observer) => { - this.handler(entries[0], observer); - }); - - this.observe(); - } - willRemove() { - this.disconnect(); + this.unobserve(); } } diff --git a/tests/integration/modifiers/did-resize-test.js b/tests/integration/modifiers/did-resize-test.js index 60bddbca..20450608 100644 --- a/tests/integration/modifiers/did-resize-test.js +++ b/tests/integration/modifiers/did-resize-test.js @@ -1,40 +1,39 @@ import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; -import { render } from '@ember/test-helpers'; +import { find, render } from '@ember/test-helpers'; import hbs from 'htmlbars-inline-precompile'; import sinon from 'sinon'; - -let resizeCallback; -let observeStub; -let unobserveStub; -let disconnectStub; -let MockResizeObserver; +import DidResizeModifier from 'ember-resize-modifier/modifiers/did-resize'; module('Integration | Modifier | did-resize', function (hooks) { setupRenderingTest(hooks); - let resizeObserver; + let resizeCallback = null; + let observeStub = sinon.stub(); + let unobserveStub = sinon.stub(); + let disconnectStub = sinon.stub(); + let resizeObserver = window.ResizeObserver; + let mockResizeObserver = class MockResizeObserver { + constructor(callback) { + resizeCallback = callback; + } + + observe = observeStub; + unobserve = unobserveStub; + disconnect = disconnectStub; + }; hooks.beforeEach(function () { - resizeCallback = null; - observeStub = sinon.stub(); - unobserveStub = sinon.stub(); - disconnectStub = sinon.stub(); - - MockResizeObserver = class MockResizeObserver { - constructor(callback) { - resizeCallback = callback; - } - - observe = observeStub; - unobserve = unobserveStub; - disconnect = disconnectStub; - }; - - resizeObserver = window.ResizeObserver; - window.ResizeObserver = MockResizeObserver; - + observeStub.reset(); + unobserveStub.reset(); + disconnectStub.reset(); this.resizeStub = sinon.stub(); + window.ResizeObserver = mockResizeObserver; + + // reset static properties to make sure every test case runs independently + DidResizeModifier.observer = null; + DidResizeModifier.handlers = null; + resizeCallback = null; }); hooks.afterEach(function () { @@ -58,15 +57,16 @@ module('Integration | Modifier | did-resize', function (hooks) { }); test('modifier triggers handler when ResizeObserver fires callback', async function (assert) { - await render(hbs`
`); - - let fakeEntry = { target: {} }; + await render( + hbs`` + ); + let entry = { target: find('#test-element') }; let fakeObserver = { observe: {} }; - resizeCallback([fakeEntry], fakeObserver); + resizeCallback([entry], fakeObserver); assert.ok( - this.resizeStub.calledOnceWith(fakeEntry, fakeObserver), + this.resizeStub.calledOnceWith(entry, fakeObserver), 'handler fired with correct parameters' ); }); @@ -86,7 +86,6 @@ module('Integration | Modifier | did-resize', function (hooks) { delete window.ResizeObserver; await render(hbs``); - assert.notOk(resizeCallback, 'no callback received'); assert.notOk(observeStub.calledOnce, 'observe was not called'); }); @@ -102,4 +101,52 @@ module('Integration | Modifier | did-resize', function (hooks) { assert.ok(unobserveStub.calledOnce, 'unobserve called'); assert.ok(observeStub.calledTwice, 'observe was called again'); }); + + test('handlers are setup and called correctly when multiple modifiers are present', async function (assert) { + this.setProperties({ + resizeStub2: sinon.stub(), + resizeStub3: sinon.stub(), + }); + + await render( + hbs` + + ` + ); + + let entries = ['#test-element1', '#test-element2', '#test-element3'].map( + (elementId) => { + return { target: find(elementId) }; + } + ); + let fakeObserver = { observe: {} }; + + // trigger resize on all elements + resizeCallback(entries, fakeObserver); + + assert.ok(this.resizeStub.calledOnce, 'First handler was called only once'); + assert.ok( + this.resizeStub2.calledOnce, + 'Second handler was called only once' + ); + assert.ok( + this.resizeStub3.calledOnce, + 'Third handler was called only once' + ); + + // trigger resize only on the first element + resizeCallback([entries[0]], fakeObserver); + assert.ok( + this.resizeStub.calledTwice, + 'First handler was called a second time' + ); + assert.notOk( + this.resizeStub2.calledTwice, + 'Second handler was not called a second time' + ); + assert.notOk( + this.resizeStub3.calledTwice, + 'Third handler was not called a second time' + ); + }); });