<commit_before>'use strict';

const MockPointer = require('./MockPointer');

describe('PIXI.interaction.InteractionManager', function ()
{
    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 = 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 = 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;
        });
    });

    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 = 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 = 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)
        {
            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();

            behindChild.beginFill(0xFF);
            behindChild.drawRect(0, 0, 50, 50);
            behindChild.on(callbackEventName, behindChildCallback);

            frontChild.beginFill(0x00FF);
            frontChild.drawRect(0, 0, 50, 50);
            frontChild.on(callbackEventName, frontChildCallback);

            parent.addChild(behindChild, frontChild);
            parent.on(callbackEventName, parentCallback);

            return {
                behindChild,
                frontChild,
                parent,
                behindChildCallback,
                frontChildCallback,
                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 = 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 = 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 = 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;
                });
            });

            describe('when front child is non-interactive', function ()
            {
                it('should not callback when clicking front child', function ()
                {
                    const stage = new PIXI.Container();
                    const 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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;
                });
            });

            describe('when front child is non-interactive', function ()
            {
                it('should callback parent when clicking front child', function ()
                {
                    const stage = new PIXI.Container();
                    const 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;
                });

                /* TODO: Fix #3596
                it('should callback parent and behind child when clicking overlap', function ()
                {
                    const stage = new PIXI.Container();
                    const 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 = 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 = 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 = 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 = 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;
                });
            });
        });
    });
});
<commit_msg>Added recursive children hitTest tests (#3665)<commit_after>'use strict';

const MockPointer = require('./MockPointer');

describe('PIXI.interaction.InteractionManager', function ()
{
    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 = 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 = 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;
        });
    });

    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 = 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 = 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)
        {
            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();

            behindChild.beginFill(0xFF);
            behindChild.drawRect(0, 0, 50, 50);
            behindChild.on(callbackEventName, behindChildCallback);

            frontChild.beginFill(0x00FF);
            frontChild.drawRect(0, 0, 50, 50);
            frontChild.on(callbackEventName, frontChildCallback);

            parent.addChild(behindChild, frontChild);
            parent.on(callbackEventName, parentCallback);

            return {
                behindChild,
                frontChild,
                parent,
                behindChildCallback,
                frontChildCallback,
                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 = 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 = 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 = 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;
                });
            });

            describe('when front child is non-interactive', function ()
            {
                it('should not callback when clicking front child', function ()
                {
                    const stage = new PIXI.Container();
                    const 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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;
                });
            });

            describe('when front child is non-interactive', function ()
            {
                it('should callback parent when clicking front child', function ()
                {
                    const stage = new PIXI.Container();
                    const 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;
                });

                /* TODO: Fix #3596
                it('should callback parent and behind child when clicking overlap', function ()
                {
                    const stage = new PIXI.Container();
                    const 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 = 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 = 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 = 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 = 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;
                });
            });
        });
    });

    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 = 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 = 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;
            });
        });
    });
});
<|endoftext|>


<commit_before>var wsURL;
var itemValueChartUrl;

var WS;
var WSConnectionOpened;

var itemValues = {};

function initWebSocket(onOpenCallback, onMessageCallback, onCloseCallback) {
    WS = new WebSocket(wsURL);

    WS.onmessage = onMessageCallback;
    WS.onopen = onOpenCallback;
    WS.onclose = onCloseCallback;
}

initWebSocket(function () {
    WSConnectionOpened = true;

    $('#loader, .linear-loader').fadeOut(function () {
        $('.control-panel').fadeIn();
    });
}, onMessage, function () {
    if (WSConnectionOpened) {
        $('.control-panel').fadeOut(function () {
            $('#loader').fadeIn().addClass('error');
        });
    } else {
        $('#loader').addClass('error');
        $('.linear-loader').fadeOut();
    }

    WSConnectionOpened = false;
});

function onMessage(e) {
    try {
        var data = JSON.parse(e.data);

        console.log(data);

        switch (data.type) {
            case 'init':
                $.each(data.items, function (key, value) {
                    if (value.type == 30) {
                        updateRGB(value.id, value.value);
                    } else {
                        if (value.value != 'N/A') {
                            saveItemValue(value.id, value.value);
                        }

                        updateItemValue(value.id, value.type, value.value, value.value_type);
                    }
                });

                afterConnected();

                break;
            case 'value':
                updateValue(data);
                break;
            case 'rgb':
                updateRGB(data.item_id, data);
                break;
            case 'error':
                showErrorMessage(data.message);
                break;
        }
    } catch (e) {
        showErrorMessage('Ошибка обработки ответа от сервера');
        console.log("Error: " + e);
    }
}

function afterConnected() {
    WSConnectionOpened = true;

    $('#loader').fadeOut(function () {
        $('.control-panel').fadeIn();
    });
}

function send(data) {
    console.log(data);

    if (typeof data != "string") {
        data = JSON.stringify(data);
    }

    if (WS && WS.readyState == 1) {
        WS.send(data);
    }
}

function updateValue(data) {
    updateItemValue(data.item_id, data.item_type, data.value, data.value_type);
}

function updateRGB(itemId, data) {
    itemValues[itemId] = data;

    if (data.mode == 'static' || data.mode == 'fade') {
        var $panelItem = $('.panel-item-rgb[data-item-id="' + itemId + '"]');

        $panelItem.css('background-color', 'rgb(' + data.red + ',' + data.green + ',' + data.blue + ')');
        $panelItem.css('color', textColorDepOnBackground(data.red, data.green, data.blue));
    }

    $('#rgb-widget-wave-fade-time').val(data.fade_time);

    if (data.mode == 'fade' || data.mode == 'wave') {
        $('#rgb-widget-wave-color-time').val(data.color_time);
    }
}

function itemSwitchOn(itemId) {
    var $item = $('.panel-item-switch[data-item-id="' + itemId + '"]');
    $item.removeClass('off');
}

function itemSwitchOff(itemId) {
    var $item = $('.panel-item-switch[data-item-id="' + itemId + '"]');
    $item.addClass('off');
}

function itemSetValue(itemId, value) {
    $('.panel-item-variable[data-item-id="' + itemId + '"] > .item-variable-value').html(value);
}

function saveItemValue(itemId, value) {
    itemValues[itemId] = value;
}

function getSavedItemValue(itemId) {
    return itemValues[itemId];
}

function updateItemValue(id, type, value, value_type) {
    type = parseInt(type);

    switch (type) {
        case 10:    // Switch
            value = Boolean(value);

            if (value) {
                itemSwitchOn(id);
            } else {
                itemSwitchOff(id);
            }

            break;
        case 20:    // Variable
            if (!value_type || value_type == null) {
                return itemSetValue(id, value);
            }

            switch (value_type) {
                case 10:    // Boolean
                    if (Boolean(value)) {
                        if (value != 'N/A') {
                            value = 'да';
                        }
                    } else {
                        value = 'нет';
                    }

                    break;
                case 20:    // Variable boolean door
                    if (value) {
                        if (value != 'N/A') {
                            value = 'открыто';
                        }
                    } else {
                        value = 'закрыто';
                    }

                    break;
                case 30:    // Celsius
                    value += ' °C';
                    break;
                case 40:    // Percent
                    value += '%';
                    break;
            }

            itemSetValue(id, value);

            break;
    }
}

function textColorDepOnBackground(red, green, blue) {
    var brightness = Math.round((red * 299 + green * 587 + blue * 114) / 1000);
    var textColor;

    if (brightness > 125) {
        textColor = 'black';
    } else {
        textColor = 'white';
    }

    return textColor;
}

$(document).ready(function () {
    $('.panel-item-switch').click(function (e) {
        e.preventDefault();

        var $this = $(this);

        var item_id = $this.data('item-id');

        send({
            "type": $this.hasClass('off') ? 'turn_on' : 'turn_off',
            "item_id": item_id
        });
    });

    $('.panel-item-variable').tooltip();

    // Value widget
    $('body').on('click', '.panel-item-variable', function () {
        var $modal = $('#item-chart-modal');
        var $this = $(this);

        var type = parseInt($this.data('item-type'));
        var title = $this.data('original-title');
        var itemId = $this.data('item-id');

        var data;

        if (type !== 20 && type !== 21 && type !== 22 && type !== 23) {
            return;
        }

        $.ajax({
            url: itemValueChartUrl + '&item_id=' + itemId,
            dataType: 'json'
        }).success(function (result) {
            $modal.find('.item-chart-name').html(title);

            var $canvas = $modal.find('#item-chart');

            Date.prototype.formatMMDDYYYY = function () {
                return this.getHours() + ':' + this.getMinutes() + ':' + this.getSeconds();
            };

            // Split timestamp and data into separate arrays
            var labels = [],
                data = [];

            $.each(result.data, function (key, value) {
                labels.push(new Date(key * 1000).formatMMDDYYYY());
                data.push(parseInt(value));
            });

            // Create the chart.js data structure using 'labels' and 'data'
            var tempData = {
                labels: labels,
                datasets: [{
                    label: title,
                    data: data,
                    lineTension: 0,
                    backgroundColor: 'transparent',
                    borderColor: '#009688'
                }]
            };

            // Get the context of the canvas element we want to select
            var ctx = $canvas.get(0).getContext("2d");

            // Instantiate a new chart
            var myLineChart = new Chart(ctx, {
                type: 'line',
                data: tempData
            });

            $modal.modal('show').on('hidden.bs.modal', function (e) {
                myLineChart.destroy();
                $modal.find('canvas').remove();
                $modal.find('.modal-body').append('<canvas id="item-chart"></canvas>');
            })
        }).fail(function () {
            showErrorMessage('Не удалось получить данные');
        });
    });

    // RGB Widget
    $('.panel-item-rgb')
        .popover({
            html: true,
            placement: 'bottom',
            trigger: 'click',
            // container: 'body',
            content: function () {
                var source = $("#rgb-item-widget-popover-content").html();
                var template = Handlebars.compile(source);

                return template({
                    item_id: $(this).data('item-id')
                });
            }
        })
        // On popover init
        .on('inserted.bs.popover', function () {
            var item_id = $(this).data('item-id');

            // Init colorpicker
            var $colorPicker = $('body').find('.rgb-widget-colorpicker[data-item-id="' + item_id + '"]').spectrum({
                flat: true,
                showInput: false,
                showButtons: false,
                preferredFormat: 'rgb'
            });

            var savedItemValue = getSavedItemValue(item_id);

            // Set colorpicker value
            if (savedItemValue != null) {
                $colorPicker.spectrum('set', 'rgb(' + savedItemValue['red'] + ',' + savedItemValue['green'] + ',' + savedItemValue['blue'] + ')');
            }

            // Open mode tabs and set variables
            if (savedItemValue.mode == 'static') {
                $('.rgb-widget-mode-static').tab('show');
            } else if (savedItemValue.mode == 'wave') {
                $('#rgb-widget-wave-color-time').val(savedItemValue.color_time);
                $('#rgb-widget-fade-color-time').val(savedItemValue.color_time);

                $('.rgb-widget-mode-wave').tab('show');
            } else if (savedItemValue.mode == 'fade') {
                $('#rgb-widget-wave-color-time').val(savedItemValue.color_time);
                $('#rgb-widget-fade-color-time').val(savedItemValue.color_time);

                $('.rgb-widget-mode-fade').tab('show');
            }

            $('#rgb-widget-static-fade-time').val(savedItemValue.fade_time);
            $('#rgb-widget-wave-fade-time').val(savedItemValue.fade_time);
            $('#rgb-widget-fade-fade-time').val(savedItemValue.fade_time);

            // On color select
            $colorPicker.on("dragstop.spectrum", function (e, color) {
                var red = Math.round(color._r);
                var green = Math.round(color._g);
                var blue = Math.round(color._b);

                var $this = $(this);
                var itemId = $this.data('item-id');

                var $panelItem = $('.panel-item-rgb[data-item-id="' + itemId + '"]');

                // Update text color
                $panelItem.css('background-color', 'rgb(' + red + ',' + green + ',' + blue + ')');
                $panelItem.css('color', textColorDepOnBackground(red, green, blue));

                var modeId = $this.parents('.tab-pane').attr('id');

                if (modeId == 'rgb-widget-static') {
                    send({
                        "type": "rgb",
                        "item_id": item_id,
                        "fade_time": parseInt($('#rgb-widget-static-fade-time').val()),
                        "mode": "static",
                        "red": red,
                        "green": green,
                        "blue": blue
                    });
                } else if (modeId == 'rgb-widget-fade') {
                    send({
                        "type": "rgb",
                        "item_id": item_id,
                        "fade_time": parseInt($('#rgb-widget-fade-fade-time').val()),
                        "color_time": parseInt($('#rgb-widget-fade-color-time').val()),
                        "mode": "fade",
                        "red": red,
                        "green": green,
                        "blue": blue
                    });
                }
            });

            $('.btn-save-times').click(function (e) {
                e.preventDefault();

                var mode = $(this).data('mode');

                if (mode == 'wave') {
                    send({
                        "type": "rgb",
                        "item_id": item_id,
                        "mode": "wave",
                        "fade_time": parseInt($('#rgb-widget-wave-fade-time').val()),
                        "color_time": parseInt($('#rgb-widget-wave-color-time').val())
                    });
                } else if (mode == 'fade') {
                    var color = $('.rgb-widget-colorpicker-fade').spectrum('get');
                    var red = Math.round(color._r);
                    var green = Math.round(color._g);
                    var blue = Math.round(color._b);

                    send({
                        "type": "rgb",
                        "item_id": item_id,
                        "mode": "fade",
                        "fade_time": parseInt($('#rgb-widget-fade-fade-time').val()),
                        "color_time": parseInt($('#rgb-widget-fade-color-time').val()),
                        "red": red,
                        "green": green,
                        "blue": blue
                    });
                }
            });

            $('.rgb-widget-popover-content[data-item-id="' + item_id + '"]').on('click', '.rgb-widget-mode', function (e) {
                var $this = $(this);

                if ($this.attr('aria-expanded') == 'true') {
                    return;
                }

                if ($this.hasClass('rgb-widget-mode-static')) {
                    var color = $('.rgb-widget-colorpicker-static').spectrum('get');
                    var red = Math.round(color._r);
                    var green = Math.round(color._g);
                    var blue = Math.round(color._b);

                    send({
                        "type": "rgb",
                        "item_id": item_id,
                        "fade_time": parseInt($('#rgb-widget-static-fade-time').val()),
                        "mode": "static",
                        "red": red,
                        "green": green,
                        "blue": blue
                    });
                } else if ($this.hasClass('rgb-widget-mode-wave')) {
                    send({
                        "type": "rgb",
                        "item_id": item_id,
                        "mode": "wave",
                        "fade_time": parseInt($('#rgb-widget-wave-fade-time').val()),
                        "color_time": parseInt($('#rgb-widget-wave-color-time').val())
                    });
                } else if ($this.hasClass('rgb-widget-mode-fade')) {
                    var color = $('.rgb-widget-colorpicker-fade').spectrum('get');
                    var red = Math.round(color._r);
                    var green = Math.round(color._g);
                    var blue = Math.round(color._b);

                    send({
                        "type": "rgb",
                        "item_id": item_id,
                        "mode": "fade",
                        "fade_time": parseInt($('#rgb-widget-fade-fade-time').val()),
                        "color_time": parseInt($('#rgb-widget-fade-color-time').val()),
                        "red": red,
                        "green": green,
                        "blue": blue
                    });
                }
            });
        });
});
<commit_msg>Try #55<commit_after>var wsURL;
var itemValueChartUrl;

var WS;
var WSConnectionOpened;

var itemValues = {};

function initWebSocket(onOpenCallback, onMessageCallback, onCloseCallback) {
    WS = new WebSocket(wsURL);

    WS.onmessage = onMessageCallback;
    WS.onopen = onOpenCallback;
    WS.onclose = onCloseCallback;
}

initWebSocket(function () {
    WSConnectionOpened = true;

    $('#loader, .linear-loader').fadeOut(function () {
        $('.control-panel').fadeIn();
    });
}, onMessage, function () {
    if (WSConnectionOpened) {
        $('.control-panel').fadeOut(function () {
            $('#loader').fadeIn().addClass('error');
        });
    } else {
        $('#loader').addClass('error');
        $('.linear-loader').fadeOut();
    }

    WSConnectionOpened = false;
});

function onMessage(e) {
    try {
        var data = JSON.parse(e.data);

        console.log(data);

        switch (data.type) {
            case 'init':
                $.each(data.items, function (key, value) {
                    if (value.type == 30) {
                        updateRGB(value.id, value.value);
                    } else {
                        if (value.value != 'N/A') {
                            saveItemValue(value.id, value.value);
                        }

                        updateItemValue(value.id, value.type, value.value, value.value_type);
                    }
                });

                afterConnected();

                break;
            case 'value':
                updateValue(data);
                break;
            case 'rgb':
                updateRGB(data.item_id, data);
                break;
            case 'error':
                showErrorMessage(data.message);
                break;
        }
    } catch (e) {
        showErrorMessage('Ошибка обработки ответа от сервера');
        console.log("Error: " + e);
    }
}

function afterConnected() {
    WSConnectionOpened = true;

    $('#loader').fadeOut(function () {
        $('.control-panel').fadeIn();
    });
}

function send(data) {
    console.log(data);

    if (typeof data != "string") {
        data = JSON.stringify(data);
    }

    if (WS && WS.readyState == 1) {
        WS.send(data);
    }
}

function updateValue(data) {
    updateItemValue(data.item_id, data.item_type, data.value, data.value_type);
}

function updateRGB(itemId, data) {
    itemValues[itemId] = data;

    if (data.mode == 'static' || data.mode == 'fade') {
        var $panelItem = $('.panel-item-rgb[data-item-id="' + itemId + '"]');

        $panelItem.css('background-color', 'rgb(' + data.red + ',' + data.green + ',' + data.blue + ')');
        $panelItem.css('color', textColorDepOnBackground(data.red, data.green, data.blue));
    }

    $('#rgb-widget-wave-fade-time').val(data.fade_time);

    if (data.mode == 'fade' || data.mode == 'wave') {
        $('#rgb-widget-wave-color-time').val(data.color_time);
    }
}

function itemSwitchOn(itemId) {
    var $item = $('.panel-item-switch[data-item-id="' + itemId + '"]');
    $item.removeClass('off');
}

function itemSwitchOff(itemId) {
    var $item = $('.panel-item-switch[data-item-id="' + itemId + '"]');
    $item.addClass('off');
}

function itemSetValue(itemId, value) {
    $('.panel-item-variable[data-item-id="' + itemId + '"] > .item-variable-value').html(value);
}

function saveItemValue(itemId, value) {
    itemValues[itemId] = value;
}

function getSavedItemValue(itemId) {
    return itemValues[itemId];
}

function updateItemValue(id, type, value, value_type) {
    type = parseInt(type);

    switch (type) {
        case 10:    // Switch
            value = Boolean(value);

            if (value) {
                itemSwitchOn(id);
            } else {
                itemSwitchOff(id);
            }

            break;
        case 20:    // Variable
            if (!value_type || value_type == null) {
                return itemSetValue(id, value);
            }

            switch (value_type) {
                case 10:    // Boolean
                    if (Boolean(value)) {
                        if (value != 'N/A') {
                            value = 'да';
                        }
                    } else {
                        value = 'нет';
                    }

                    break;
                case 20:    // Variable boolean door
                    if (value) {
                        if (value != 'N/A') {
                            value = 'открыто';
                        }
                    } else {
                        value = 'закрыто';
                    }

                    break;
                case 30:    // Celsius
                    value += ' °C';
                    break;
                case 40:    // Percent
                    value += '%';
                    break;
            }

            itemSetValue(id, value);

            break;
    }
}

function textColorDepOnBackground(red, green, blue) {
    var brightness = Math.round((red * 299 + green * 587 + blue * 114) / 1000);
    var textColor;

    if (brightness > 125) {
        textColor = 'black';
    } else {
        textColor = 'white';
    }

    return textColor;
}

$(document).ready(function () {
    $('.panel-item-switch').click(function (e) {
        e.preventDefault();

        var $this = $(this);

        var item_id = $this.data('item-id');

        send({
            "type": $this.hasClass('off') ? 'turn_on' : 'turn_off',
            "item_id": item_id
        });
    });

    $('.panel-item-variable').tooltip();

    // Value widget
    $('body').on('click', '.panel-item-variable', function () {
        var $modal = $('#item-chart-modal');
        var $this = $(this);

        var type = parseInt($this.data('item-type'));
        var title = $this.data('original-title');
        var itemId = $this.data('item-id');

        var data;

        if (type !== 20 && type !== 21 && type !== 22 && type !== 23) {
            return;
        }

        $.ajax({
            url: itemValueChartUrl + '&item_id=' + itemId,
            dataType: 'json'
        }).success(function (result) {
            $modal.find('.item-chart-name').html(title);

            var $canvas = $modal.find('#item-chart');

            Date.prototype.formatMMDDYYYY = function () {
                return this.getHours() + ':' + this.getMinutes() + ':' + this.getSeconds();
            };

            // Split timestamp and data into separate arrays
            var labels = [],
                data = [];

            $.each(result.data, function (key, value) {
                labels.push(new Date(key * 1000).formatMMDDYYYY());
                data.push(parseInt(value));
            });

            // Create the chart.js data structure using 'labels' and 'data'
            var tempData = {
                labels: labels,
                datasets: [{
                    label: title,
                    data: data,
                    lineTension: 0,
                    backgroundColor: 'transparent',
                    borderColor: '#009688'
                }]
            };

            // Get the context of the canvas element we want to select
            var ctx = $canvas.get(0).getContext("2d");

            // Instantiate a new chart
            var myLineChart = new Chart(ctx, {
                type: 'line',
                data: tempData
            });

            $modal.modal('show').on('hidden.bs.modal', function (e) {
                myLineChart.destroy();
                $modal.find('canvas, iframe').remove();
                $modal.find('.modal-body').append('<canvas id="item-chart"></canvas>');
            })
        }).fail(function () {
            showErrorMessage('Не удалось получить данные');
        });
    });

    // RGB Widget
    $('.panel-item-rgb')
        .popover({
            html: true,
            placement: 'bottom',
            trigger: 'click',
            // container: 'body',
            content: function () {
                var source = $("#rgb-item-widget-popover-content").html();
                var template = Handlebars.compile(source);

                return template({
                    item_id: $(this).data('item-id')
                });
            }
        })
        // On popover init
        .on('inserted.bs.popover', function () {
            var item_id = $(this).data('item-id');

            // Init colorpicker
            var $colorPicker = $('body').find('.rgb-widget-colorpicker[data-item-id="' + item_id + '"]').spectrum({
                flat: true,
                showInput: false,
                showButtons: false,
                preferredFormat: 'rgb'
            });

            var savedItemValue = getSavedItemValue(item_id);

            // Set colorpicker value
            if (savedItemValue != null) {
                $colorPicker.spectrum('set', 'rgb(' + savedItemValue['red'] + ',' + savedItemValue['green'] + ',' + savedItemValue['blue'] + ')');
            }

            // Open mode tabs and set variables
            if (savedItemValue.mode == 'static') {
                $('.rgb-widget-mode-static').tab('show');
            } else if (savedItemValue.mode == 'wave') {
                $('#rgb-widget-wave-color-time').val(savedItemValue.color_time);
                $('#rgb-widget-fade-color-time').val(savedItemValue.color_time);

                $('.rgb-widget-mode-wave').tab('show');
            } else if (savedItemValue.mode == 'fade') {
                $('#rgb-widget-wave-color-time').val(savedItemValue.color_time);
                $('#rgb-widget-fade-color-time').val(savedItemValue.color_time);

                $('.rgb-widget-mode-fade').tab('show');
            }

            $('#rgb-widget-static-fade-time').val(savedItemValue.fade_time);
            $('#rgb-widget-wave-fade-time').val(savedItemValue.fade_time);
            $('#rgb-widget-fade-fade-time').val(savedItemValue.fade_time);

            // On color select
            $colorPicker.on("dragstop.spectrum", function (e, color) {
                var red = Math.round(color._r);
                var green = Math.round(color._g);
                var blue = Math.round(color._b);

                var $this = $(this);
                var itemId = $this.data('item-id');

                var $panelItem = $('.panel-item-rgb[data-item-id="' + itemId + '"]');

                // Update text color
                $panelItem.css('background-color', 'rgb(' + red + ',' + green + ',' + blue + ')');
                $panelItem.css('color', textColorDepOnBackground(red, green, blue));

                var modeId = $this.parents('.tab-pane').attr('id');

                if (modeId == 'rgb-widget-static') {
                    send({
                        "type": "rgb",
                        "item_id": item_id,
                        "fade_time": parseInt($('#rgb-widget-static-fade-time').val()),
                        "mode": "static",
                        "red": red,
                        "green": green,
                        "blue": blue
                    });
                } else if (modeId == 'rgb-widget-fade') {
                    send({
                        "type": "rgb",
                        "item_id": item_id,
                        "fade_time": parseInt($('#rgb-widget-fade-fade-time').val()),
                        "color_time": parseInt($('#rgb-widget-fade-color-time').val()),
                        "mode": "fade",
                        "red": red,
                        "green": green,
                        "blue": blue
                    });
                }
            });

            $('.btn-save-times').click(function (e) {
                e.preventDefault();

                var mode = $(this).data('mode');

                if (mode == 'wave') {
                    send({
                        "type": "rgb",
                        "item_id": item_id,
                        "mode": "wave",
                        "fade_time": parseInt($('#rgb-widget-wave-fade-time').val()),
                        "color_time": parseInt($('#rgb-widget-wave-color-time').val())
                    });
                } else if (mode == 'fade') {
                    var color = $('.rgb-widget-colorpicker-fade').spectrum('get');
                    var red = Math.round(color._r);
                    var green = Math.round(color._g);
                    var blue = Math.round(color._b);

                    send({
                        "type": "rgb",
                        "item_id": item_id,
                        "mode": "fade",
                        "fade_time": parseInt($('#rgb-widget-fade-fade-time').val()),
                        "color_time": parseInt($('#rgb-widget-fade-color-time').val()),
                        "red": red,
                        "green": green,
                        "blue": blue
                    });
                }
            });

            $('.rgb-widget-popover-content[data-item-id="' + item_id + '"]').on('click', '.rgb-widget-mode', function (e) {
                var $this = $(this);

                if ($this.attr('aria-expanded') == 'true') {
                    return;
                }

                if ($this.hasClass('rgb-widget-mode-static')) {
                    var color = $('.rgb-widget-colorpicker-static').spectrum('get');
                    var red = Math.round(color._r);
                    var green = Math.round(color._g);
                    var blue = Math.round(color._b);

                    send({
                        "type": "rgb",
                        "item_id": item_id,
                        "fade_time": parseInt($('#rgb-widget-static-fade-time').val()),
                        "mode": "static",
                        "red": red,
                        "green": green,
                        "blue": blue
                    });
                } else if ($this.hasClass('rgb-widget-mode-wave')) {
                    send({
                        "type": "rgb",
                        "item_id": item_id,
                        "mode": "wave",
                        "fade_time": parseInt($('#rgb-widget-wave-fade-time').val()),
                        "color_time": parseInt($('#rgb-widget-wave-color-time').val())
                    });
                } else if ($this.hasClass('rgb-widget-mode-fade')) {
                    var color = $('.rgb-widget-colorpicker-fade').spectrum('get');
                    var red = Math.round(color._r);
                    var green = Math.round(color._g);
                    var blue = Math.round(color._b);

                    send({
                        "type": "rgb",
                        "item_id": item_id,
                        "mode": "fade",
                        "fade_time": parseInt($('#rgb-widget-fade-fade-time').val()),
                        "color_time": parseInt($('#rgb-widget-fade-color-time').val()),
                        "red": red,
                        "green": green,
                        "blue": blue
                    });
                }
            });
        });
});
<|endoftext|>


<commit_before>exports.BattleMovedex = {
        "vinewhip": {
                num: 22,
                accuracy: 100,
                basePower: 35,
                category: "Physical",
                desc: "Deals damage to one adjacent target. Makes contact.",
                shortDesc: "No additional effect.",
                id: "vinewhip",
                name: "Vine Whip",
                pp: 25,
                priority: 0,
                isContact: true,
                secondary: false,
                target: "normal",
                type: "Grass"
        },
      	"lunardance": {
		num: 461,
		accuracy: true,
		basePower: 0,
		category: "Status",
		desc: "The user faints and the Pokemon brought out to replace it has its HP and PP fully restored along with having any major status condition cured. Fails if the user is the last unfainted Pokemon in its party.",
		shortDesc: "User faints. Replacement is fully healed, with PP.",
		id: "lunardance",
		isViable: true,
		name: "Lunar Dance",
		pp: 20,
		priority: 0,
		isSnatchable: true,
		boosts: {
			spa: 1,
			spe: 1
		},
		target: "self",
		type: "Psychic"
	},
	"moonblast": {
		num: 1000,
		accuracy: 90,
		basePower: 130,
		category: "Special",
		desc: "Surronds the user with moonlight energy and releases all of that energy in one blast using up all the energy making the attacks Special Attack go down.",
		shortDesc: "Deals damage and lowers the attacker's SpA.",
		id: "moonblast",
		isViable: true,
		name: "Moonblast",
		pp: 5,
		priority: 0,
		self: {
			boosts: {
				spa: -2

			}
		},
		secondary: false,
		target: "normal",
		type: "Fairy"
	},
	"fairywind": {
		num: 1001,
	        accuracy: 100,
		basePower: 85,
		category: "Special",
		desc: "Deals damage to one adjacent target by summoning a mystical wind that has a chance to encloak the user with mystical energy that boosts speed.",
		shortDesc: "Deals damage and has 10% chance to raise speed.",
		id: "fairywind",
		isViable: true,
		name: "Fairy Wind",
		pp: 15,
		priority: 0,
		secondary: {
			chance: 10,
			self: {
				boosts: {
					spd: 1
				}
			}
		},
		target: "normal",
		type: "Fairy"
	},
		"mysticcrash": {
		num: 1002,
	        accuracy: 100,
		basePower: 120,
		category: "Physical",
		desc: "Deals damage to one adjacent target by crashing into the target with a cloak of mystical energy.",
		shortDesc: "Deals damage and has recoil Basically Double Edge for faries.",
		id: "mysticcrash",
		isViable: true,
		name: "Mystic Crash",
		pp: 15,
		priority: 0,
		isContact: true,
		recoil: [22,100],
		secondary: false,
		target: "normal",
		type: "Fairy"
	},
        "partingshot": {
                num: 1032,
                accuracy: 100,
                basePower: 75,
                category: "Status",
                desc: "Lowers the opponent's Special Attack and Attack by 1, then switches the user out of battle.",
                shortDesc: "User switches out after lowering the target's SpA and Atk.",
                id: "partingshot",
                name: "Parting Shot",
                pp: 25,
                priority: 0,
                boosts: {
                        atk: -1,
                        spa: -1
                },
                selfSwitch: true,
                secondary: false,
                target: "normal",
                type: "Fighting"
        },
        "paraboliccharge": {
                num: 1033,
                accuracy: 100,
                basePower: 80,
                category: "Special",
                desc: "Deals damage to all targets, including its ally. The user recovers half of the HP lost by the target, rounded up. If Big Root is held by the user, the HP recovered is 1.3x normal, rounded half down.",
                shortDesc: "User recovers 50% of the damage dealt.",
                id: "paraboliccharge",
                name: "Parabolic Charge",
                pp: 16,
                priority: 0,
                drain: [1,2],
                secondary: false,
                target: "normal",
                type: "Electric"
        },
        "boomburst": {
                num: 1034,
                accuracy: 100,
                basePower: 100,
                category: "Special",
                desc: "A sound attack that deals damage to all targets, including its ally.",
                shortDesc: "Deals damage to all targets.",
                id: "boomburst",
                name: "Boomburst",
                pp: 16,
                priority: 0,
                isSoundBased: true,
                secondary: false,
                target: "all",
                type: "Normal"
        },
        "moonblast": {
                num: 1035,
                accuracy: 100,
                basePower: 110,
                category: "Special",
                desc: "Deals damage to one adjacent target with a ???% chance to lower Special Attack",
                shortDesc: "30% chance to lower SpA.",
                id: "moonblast",
                name: "Moonblast",
                pp: 25,
                priority: 0,
                secondary: {
                        chance: 30,
                        boosts: {
                                spa: -1
                        }
                },
                target: "normal",
                type: "Fairy"
        },
        "nobleroar": {
                num: 1036,
                accuracy: 100,
                basePower: 85,
                category: "Status",
                desc: "Lowers the foe's Attack and Special Attack by one stage.",
                shortDesc: "Lowers SpA and Atk by 1.",
                id: "nobleroar",
                name: "Noble Roar",
                pp: 16,
                priority: 0,
                boosts: {
                        atk: -1,
                        spa: -1
                },
                secondary: false,
                target: "normal",
                type: "Fire"
        },
        "fairywind": {
                num: 1037,
                accuracy: 100,
                basePower: 85,
                category: "Special",
                desc: "Deals massive damage to all adjacent foes.",
                shortDesc: "Damages all adjacent foes.",
                id: "fairywind",
                name: "Fairy Wind",
                pp: 25,
                priority: 0,
                secondary: false,
                target: "allAdjacentFoes",
                type: "Fairy"
        },
        "topsyturvy": {
                num: 1038,
                accuracy: 100,
                basePower: 0,
                category: "Status",
                desc: "Reverses all stat modifications that the foe has.",
                shortDesc: "Reverses the foe's stat changes.",
                id: "topsyturvy",
                name: "Topsy-Turvy",
                pp: 16,
                priority: 0,
                onHit: function(target) {
                         for (var i in target.boosts) {
                               target.boosts[i] = target.boosts[i] * (-1);
                        }
                        this.add('-reverseboost', target, '[from] move: Topsy-Turvy');
                },
                secondary: false,
                target: "normal",
                type: "Fairy"
        },
        "drainingkiss": {
                num: 1039,
                accuracy: 100,
                basePower: 80,
                category: "Special",
                desc: "Deals damage to one adjacent target. The user recovers half of the HP lost by the target, rounded up. If Big Root is held by the user, the HP recovered is 1.3x normal, rounded half down.",
                shortDesc: "User recovers 50% of the damage dealt.",
                id: "drainingkiss",
                name: "Draining Kiss",
                pp: 16,
                priority: 0,
                drain: [1,2],
                secondary: false,
                target: "normal",
                type: "Fairy"
        },
        "geomancy": {
                num: 1040,
                accuracy: 100,
                basePower: 130,
                category: "Special",
                desc: "Deal massive damage to one adjacent target.",
                shortDesc: "No additional effect.",
                id: "geomancy",
                name: "Geomancy",
                pp: 16,
                priority: 0,
		self: {
			boosts: {
				spa: -1,
                                spd: -1
			}
		},
                secondary: false,
                target: "all",
                type: "Fairy"
        },
        "oblivionwing": {
                num: 1041,
                accuracy: 100,
                basePower: 130,
                category: "Special",
                desc: "Deal massive damage to one adjacent target.",
                shortDesc: "No additional effect.",
                id: "oblivionwing",
                name: "Oblivion Wing",
                pp: 16,
        	priority: 0,
		self: {
			boosts: {
				spa: -1,
                                spd: -1
			}
		},
                secondary: false,
                target: "all",
                type: "Dark"
	}
};
<commit_msg>Update moves.js<commit_after>exports.BattleMovedex = {
        "vinewhip": {
                num: 22,
                accuracy: 100,
                basePower: 35,
                category: "Physical",
                desc: "Deals damage to one adjacent target. Makes contact.",
                shortDesc: "No additional effect.",
                id: "vinewhip",
                name: "Vine Whip",
                pp: 25,
                priority: 0,
                isContact: true,
                secondary: false,
                target: "normal",
                type: "Grass"
        },
      	"lunardance": {
		num: 461,
		accuracy: true,
		basePower: 0,
		category: "Status",
		desc: "The user faints and the Pokemon brought out to replace it has its HP and PP fully restored along with having any major status condition cured. Fails if the user is the last unfainted Pokemon in its party.",
		shortDesc: "User faints. Replacement is fully healed, with PP.",
		id: "lunardance",
		isViable: true,
		name: "Lunar Dance",
		pp: 20,
		priority: 0,
		isSnatchable: true,
		boosts: {
			spa: 1,
			spe: 1
		},
		target: "self",
		type: "Psychic"
	},
	"moonblast": {
		num: 1000,
		accuracy: 90,
		basePower: 130,
		category: "Special",
		desc: "Surronds the user with moonlight energy and releases all of that energy in one blast using up all the energy making the attacks Special Attack go down.",
		shortDesc: "Deals damage and lowers the attacker's SpA.",
		id: "moonblast",
		isViable: true,
		name: "Moonblast",
		pp: 5,
		priority: 0,
		self: {
			boosts: {
				spa: -2

			}
		},
		secondary: false,
		target: "normal",
		type: "Fairy"
	},
	"fairywind": {
		num: 1001,
	        accuracy: 100,
		basePower: 85,
		category: "Special",
		desc: "Deals damage to one adjacent target by summoning a mystical wind that has a chance to encloak the user with mystical energy that boosts speed.",
		shortDesc: "Deals damage and has 10% chance to raise speed.",
		id: "fairywind",
		isViable: true,
		name: "Fairy Wind",
		pp: 15,
		priority: 0,
		secondary: {
			chance: 10,
			self: {
				boosts: {
					spd: 1
				}
			}
		},
		target: "normal",
		type: "Fairy"
	},
		"mysticcrash": {
		num: 1002,
	        accuracy: 100,
		basePower: 120,
		category: "Physical",
		desc: "Deals damage to one adjacent target by crashing into the target with a cloak of mystical energy.",
		shortDesc: "Deals damage and has recoil Basically Double Edge for faries.",
		id: "mysticcrash",
		isViable: true,
		name: "Mystic Crash",
		pp: 15,
		priority: 0,
		isContact: true,
		recoil: [22,100],
		secondary: false,
		target: "normal",
		type: "Fairy"
	},
        "partingshot": {
                num: 1032,
                accuracy: 100,
                basePower: 75,
                category: "Status",
                desc: "Lowers the opponent's Special Attack and Attack by 1, then switches the user out of battle.",
                shortDesc: "User switches out after lowering the target's SpA and Atk.",
                id: "partingshot",
                name: "Parting Shot",
                pp: 25,
                priority: 0,
                boosts: {
                        atk: -1,
                        spa: -1
                },
                selfSwitch: true,
                secondary: false,
                target: "normal",
                type: "Fighting"
        },
        "paraboliccharge": {
                num: 1033,
                accuracy: 100,
                basePower: 80,
                category: "Special",
                desc: "Deals damage to all targets, including its ally. The user recovers half of the HP lost by the target, rounded up. If Big Root is held by the user, the HP recovered is 1.3x normal, rounded half down.",
                shortDesc: "User recovers 50% of the damage dealt.",
                id: "paraboliccharge",
                name: "Parabolic Charge",
                pp: 16,
                priority: 0,
                drain: [1,2],
                secondary: false,
                target: "normal",
                type: "Electric"
        },
        "boomburst": {
                num: 1034,
                accuracy: 100,
                basePower: 100,
                category: "Special",
                desc: "A sound attack that deals damage to all targets, including its ally.",
                shortDesc: "Deals damage to all targets.",
                id: "boomburst",
                name: "Boomburst",
                pp: 16,
                priority: 0,
                isSoundBased: true,
                secondary: false,
                target: "all",
                type: "Normal"
        },
        "moonblast": {
                num: 1035,
                accuracy: 100,
                basePower: 110,
                category: "Special",
                desc: "Deals damage to one adjacent target with a ???% chance to lower Special Attack",
                shortDesc: "30% chance to lower SpA.",
                id: "moonblast",
                name: "Moonblast",
                pp: 25,
                priority: 0,
                secondary: {
                        chance: 30,
                        boosts: {
                                spa: -1
                        }
                },
                target: "normal",
                type: "Fairy"
        },
        "nobleroar": {
                num: 1036,
                accuracy: 100,
                basePower: 85,
                category: "Status",
                desc: "Lowers the foe's Attack and Special Attack by one stage.",
                shortDesc: "Lowers SpA and Atk by 1.",
                id: "nobleroar",
                name: "Noble Roar",
                pp: 16,
                priority: 0,
                boosts: {
                        atk: -1,
                        spa: -1
                },
                secondary: false,
                target: "normal",
                type: "Fire"
        },
        "fairywind": {
                num: 1037,
                accuracy: 100,
                basePower: 85,
                category: "Special",
                desc: "Deals massive damage to all adjacent foes.",
                shortDesc: "Damages all adjacent foes.",
                id: "fairywind",
                name: "Fairy Wind",
                pp: 25,
                priority: 0,
                secondary: false,
                target: "allAdjacentFoes",
                type: "Fairy"
        },
        "topsyturvy": {
                num: 1038,
                accuracy: 100,
                basePower: 0,
                category: "Status",
                desc: "Reverses all stat modifications that the foe has.",
                shortDesc: "Reverses the foe's stat changes.",
                id: "topsyturvy",
                name: "Topsy-Turvy",
                pp: 16,
                priority: 0,
                onHit: function(target) {
                         for (var i in target.boosts) {
                               target.boosts[i] = target.boosts[i] * (-1);
                        }
                        this.add('-reverseboost', target, '[from] move: Topsy-Turvy');
                },
                secondary: false,
                target: "normal",
                type: "Fairy"
        },
        "drainingkiss": {
                num: 1039,
                accuracy: 100,
                basePower: 80,
                category: "Special",
                desc: "Deals damage to one adjacent target. The user recovers half of the HP lost by the target, rounded up. If Big Root is held by the user, the HP recovered is 1.3x normal, rounded half down.",
                shortDesc: "User recovers 50% of the damage dealt.",
                id: "drainingkiss",
                name: "Draining Kiss",
                pp: 16,
                priority: 0,
                drain: [1,2],
                secondary: false,
                target: "normal",
                type: "Fairy"
        },
        "geomancy": {
                num: 1040,
                accuracy: 100,
                basePower: 130,
                category: "Special",
                desc: "Deal massive damage to one adjacent target.",
                shortDesc: "No additional effect.",
                id: "geomancy",
                name: "Geomancy",
                pp: 16,
                priority: 0,
		self: {
			boosts: {
				spa: -1,
                                spd: -1
			}
		},
                secondary: false,
                target: "normal",
                type: "Fairy"
        },
        "oblivionwing": {
                num: 1041,
                accuracy: 100,
                basePower: 130,
                category: "Special",
                desc: "Deal massive damage to one adjacent target.",
                shortDesc: "No additional effect.",
                id: "oblivionwing",
                name: "Oblivion Wing",
                pp: 16,
        	priority: 0,
		self: {
			boosts: {
				spa: -1,
                                spd: -1
			}
		},
                secondary: false,
                target: "normal",
                type: "Dark"
	}
};
<|endoftext|>