'use strict'; const MockPointer = require('./MockPointer'); describe('PIXI.interaction.InteractionManager', function () { afterEach(function () { // if we made a MockPointer for the test, clean it up if (this.pointer) { this.pointer.cleanUp(); this.pointer = null; } }); describe('event basics', function () { it('should call mousedown handler', function () { const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const eventSpy = sinon.spy(); const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); graphics.drawRect(0, 0, 50, 50); graphics.interactive = true; graphics.on('mousedown', eventSpy); pointer.mousedown(10, 10); expect(eventSpy).to.have.been.calledOnce; }); it('should call mouseup handler', function () { const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const eventSpy = sinon.spy(); const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); graphics.drawRect(0, 0, 50, 50); graphics.interactive = true; graphics.on('mouseup', eventSpy); pointer.click(10, 10); expect(eventSpy).to.have.been.called; }); it('should call mouseupoutside handler', function () { const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const eventSpy = sinon.spy(); const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); graphics.drawRect(0, 0, 50, 50); graphics.interactive = true; graphics.on('mouseupoutside', eventSpy); pointer.mousedown(10, 10); pointer.mouseup(60, 60); expect(eventSpy).to.have.been.called; }); it('should call mouseover handler', function () { const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const eventSpy = sinon.spy(); const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); graphics.drawRect(0, 0, 50, 50); graphics.interactive = true; graphics.on('mouseover', eventSpy); pointer.mousemove(10, 10); expect(eventSpy).to.have.been.called; }); it('should call mouseout handler', function () { const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const eventSpy = sinon.spy(); const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); graphics.drawRect(0, 0, 50, 50); graphics.interactive = true; graphics.on('mouseout', eventSpy); pointer.mousemove(10, 10); pointer.mousemove(60, 60); expect(eventSpy).to.have.been.called; }); }); describe('add/remove events', function () { let stub; before(function () { stub = sinon.stub(PIXI.interaction.InteractionManager.prototype, 'setTargetElement'); }); after(function () { stub.restore(); }); it('should add and remove pointer events to document', function () { const manager = new PIXI.interaction.InteractionManager(sinon.stub()); const addSpy = sinon.spy(window.document, 'addEventListener'); const removeSpy = sinon.spy(window.document, 'removeEventListener'); manager.interactionDOMElement = { style: {}, addEventListener: sinon.stub(), removeEventListener: sinon.stub() }; manager.supportsPointerEvents = true; manager.addEvents(); expect(addSpy).to.have.been.calledOnce; expect(addSpy).to.have.been.calledWith('pointermove'); manager.removeEvents(); expect(removeSpy).to.have.been.calledOnce; expect(removeSpy).to.have.been.calledWith('pointermove'); addSpy.restore(); removeSpy.restore(); }); it('should add and remove pointer events to window', function () { const manager = new PIXI.interaction.InteractionManager(sinon.stub()); const addSpy = sinon.spy(window, 'addEventListener'); const removeSpy = sinon.spy(window, 'removeEventListener'); manager.interactionDOMElement = { style: {}, addEventListener: sinon.stub(), removeEventListener: sinon.stub() }; manager.supportsPointerEvents = true; manager.addEvents(); expect(addSpy).to.have.been.calledTwice; expect(addSpy).to.have.been.calledWith('pointercancel'); expect(addSpy).to.have.been.calledWith('pointerup'); manager.removeEvents(); expect(removeSpy).to.have.been.calledTwice; expect(removeSpy).to.have.been.calledWith('pointercancel'); expect(removeSpy).to.have.been.calledWith('pointerup'); addSpy.restore(); removeSpy.restore(); }); it('should add and remove pointer events to element', function () { const manager = new PIXI.interaction.InteractionManager(sinon.stub()); const element = { style: {}, addEventListener: sinon.stub(), removeEventListener: sinon.stub() }; manager.interactionDOMElement = element; manager.supportsPointerEvents = true; manager.addEvents(); expect(element.addEventListener).to.have.been.calledThrice; expect(element.addEventListener).to.have.been.calledWith('pointerdown'); expect(element.addEventListener).to.have.been.calledWith('pointerleave'); expect(element.addEventListener).to.have.been.calledWith('pointerover'); manager.removeEvents(); expect(element.removeEventListener).to.have.been.calledThrice; expect(element.removeEventListener).to.have.been.calledWith('pointerdown'); expect(element.removeEventListener).to.have.been.calledWith('pointerleave'); expect(element.removeEventListener).to.have.been.calledWith('pointerover'); }); it('should add and remove mouse events to document', function () { const manager = new PIXI.interaction.InteractionManager(sinon.stub()); const addSpy = sinon.spy(window.document, 'addEventListener'); const removeSpy = sinon.spy(window.document, 'removeEventListener'); manager.interactionDOMElement = { style: {}, addEventListener: sinon.stub(), removeEventListener: sinon.stub() }; manager.supportsPointerEvents = false; manager.addEvents(); expect(addSpy).to.have.been.calledOnce; expect(addSpy).to.have.been.calledWith('mousemove'); manager.removeEvents(); expect(removeSpy).to.have.been.calledOnce; expect(removeSpy).to.have.been.calledWith('mousemove'); addSpy.restore(); removeSpy.restore(); }); it('should add and remove mouse events to window', function () { const manager = new PIXI.interaction.InteractionManager(sinon.stub()); const addSpy = sinon.spy(window, 'addEventListener'); const removeSpy = sinon.spy(window, 'removeEventListener'); manager.interactionDOMElement = { style: {}, addEventListener: sinon.stub(), removeEventListener: sinon.stub() }; manager.supportsPointerEvents = false; manager.addEvents(); expect(addSpy).to.have.been.calledOnce; expect(addSpy).to.have.been.calledWith('mouseup'); manager.removeEvents(); expect(removeSpy).to.have.been.calledOnce; expect(removeSpy).to.have.been.calledWith('mouseup'); addSpy.restore(); removeSpy.restore(); }); it('should add and remove mouse events to element', function () { const manager = new PIXI.interaction.InteractionManager(sinon.stub()); const element = { style: {}, addEventListener: sinon.stub(), removeEventListener: sinon.stub() }; manager.interactionDOMElement = element; manager.supportsPointerEvents = false; manager.supportsTouchEvents = false; manager.addEvents(); expect(element.addEventListener).to.have.been.calledThrice; expect(element.addEventListener).to.have.been.calledWith('mousedown'); expect(element.addEventListener).to.have.been.calledWith('mouseout'); expect(element.addEventListener).to.have.been.calledWith('mouseover'); manager.removeEvents(); expect(element.removeEventListener).to.have.been.calledThrice; expect(element.removeEventListener).to.have.been.calledWith('mousedown'); expect(element.removeEventListener).to.have.been.calledWith('mouseout'); expect(element.removeEventListener).to.have.been.calledWith('mouseover'); }); it('should add and remove touch events to element', function () { const manager = new PIXI.interaction.InteractionManager(sinon.stub()); const element = { style: {}, addEventListener: sinon.stub(), removeEventListener: sinon.stub() }; manager.interactionDOMElement = element; manager.supportsPointerEvents = false; manager.supportsTouchEvents = true; manager.addEvents(); expect(element.addEventListener).to.have.been.calledWith('touchstart'); expect(element.addEventListener).to.have.been.calledWith('touchcancel'); expect(element.addEventListener).to.have.been.calledWith('touchend'); expect(element.addEventListener).to.have.been.calledWith('touchmove'); manager.removeEvents(); expect(element.removeEventListener).to.have.been.calledWith('touchstart'); expect(element.removeEventListener).to.have.been.calledWith('touchcancel'); expect(element.removeEventListener).to.have.been.calledWith('touchend'); expect(element.removeEventListener).to.have.been.calledWith('touchmove'); }); }); describe('onClick', function () { it('should call handler when inside', function () { const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const clickSpy = sinon.spy(); const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); graphics.drawRect(0, 0, 50, 50); graphics.interactive = true; graphics.on('click', clickSpy); pointer.click(10, 10); expect(clickSpy).to.have.been.calledOnce; }); it('should not call handler when outside', function () { const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const clickSpy = sinon.spy(); const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); graphics.drawRect(0, 0, 50, 50); graphics.interactive = true; graphics.on('click', clickSpy); pointer.click(60, 60); expect(clickSpy).to.not.have.been.called; }); it('should not call handler when mousedown not received', function () { const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const clickSpy = sinon.spy(); const pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); graphics.drawRect(0, 0, 50, 50); graphics.interactive = true; graphics.on('click', clickSpy); pointer.mouseup(10, 10); expect(clickSpy, 'click should not happen on first mouseup').to.not.have.been.called; // test again, just because it was a bug that was reported pointer.mouseup(20, 20); expect(clickSpy, 'click should not happen on second mouseup').to.not.have.been.called; }); }); describe('onTap', function () { it('should call handler when inside', function () { const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const clickSpy = sinon.spy(); const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); graphics.drawRect(0, 0, 50, 50); graphics.interactive = true; graphics.on('tap', clickSpy); pointer.tap(10, 10); expect(clickSpy).to.have.been.calledOnce; }); it('should not call handler when outside', function () { const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const clickSpy = sinon.spy(); const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); graphics.drawRect(0, 0, 50, 50); graphics.interactive = true; graphics.on('tap', clickSpy); pointer.tap(60, 60); expect(clickSpy).to.not.have.been.called; }); }); describe('overlapping children', function () { function getScene(callbackEventName, splitParents) { const behindChild = new PIXI.Graphics(); const frontChild = new PIXI.Graphics(); const parent = new PIXI.Container(); 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'; 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, }; } describe('when parent is non-interactive', function () { describe('when both children are interactive', function () { it('should callback front child when clicking front child', function () { const stage = new PIXI.Container(); const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; scene.behindChild.x = 25; scene.frontChild.interactive = true; stage.addChild(scene.parent); pointer.click(10, 10); expect(scene.behindChildCallback).to.not.have.been.called; expect(scene.frontChildCallback).to.have.been.calledOnce; expect(scene.parentCallback).to.not.have.been.called; }); it('should callback front child when clicking overlap', function () { const stage = new PIXI.Container(); const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); 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; }); it('should callback behind child when clicking behind child', function () { const stage = new PIXI.Container(); const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; scene.behindChild.x = 25; scene.frontChild.interactive = true; stage.addChild(scene.parent); pointer.click(60, 10); expect(scene.frontChildCallback).to.not.have.been.called; 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 () { it('should not callback when clicking front child', function () { const stage = new PIXI.Container(); const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; scene.behindChild.x = 25; stage.addChild(scene.parent); pointer.click(10, 10); expect(scene.behindChildCallback).to.not.have.been.called; expect(scene.frontChildCallback).to.not.have.been.called; expect(scene.parentCallback).to.not.have.been.called; }); it('should callback behind child when clicking overlap', function () { const stage = new PIXI.Container(); const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; scene.behindChild.x = 25; stage.addChild(scene.parent); pointer.click(40, 10); expect(scene.behindChildCallback).to.have.been.calledOnce; expect(scene.frontChildCallback).to.not.have.been.called; expect(scene.parentCallback).to.not.have.been.called; }); it('should callback behind child when clicking behind child', function () { const stage = new PIXI.Container(); const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; scene.behindChild.x = 25; stage.addChild(scene.parent); pointer.click(60, 10); expect(scene.frontChildCallback).to.not.have.been.called; expect(scene.behindChildCallback).to.have.been.calledOnce; expect(scene.parentCallback).to.not.have.been.called; }); }); describe('when behind child is non-interactive', function () { it('should callback front child when clicking front child', function () { const stage = new PIXI.Container(); const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.x = 25; scene.frontChild.interactive = true; stage.addChild(scene.parent); pointer.click(10, 10); expect(scene.behindChildCallback).to.not.have.been.called; expect(scene.frontChildCallback).to.have.been.calledOnce; expect(scene.parentCallback).to.not.have.been.called; }); it('should callback front child when clicking overlap', function () { const stage = new PIXI.Container(); const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); 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; }); it('should not callback when clicking behind child', function () { const stage = new PIXI.Container(); const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.x = 25; scene.frontChild.interactive = true; stage.addChild(scene.parent); pointer.click(60, 10); expect(scene.frontChildCallback).to.not.have.been.called; expect(scene.behindChildCallback).to.not.have.been.called; expect(scene.parentCallback).to.not.have.been.called; }); }); }); describe('when parent is interactive', function () { describe('when both children are interactive', function () { it('should callback parent and front child when clicking front child', function () { const stage = new PIXI.Container(); const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; scene.behindChild.x = 25; scene.frontChild.interactive = true; scene.parent.interactive = true; stage.addChild(scene.parent); pointer.click(10, 10); expect(scene.behindChildCallback).to.not.have.been.called; expect(scene.frontChildCallback).to.have.been.calledOnce; expect(scene.parentCallback).to.have.been.calledOnce; }); it('should callback parent and front child when clicking overlap', function () { const stage = new PIXI.Container(); const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); 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; }); it('should callback parent and behind child when clicking behind child', function () { const stage = new PIXI.Container(); const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; scene.behindChild.x = 25; scene.frontChild.interactive = true; scene.parent.interactive = true; stage.addChild(scene.parent); pointer.click(60, 10); expect(scene.frontChildCallback).to.not.have.been.called; 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 () { it('should callback parent when clicking front child', function () { const stage = new PIXI.Container(); const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; scene.behindChild.x = 25; scene.parent.interactive = true; stage.addChild(scene.parent); pointer.click(10, 10); expect(scene.behindChildCallback).to.not.have.been.called; expect(scene.frontChildCallback).to.not.have.been.called; expect(scene.parentCallback).to.have.been.calledOnce; }); it('should callback parent and behind child when clicking overlap', function () { const stage = new PIXI.Container(); const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; scene.behindChild.x = 25; scene.parent.interactive = true; stage.addChild(scene.parent); pointer.click(40, 10); expect(scene.behindChildCallback).to.have.been.calledOnce; expect(scene.frontChildCallback).to.not.have.been.called; expect(scene.parentCallback).to.have.been.calledOnce; }); it('should callback parent and behind child when clicking behind child', function () { const stage = new PIXI.Container(); const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.interactive = true; scene.behindChild.x = 25; scene.parent.interactive = true; stage.addChild(scene.parent); pointer.click(60, 10); expect(scene.frontChildCallback).to.not.have.been.called; expect(scene.behindChildCallback).to.have.been.calledOnce; expect(scene.parentCallback).to.have.been.calledOnce; }); }); describe('when behind child is non-interactive', function () { it('should callback parent and front child when clicking front child', function () { const stage = new PIXI.Container(); const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.x = 25; scene.frontChild.interactive = true; scene.parent.interactive = true; stage.addChild(scene.parent); pointer.click(10, 10); expect(scene.behindChildCallback).to.not.have.been.called; expect(scene.frontChildCallback).to.have.been.calledOnce; expect(scene.parentCallback).to.have.been.calledOnce; }); it('should callback parent and front child when clicking overlap', function () { const stage = new PIXI.Container(); const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); 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; }); it('should callback parent when clicking behind child', function () { const stage = new PIXI.Container(); const pointer = this.pointer = new MockPointer(stage); const scene = getScene('click'); scene.behindChild.x = 25; scene.frontChild.interactive = true; scene.parent.interactive = true; stage.addChild(scene.parent); pointer.click(60, 10); expect(scene.frontChildCallback).to.not.have.been.called; expect(scene.behindChildCallback).to.not.have.been.called; expect(scene.parentCallback).to.have.been.calledOnce; }); }); }); it('Semi-complicated nesting with overlap, should not call behind callback', function () { const stage = new PIXI.Container(); const frontParent = new PIXI.Container(); const frontChild = new PIXI.Graphics(); const behindParent = new PIXI.Container(); const subParent = new PIXI.Container(); const behindChild = new PIXI.Graphics(); const behindCallback = sinon.spy(function behindSpy() { /* no op*/ }); const frontCallback = sinon.spy(function frontSpy() { /* no op*/ }); behindChild.beginFill(0xFF); behindChild.drawRect(0, 0, 50, 50); subParent.on('click', behindCallback); frontChild.beginFill(0x00FF); frontChild.drawRect(0, 0, 50, 50); frontParent.on('click', frontCallback); const pointer = this.pointer = new MockPointer(stage); behindParent.x = 25; subParent.interactive = true; frontParent.interactive = true; behindParent.addChild(subParent); subParent.addChild(behindChild); stage.addChild(behindParent); frontParent.addChild(frontChild); stage.addChild(frontParent); pointer.click(40, 10); expect(behindCallback).to.not.have.been.called; expect(frontCallback).to.have.been.calledOnce; }); }); describe('cursor changes', function () { it('cursor should be the cursor of interactive item', 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; graphics.cursor = 'help'; pointer.interaction.cursorStyles.help = 'help'; pointer.mousemove(10, 10); expect(pointer.renderer.view.style.cursor).to.equal('help'); }); it('should return cursor to default on mouseout', 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; graphics.cursor = 'help'; pointer.interaction.cursorStyles.help = 'help'; pointer.mousemove(10, 10); pointer.mousemove(60, 60); expect(pointer.renderer.view.style.cursor).to.equal(pointer.interaction.cursorStyles.default); }); it('should still be the over cursor after a click', 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; graphics.cursor = 'help'; pointer.interaction.cursorStyles.help = 'help'; pointer.mousemove(10, 10); pointer.click(10, 10); expect(pointer.renderer.view.style.cursor).to.equal('help'); }); it('should return cursor to default when mouse leaves renderer', 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; graphics.cursor = 'help'; pointer.interaction.cursorStyles.help = 'help'; pointer.mousemove(10, 10); pointer.mousemove(-10, 60); expect(pointer.renderer.view.style.cursor).to.equal(pointer.interaction.cursorStyles.default); }); it('cursor callback should be called', function () { const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const overSpy = sinon.spy(); const defaultSpy = sinon.spy(); const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); graphics.drawRect(0, 0, 50, 50); graphics.interactive = true; graphics.cursor = 'help'; pointer.interaction.cursorStyles.help = overSpy; pointer.interaction.cursorStyles.default = defaultSpy; pointer.mousemove(10, 10); pointer.mousemove(60, 60); expect(overSpy).to.have.been.called; expect(defaultSpy).to.have.been.called; }); it('cursor callback should only be called if the cursor actually changed', function () { const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const defaultSpy = sinon.spy(); const pointer = this.pointer = new MockPointer(stage); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); graphics.drawRect(0, 0, 50, 50); graphics.interactive = true; graphics.cursor = null; pointer.interaction.cursorStyles.default = defaultSpy; pointer.mousemove(10, 10); pointer.mousemove(20, 20); expect(defaultSpy).to.have.been.calledOnce; }); it('cursor style object should be fully applied', 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; graphics.cursor = 'help'; pointer.interaction.cursorStyles.help = { cursor: 'none', display: 'none', }; pointer.mousemove(10, 10); expect(pointer.renderer.view.style.cursor).to.equal('none'); expect(pointer.renderer.view.style.display).to.equal('none'); }); it('should not change cursor style if no cursor style provided', 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; graphics.cursor = 'pointer'; pointer.interaction.cursorStyles.pointer = null; pointer.interaction.cursorStyles.default = null; pointer.mousemove(10, 10); expect(pointer.renderer.view.style.cursor).to.equal(''); pointer.mousemove(60, 60); expect(pointer.renderer.view.style.cursor).to.equal(''); }); }); describe('recursive hitTesting', function () { function getScene() { const stage = new PIXI.Container(); const behindChild = new PIXI.Graphics(); const middleChild = new PIXI.Graphics(); const frontChild = new PIXI.Graphics(); behindChild.beginFill(0xFF); behindChild.drawRect(0, 0, 50, 50); middleChild.beginFill(0xFF00); middleChild.drawRect(0, 0, 50, 50); frontChild.beginFill(0xFF0000); frontChild.drawRect(0, 0, 50, 50); stage.addChild(behindChild, middleChild, frontChild); return { behindChild, middleChild, frontChild, stage, }; } describe('when frontChild is interactive', function () { it('should stop hitTesting after first hit', function () { const scene = getScene(); const pointer = this.pointer = new MockPointer(scene.stage); const frontHitTest = sinon.spy(scene.frontChild, 'containsPoint'); const middleHitTest = sinon.spy(scene.middleChild, 'containsPoint'); const behindHitTest = sinon.spy(scene.behindChild, 'containsPoint'); scene.frontChild.interactive = true; scene.middleChild.interactive = true; scene.behindChild.interactive = true; pointer.mousedown(25, 25); expect(frontHitTest).to.have.been.calledOnce; expect(middleHitTest).to.not.have.been.called; expect(behindHitTest).to.not.have.been.called; }); }); describe('when frontChild is not interactive', function () { it('should stop hitTesting after first hit', function () { const scene = getScene(); const pointer = this.pointer = new MockPointer(scene.stage); const frontHitTest = sinon.spy(scene.frontChild, 'containsPoint'); const middleHitTest = sinon.spy(scene.middleChild, 'containsPoint'); const behindHitTest = sinon.spy(scene.behindChild, 'containsPoint'); scene.frontChild.interactive = false; scene.middleChild.interactive = true; scene.behindChild.interactive = true; pointer.mousedown(25, 25); expect(frontHitTest).to.not.have.been.called; expect(middleHitTest).to.have.been.calledOnce; expect(behindHitTest).to.not.have.been.called; }); }); }); describe('pointer handling', function () { it('pointer event from mouse should use single mouse data', function () { const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const pointer = this.pointer = new MockPointer(stage, 100, 100, true); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); graphics.drawRect(0, 0, 50, 50); graphics.interactive = true; pointer.mousemove(20, 10, true); expect(pointer.interaction.mouse.global.x).to.equal(20); 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; }); }); describe('hitTest()', function () { it('should return hit', 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.render(); const hit = pointer.interaction.hitTest(new PIXI.Point(10, 10)); expect(hit).to.equal(graphics); }); it('should return null if not hit', 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.render(); const hit = pointer.interaction.hitTest(new PIXI.Point(60, 60)); expect(hit).to.be.null; }); it('should return top thing that was hit', function () { const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const behind = new PIXI.Graphics(); const pointer = this.pointer = new MockPointer(stage); stage.addChild(behind); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); graphics.drawRect(0, 0, 50, 50); graphics.interactive = true; behind.beginFill(0xFFFFFF); behind.drawRect(0, 0, 50, 50); behind.interactive = true; pointer.render(); const hit = pointer.interaction.hitTest(new PIXI.Point(10, 10)); expect(hit).to.equal(graphics); }); it('should return hit when passing in root', function () { const stage = new PIXI.Container(); const graphics = new PIXI.Graphics(); const behind = new PIXI.Graphics(); const pointer = this.pointer = new MockPointer(stage); stage.addChild(behind); stage.addChild(graphics); graphics.beginFill(0xFFFFFF); graphics.drawRect(0, 0, 50, 50); graphics.interactive = true; behind.beginFill(0xFFFFFF); behind.drawRect(0, 0, 50, 50); behind.interactive = true; pointer.render(); const hit = pointer.interaction.hitTest(new PIXI.Point(10, 10), behind); expect(hit).to.equal(behind); }); }); });