diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index b4891db..d76152c 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -13,6 +13,12 @@ const MOUSE_POINTER_ID = 'MOUSE'; +// private constants for use in processInteractive - tracks whether we hit anything at all, or an +// actual interactive child, so that we can keep that state going back up the display tree +const HIT_NONE = 0; +const HIT_ANY = 1; +const HIT_INTERACTIVE = 2; + /** * The interaction manager deals with mouse, touch and pointer events. Any DisplayObject can be interactive * if its interactive parameter is set to true @@ -761,7 +767,7 @@ * interactionEvent, displayObject and hit will be passed to the function * @param {boolean} [hitTest] - this indicates if the objects inside should be hit test against the point * @param {boolean} [interactive] - Whether the displayObject is interactive - * @return {boolean} returns true if the displayObject hit the point + * @return {number} returns 1 or 2 if the displayObject hit the point, 0 if not */ processInteractive(interactionEvent, displayObject, func, hitTest, interactive) { @@ -787,7 +793,7 @@ interactive = displayObject.interactive || interactive; - let hit = false; + let hit = HIT_NONE; let interactiveParent = interactive; // if the displayobject has a hitArea, then it does not need to hitTest children. @@ -804,8 +810,6 @@ } } - let keepHitTestingAfterChildren = hitTest; - // ** FREE TIP **! If an object is not interactive or has no buttons in it // (such as a game scene!) set interactiveChildren to false for that displayObject. // This will allow pixi to completely ignore and bypass checking the displayObjects children. @@ -818,7 +822,9 @@ const child = children[i]; // time to get recursive.. if this function will return if something is hit.. - if (this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent)) + const childHit = this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent); + + if (childHit) { // its a good idea to check if a child has lost its parent. // this means it has been removed whilst looping so its best @@ -827,8 +833,6 @@ continue; } - hit = true; - // we no longer need to hit test any more objects in this container as we we // now know the parent has been hit interactiveParent = false; @@ -838,36 +842,42 @@ // This means we no longer need to hit test anything else. We still need to run // through all objects, but we don't need to perform any hit tests. - keepHitTestingAfterChildren = false; - - if (child.interactive) + if (childHit === HIT_INTERACTIVE) { hitTest = false; + hit = HIT_INTERACTIVE; } - - // we can break now as we have hit an object. + else if (hit === HIT_NONE) + { + hit = HIT_ANY; + } } } } - hitTest = keepHitTestingAfterChildren; - // no point running this if the item is not interactive or does not have an interactive parent. if (interactive) { // if we are hit testing (as in we have no hit any objects yet) // We also don't need to worry about hit testing if once of the displayObjects children - // has already been hit! - if (hitTest && !hit) + // has already been hit - but only if it was interactive, otherwise we need to keep + // looking for an interactive child, just in case we hit one + if (hitTest && hit !== HIT_INTERACTIVE) { if (displayObject.hitArea) { displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y); + if (displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y)) + { + hit = displayObject.interactive ? HIT_INTERACTIVE : HIT_ANY; + } } else if (displayObject.containsPoint) { - hit = displayObject.containsPoint(point); + if (displayObject.containsPoint(point)) + { + hit = displayObject.interactive ? HIT_INTERACTIVE : HIT_ANY; + } } } @@ -878,7 +888,7 @@ interactionEvent.target = displayObject; } - func(interactionEvent, displayObject, hit); + func(interactionEvent, displayObject, !!hit); } } @@ -1268,6 +1278,12 @@ { this.emit('mouseout', interactionEvent); } + else + { + // we can get touchleave events after touchend, so we want to make sure we don't + // introduce memory leaks + this.releaseInteractionDataForPointerId(interactionData.identifier); + } } /** diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index b4891db..d76152c 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -13,6 +13,12 @@ const MOUSE_POINTER_ID = 'MOUSE'; +// private constants for use in processInteractive - tracks whether we hit anything at all, or an +// actual interactive child, so that we can keep that state going back up the display tree +const HIT_NONE = 0; +const HIT_ANY = 1; +const HIT_INTERACTIVE = 2; + /** * The interaction manager deals with mouse, touch and pointer events. Any DisplayObject can be interactive * if its interactive parameter is set to true @@ -761,7 +767,7 @@ * interactionEvent, displayObject and hit will be passed to the function * @param {boolean} [hitTest] - this indicates if the objects inside should be hit test against the point * @param {boolean} [interactive] - Whether the displayObject is interactive - * @return {boolean} returns true if the displayObject hit the point + * @return {number} returns 1 or 2 if the displayObject hit the point, 0 if not */ processInteractive(interactionEvent, displayObject, func, hitTest, interactive) { @@ -787,7 +793,7 @@ interactive = displayObject.interactive || interactive; - let hit = false; + let hit = HIT_NONE; let interactiveParent = interactive; // if the displayobject has a hitArea, then it does not need to hitTest children. @@ -804,8 +810,6 @@ } } - let keepHitTestingAfterChildren = hitTest; - // ** FREE TIP **! If an object is not interactive or has no buttons in it // (such as a game scene!) set interactiveChildren to false for that displayObject. // This will allow pixi to completely ignore and bypass checking the displayObjects children. @@ -818,7 +822,9 @@ const child = children[i]; // time to get recursive.. if this function will return if something is hit.. - if (this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent)) + const childHit = this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent); + + if (childHit) { // its a good idea to check if a child has lost its parent. // this means it has been removed whilst looping so its best @@ -827,8 +833,6 @@ continue; } - hit = true; - // we no longer need to hit test any more objects in this container as we we // now know the parent has been hit interactiveParent = false; @@ -838,36 +842,42 @@ // This means we no longer need to hit test anything else. We still need to run // through all objects, but we don't need to perform any hit tests. - keepHitTestingAfterChildren = false; - - if (child.interactive) + if (childHit === HIT_INTERACTIVE) { hitTest = false; + hit = HIT_INTERACTIVE; } - - // we can break now as we have hit an object. + else if (hit === HIT_NONE) + { + hit = HIT_ANY; + } } } } - hitTest = keepHitTestingAfterChildren; - // no point running this if the item is not interactive or does not have an interactive parent. if (interactive) { // if we are hit testing (as in we have no hit any objects yet) // We also don't need to worry about hit testing if once of the displayObjects children - // has already been hit! - if (hitTest && !hit) + // has already been hit - but only if it was interactive, otherwise we need to keep + // looking for an interactive child, just in case we hit one + if (hitTest && hit !== HIT_INTERACTIVE) { if (displayObject.hitArea) { displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y); + if (displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y)) + { + hit = displayObject.interactive ? HIT_INTERACTIVE : HIT_ANY; + } } else if (displayObject.containsPoint) { - hit = displayObject.containsPoint(point); + if (displayObject.containsPoint(point)) + { + hit = displayObject.interactive ? HIT_INTERACTIVE : HIT_ANY; + } } } @@ -878,7 +888,7 @@ interactionEvent.target = displayObject; } - func(interactionEvent, displayObject, hit); + func(interactionEvent, displayObject, !!hit); } } @@ -1268,6 +1278,12 @@ { this.emit('mouseout', interactionEvent); } + else + { + // we can get touchleave events after touchend, so we want to make sure we don't + // introduce memory leaks + this.releaseInteractionDataForPointerId(interactionData.identifier); + } } /** diff --git a/test/interaction/InteractionManager.js b/test/interaction/InteractionManager.js index 2a04bc5..d4467fe 100644 --- a/test/interaction/InteractionManager.js +++ b/test/interaction/InteractionManager.js @@ -392,32 +392,62 @@ describe('overlapping children', function () { - function getScene(callbackEventName) + function getScene(callbackEventName, splitParents) { const behindChild = new PIXI.Graphics(); const frontChild = new PIXI.Graphics(); const parent = new PIXI.Container(); - const behindChildCallback = sinon.spy(); - const frontChildCallback = sinon.spy(); - const parentCallback = sinon.spy(); + const behindChildCallback = sinon.spy(function behindSpy() { /* no op*/ }); + const frontChildCallback = sinon.spy(function frontSpy() { /* no op*/ }); + const parentCallback = sinon.spy(function parentSpy() { /* no op*/ }); + let behindParent; + let frontParent; + let behindParentCallback; + let frontParentCallback; behindChild.beginFill(0xFF); behindChild.drawRect(0, 0, 50, 50); behindChild.on(callbackEventName, behindChildCallback); + behindChild.name = 'behind'; frontChild.beginFill(0x00FF); frontChild.drawRect(0, 0, 50, 50); frontChild.on(callbackEventName, frontChildCallback); + frontChild.name = 'front'; - parent.addChild(behindChild, frontChild); + if (splitParents) + { + behindParent = new PIXI.Container(); + behindParent.name = 'behindParent'; + frontParent = new PIXI.Container(); + frontParent.name = 'frontParent'; + behindParentCallback = sinon.spy(function behindParentSpy() { /* no op*/ }); + frontParentCallback = sinon.spy(function frontParentSpy() { /* no op*/ }); + behindParent.on(callbackEventName, behindParentCallback); + frontParent.on(callbackEventName, frontParentCallback); + + parent.addChild(behindParent, frontParent); + behindParent.addChild(behindChild); + frontParent.addChild(frontChild); + + parent.name = 'parent'; + } + else + { + parent.addChild(behindChild, frontChild); + } parent.on(callbackEventName, parentCallback); return { behindChild, frontChild, + behindParent, + frontParent, parent, behindChildCallback, frontChildCallback, + behindParentCallback, + frontParentCallback, parentCallback, }; } @@ -479,6 +509,48 @@ expect(scene.behindChildCallback).to.have.been.calledOnce; expect(scene.parentCallback).to.not.have.been.called; }); + + it('should callback front child of different non-interactive parents when clicking overlap', function () + { + const stage = new PIXI.Container(); + const pointer = this.pointer = new MockPointer(stage); + const scene = getScene('click', true); + + scene.behindChild.interactive = true; + scene.behindChild.x = 25; + scene.frontChild.interactive = true; + + stage.addChild(scene.parent); + pointer.click(40, 10); + + expect(scene.behindChildCallback).to.not.have.been.called; + expect(scene.frontChildCallback).to.have.been.calledOnce; + expect(scene.parentCallback).to.not.have.been.called; + expect(scene.behindParentCallback).to.not.have.been.called; + expect(scene.frontParentCallback).to.not.have.been.called; + }); + + it('should callback front child of different interactive parents when clicking overlap', function () + { + const stage = new PIXI.Container(); + const pointer = this.pointer = new MockPointer(stage); + const scene = getScene('click', true); + + scene.behindChild.interactive = true; + scene.behindChild.x = 25; + scene.frontChild.interactive = true; + scene.behindParent.interactive = true; + scene.frontParent.interactive = true; + + stage.addChild(scene.parent); + pointer.click(40, 10); + + expect(scene.behindChildCallback).to.not.have.been.called; + expect(scene.frontChildCallback).to.have.been.calledOnce; + expect(scene.parentCallback).to.not.have.been.called; + expect(scene.behindParentCallback).to.not.have.been.called; + expect(scene.frontParentCallback).to.have.been.calledOnce; + }); }); describe('when front child is non-interactive', function () @@ -650,6 +722,50 @@ expect(scene.behindChildCallback).to.have.been.calledOnce; expect(scene.parentCallback).to.have.been.calledOnce; }); + + it('should callback front child of different non-interactive parents when clicking overlap', function () + { + const stage = new PIXI.Container(); + const pointer = this.pointer = new MockPointer(stage); + const scene = getScene('click', true); + + scene.behindChild.interactive = true; + scene.behindChild.x = 25; + scene.frontChild.interactive = true; + scene.parent.interactive = true; + + stage.addChild(scene.parent); + pointer.click(40, 10); + + expect(scene.behindChildCallback).to.not.have.been.called; + expect(scene.frontChildCallback).to.have.been.calledOnce; + expect(scene.parentCallback).to.have.been.calledOnce; + expect(scene.behindParentCallback).to.not.have.been.called; + expect(scene.frontParentCallback).to.not.have.been.called; + }); + + it('should callback front child of different interactive parents when clicking overlap', function () + { + const stage = new PIXI.Container(); + const pointer = this.pointer = new MockPointer(stage); + const scene = getScene('click', true); + + scene.behindChild.interactive = true; + scene.behindChild.x = 25; + scene.frontChild.interactive = true; + scene.parent.interactive = true; + scene.behindParent.interactive = true; + scene.frontParent.interactive = true; + + stage.addChild(scene.parent); + pointer.click(40, 10); + + expect(scene.behindChildCallback).to.not.have.been.called; + expect(scene.frontChildCallback).to.have.been.calledOnce; + expect(scene.parentCallback).to.have.been.calledOnce; + expect(scene.behindParentCallback).to.not.have.been.called; + expect(scene.frontParentCallback).to.have.been.calledOnce; + }); }); describe('when front child is non-interactive', function () @@ -1024,4 +1140,26 @@ expect(pointer.interaction.mouse.global.y).to.equal(10); }); }); + + describe('data cleanup', function () + { + it('touchleave after touchout should not orphan data', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const pointer = this.pointer = new MockPointer(stage); + + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + + pointer.touchstart(10, 10, 42); + expect(pointer.interaction.activeInteractionData[42]).to.exist; + pointer.touchend(10, 10, 42); + expect(pointer.interaction.activeInteractionData[42]).to.be.undefined; + pointer.touchleave(10, 10, 42); + expect(pointer.interaction.activeInteractionData[42]).to.be.undefined; + }); + }); }); diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index b4891db..d76152c 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -13,6 +13,12 @@ const MOUSE_POINTER_ID = 'MOUSE'; +// private constants for use in processInteractive - tracks whether we hit anything at all, or an +// actual interactive child, so that we can keep that state going back up the display tree +const HIT_NONE = 0; +const HIT_ANY = 1; +const HIT_INTERACTIVE = 2; + /** * The interaction manager deals with mouse, touch and pointer events. Any DisplayObject can be interactive * if its interactive parameter is set to true @@ -761,7 +767,7 @@ * interactionEvent, displayObject and hit will be passed to the function * @param {boolean} [hitTest] - this indicates if the objects inside should be hit test against the point * @param {boolean} [interactive] - Whether the displayObject is interactive - * @return {boolean} returns true if the displayObject hit the point + * @return {number} returns 1 or 2 if the displayObject hit the point, 0 if not */ processInteractive(interactionEvent, displayObject, func, hitTest, interactive) { @@ -787,7 +793,7 @@ interactive = displayObject.interactive || interactive; - let hit = false; + let hit = HIT_NONE; let interactiveParent = interactive; // if the displayobject has a hitArea, then it does not need to hitTest children. @@ -804,8 +810,6 @@ } } - let keepHitTestingAfterChildren = hitTest; - // ** FREE TIP **! If an object is not interactive or has no buttons in it // (such as a game scene!) set interactiveChildren to false for that displayObject. // This will allow pixi to completely ignore and bypass checking the displayObjects children. @@ -818,7 +822,9 @@ const child = children[i]; // time to get recursive.. if this function will return if something is hit.. - if (this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent)) + const childHit = this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent); + + if (childHit) { // its a good idea to check if a child has lost its parent. // this means it has been removed whilst looping so its best @@ -827,8 +833,6 @@ continue; } - hit = true; - // we no longer need to hit test any more objects in this container as we we // now know the parent has been hit interactiveParent = false; @@ -838,36 +842,42 @@ // This means we no longer need to hit test anything else. We still need to run // through all objects, but we don't need to perform any hit tests. - keepHitTestingAfterChildren = false; - - if (child.interactive) + if (childHit === HIT_INTERACTIVE) { hitTest = false; + hit = HIT_INTERACTIVE; } - - // we can break now as we have hit an object. + else if (hit === HIT_NONE) + { + hit = HIT_ANY; + } } } } - hitTest = keepHitTestingAfterChildren; - // no point running this if the item is not interactive or does not have an interactive parent. if (interactive) { // if we are hit testing (as in we have no hit any objects yet) // We also don't need to worry about hit testing if once of the displayObjects children - // has already been hit! - if (hitTest && !hit) + // has already been hit - but only if it was interactive, otherwise we need to keep + // looking for an interactive child, just in case we hit one + if (hitTest && hit !== HIT_INTERACTIVE) { if (displayObject.hitArea) { displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y); + if (displayObject.hitArea.contains(this._tempPoint.x, this._tempPoint.y)) + { + hit = displayObject.interactive ? HIT_INTERACTIVE : HIT_ANY; + } } else if (displayObject.containsPoint) { - hit = displayObject.containsPoint(point); + if (displayObject.containsPoint(point)) + { + hit = displayObject.interactive ? HIT_INTERACTIVE : HIT_ANY; + } } } @@ -878,7 +888,7 @@ interactionEvent.target = displayObject; } - func(interactionEvent, displayObject, hit); + func(interactionEvent, displayObject, !!hit); } } @@ -1268,6 +1278,12 @@ { this.emit('mouseout', interactionEvent); } + else + { + // we can get touchleave events after touchend, so we want to make sure we don't + // introduce memory leaks + this.releaseInteractionDataForPointerId(interactionData.identifier); + } } /** diff --git a/test/interaction/InteractionManager.js b/test/interaction/InteractionManager.js index 2a04bc5..d4467fe 100644 --- a/test/interaction/InteractionManager.js +++ b/test/interaction/InteractionManager.js @@ -392,32 +392,62 @@ describe('overlapping children', function () { - function getScene(callbackEventName) + function getScene(callbackEventName, splitParents) { const behindChild = new PIXI.Graphics(); const frontChild = new PIXI.Graphics(); const parent = new PIXI.Container(); - const behindChildCallback = sinon.spy(); - const frontChildCallback = sinon.spy(); - const parentCallback = sinon.spy(); + const behindChildCallback = sinon.spy(function behindSpy() { /* no op*/ }); + const frontChildCallback = sinon.spy(function frontSpy() { /* no op*/ }); + const parentCallback = sinon.spy(function parentSpy() { /* no op*/ }); + let behindParent; + let frontParent; + let behindParentCallback; + let frontParentCallback; behindChild.beginFill(0xFF); behindChild.drawRect(0, 0, 50, 50); behindChild.on(callbackEventName, behindChildCallback); + behindChild.name = 'behind'; frontChild.beginFill(0x00FF); frontChild.drawRect(0, 0, 50, 50); frontChild.on(callbackEventName, frontChildCallback); + frontChild.name = 'front'; - parent.addChild(behindChild, frontChild); + if (splitParents) + { + behindParent = new PIXI.Container(); + behindParent.name = 'behindParent'; + frontParent = new PIXI.Container(); + frontParent.name = 'frontParent'; + behindParentCallback = sinon.spy(function behindParentSpy() { /* no op*/ }); + frontParentCallback = sinon.spy(function frontParentSpy() { /* no op*/ }); + behindParent.on(callbackEventName, behindParentCallback); + frontParent.on(callbackEventName, frontParentCallback); + + parent.addChild(behindParent, frontParent); + behindParent.addChild(behindChild); + frontParent.addChild(frontChild); + + parent.name = 'parent'; + } + else + { + parent.addChild(behindChild, frontChild); + } parent.on(callbackEventName, parentCallback); return { behindChild, frontChild, + behindParent, + frontParent, parent, behindChildCallback, frontChildCallback, + behindParentCallback, + frontParentCallback, parentCallback, }; } @@ -479,6 +509,48 @@ expect(scene.behindChildCallback).to.have.been.calledOnce; expect(scene.parentCallback).to.not.have.been.called; }); + + it('should callback front child of different non-interactive parents when clicking overlap', function () + { + const stage = new PIXI.Container(); + const pointer = this.pointer = new MockPointer(stage); + const scene = getScene('click', true); + + scene.behindChild.interactive = true; + scene.behindChild.x = 25; + scene.frontChild.interactive = true; + + stage.addChild(scene.parent); + pointer.click(40, 10); + + expect(scene.behindChildCallback).to.not.have.been.called; + expect(scene.frontChildCallback).to.have.been.calledOnce; + expect(scene.parentCallback).to.not.have.been.called; + expect(scene.behindParentCallback).to.not.have.been.called; + expect(scene.frontParentCallback).to.not.have.been.called; + }); + + it('should callback front child of different interactive parents when clicking overlap', function () + { + const stage = new PIXI.Container(); + const pointer = this.pointer = new MockPointer(stage); + const scene = getScene('click', true); + + scene.behindChild.interactive = true; + scene.behindChild.x = 25; + scene.frontChild.interactive = true; + scene.behindParent.interactive = true; + scene.frontParent.interactive = true; + + stage.addChild(scene.parent); + pointer.click(40, 10); + + expect(scene.behindChildCallback).to.not.have.been.called; + expect(scene.frontChildCallback).to.have.been.calledOnce; + expect(scene.parentCallback).to.not.have.been.called; + expect(scene.behindParentCallback).to.not.have.been.called; + expect(scene.frontParentCallback).to.have.been.calledOnce; + }); }); describe('when front child is non-interactive', function () @@ -650,6 +722,50 @@ expect(scene.behindChildCallback).to.have.been.calledOnce; expect(scene.parentCallback).to.have.been.calledOnce; }); + + it('should callback front child of different non-interactive parents when clicking overlap', function () + { + const stage = new PIXI.Container(); + const pointer = this.pointer = new MockPointer(stage); + const scene = getScene('click', true); + + scene.behindChild.interactive = true; + scene.behindChild.x = 25; + scene.frontChild.interactive = true; + scene.parent.interactive = true; + + stage.addChild(scene.parent); + pointer.click(40, 10); + + expect(scene.behindChildCallback).to.not.have.been.called; + expect(scene.frontChildCallback).to.have.been.calledOnce; + expect(scene.parentCallback).to.have.been.calledOnce; + expect(scene.behindParentCallback).to.not.have.been.called; + expect(scene.frontParentCallback).to.not.have.been.called; + }); + + it('should callback front child of different interactive parents when clicking overlap', function () + { + const stage = new PIXI.Container(); + const pointer = this.pointer = new MockPointer(stage); + const scene = getScene('click', true); + + scene.behindChild.interactive = true; + scene.behindChild.x = 25; + scene.frontChild.interactive = true; + scene.parent.interactive = true; + scene.behindParent.interactive = true; + scene.frontParent.interactive = true; + + stage.addChild(scene.parent); + pointer.click(40, 10); + + expect(scene.behindChildCallback).to.not.have.been.called; + expect(scene.frontChildCallback).to.have.been.calledOnce; + expect(scene.parentCallback).to.have.been.calledOnce; + expect(scene.behindParentCallback).to.not.have.been.called; + expect(scene.frontParentCallback).to.have.been.calledOnce; + }); }); describe('when front child is non-interactive', function () @@ -1024,4 +1140,26 @@ expect(pointer.interaction.mouse.global.y).to.equal(10); }); }); + + describe('data cleanup', function () + { + it('touchleave after touchout should not orphan data', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const pointer = this.pointer = new MockPointer(stage); + + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + + pointer.touchstart(10, 10, 42); + expect(pointer.interaction.activeInteractionData[42]).to.exist; + pointer.touchend(10, 10, 42); + expect(pointer.interaction.activeInteractionData[42]).to.be.undefined; + pointer.touchleave(10, 10, 42); + expect(pointer.interaction.activeInteractionData[42]).to.be.undefined; + }); + }); }); diff --git a/test/interaction/MockPointer.js b/test/interaction/MockPointer.js index c843045..c9c6228 100644 --- a/test/interaction/MockPointer.js +++ b/test/interaction/MockPointer.js @@ -34,6 +34,8 @@ this.renderer = new PIXI.CanvasRenderer(width || 100, height || 100); this.renderer.sayHello = () => { /* empty */ }; this.interaction = this.renderer.plugins.interaction; + this.interaction.supportsTouchEvents = true; + PIXI.ticker.shared.remove(this.interaction.update, this.interaction); } /** @@ -171,23 +173,25 @@ /** * @param {number} x - pointer x position * @param {number} y - pointer y position + * @param {number} [identifier] - pointer id */ - tap(x, y) + tap(x, y, identifier) { - this.touchstart(x, y); - this.touchend(x, y); + this.touchstart(x, y, identifier); + this.touchend(x, y, identifier); } /** * @param {number} x - pointer x position * @param {number} y - pointer y position + * @param {number} [identifier] - pointer id */ - touchstart(x, y) + touchstart(x, y, identifier) { const touchEvent = new TouchEvent('touchstart', { preventDefault: sinon.stub(), changedTouches: [ - new Touch({ identifier: 0, target: this.renderer.view }), + new Touch({ identifier: identifier || 0, target: this.renderer.view }), ], }); @@ -199,13 +203,14 @@ /** * @param {number} x - pointer x position * @param {number} y - pointer y position + * @param {number} [identifier] - pointer id */ - touchend(x, y) + touchend(x, y, identifier) { const touchEvent = new TouchEvent('touchend', { preventDefault: sinon.stub(), changedTouches: [ - new Touch({ identifier: 0, target: this.renderer.view }), + new Touch({ identifier: identifier || 0, target: this.renderer.view }), ], }); @@ -213,6 +218,25 @@ this.render(); this.interaction.onPointerUp(touchEvent); } + + /** + * @param {number} x - pointer x position + * @param {number} y - pointer y position + * @param {number} [identifier] - pointer id + */ + touchleave(x, y, identifier) + { + const touchEvent = new TouchEvent('touchleave', { + preventDefault: sinon.stub(), + changedTouches: [ + new Touch({ identifier: identifier || 0, target: this.renderer.view }), + ], + }); + + this.setPosition(x, y); + this.render(); + this.interaction.onPointerOut(touchEvent); + } } module.exports = MockPointer;