/**
* @author       Richard Davey <rich@photonstorm.com>
* @copyright    2013 Photon Storm Ltd.
* @license      {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/

/**
* Phaser - Mouse constructor.
*
* @class Phaser.Mouse
* @classdesc The Mouse class
* @constructor
* @param {Phaser.Game} game - A reference to the currently running game.
*/
Phaser.Mouse = function (game) {

	/**
	* @property {Phaser.Game} game - Local reference to game.
	*/
	this.game = game;

	/**
	* @property {Object} callbackContext - Description.
	*/
	this.callbackContext = this.game;

	/**
	* @property {function} mouseDownCallback - Description.
	* @default
	*/
	this.mouseDownCallback = null;

	/**
	* @property {function} mouseMoveCallback - Description.
	* @default
	*/
	this.mouseMoveCallback = null;

	/**
	* @property {function} mouseUpCallback - Description.
	* @default
	*/
	this.mouseUpCallback = null;

	/**
	* @property {number} button- The type of click, either: Phaser.Mouse.NO_BUTTON, Phaser.Mouse.LEFT_BUTTON, Phaser.Mouse.MIDDLE_BUTTON or Phaser.Mouse.RIGHT_BUTTON.
	* @default
	*/
	this.button = -1;

    /**
    * You can disable all Input by setting disabled = true. While set all new input related events will be ignored.
    * @property {boolean} disabled
    * @default
    */
    this.disabled = false;

    /**
    * If the mouse has been Pointer Locked successfully this will be set to true.
    * @property {boolean} locked
    * @default
    */
    this.locked = false;

    /**
    * This event is dispatched when the browser enters or leaves pointer lock state.
    * @property {Phaser.Signal} pointerLock
    * @default
    */
    this.pointerLock = new Phaser.Signal;

    /**
    * The browser mouse event.
    * @property {MouseEvent} event
    */
    this.event;

    /**
    * @property {function} _onMouseDown
    * @private
    */
    this._onMouseDown;

    /**
    * @property {function} _onMouseMove
    * @private
    */
    this._onMouseMove;

    /**
    * @property {function} _onMouseUp
    * @private
    */
    this._onMouseUp;

};

/**
* @constant
* @type {number}
*/
Phaser.Mouse.NO_BUTTON = -1;
/**
* @constant
* @type {number}
*/
Phaser.Mouse.LEFT_BUTTON = 0;

/**
* @constant
* @type {number}
*/
Phaser.Mouse.MIDDLE_BUTTON = 1;

/**
* @constant
* @type {number}
*/
Phaser.Mouse.RIGHT_BUTTON = 2;

Phaser.Mouse.prototype = {

	/**
    * Starts the event listeners running.
    * @method Phaser.Mouse#start
    */
    start: function () {

        var _this = this;

        if (this.game.device.android && this.game.device.chrome == false)
        {
            //  Android stock browser fires mouse events even if you preventDefault on the touchStart, so ...
            return;
        }

        this._onMouseDown = function (event) {
            return _this.onMouseDown(event);
        };

        this._onMouseMove = function (event) {
            return _this.onMouseMove(event);
        };

        this._onMouseUp = function (event) {
            return _this.onMouseUp(event);
        };

        // this.game.renderer.view.addEventListener('mousedown', this._onMouseDown, true);
        // this.game.renderer.view.addEventListener('mousemove', this._onMouseMove, true);
        // this.game.renderer.view.addEventListener('mouseup', this._onMouseUp, true);

        document.addEventListener('mousedown', this._onMouseDown, true);
        document.addEventListener('mousemove', this._onMouseMove, true);
        document.addEventListener('mouseup', this._onMouseUp, true);

    },

	/**
	* The internal method that handles the mouse down event from the browser.
	* @method Phaser.Mouse#onMouseDown
    * @param {MouseEvent} event
    */
    onMouseDown: function (event) {

        this.event = event;

        event.preventDefault();

        this.button = event.which;

        if (this.mouseDownCallback)
        {
            this.mouseDownCallback.call(this.callbackContext, event);
        }

        if (this.game.input.disabled || this.disabled)
        {
            return;
        }

        event['identifier'] = 0;

        this.game.input.mousePointer.start(event);

    },

	/**
    * The internal method that handles the mouse move event from the browser.
	* @method Phaser.Mouse#onMouseMove
    * @param {MouseEvent} event
    */
    onMouseMove: function (event) {

        this.event = event;

        event.preventDefault();

        if (this.mouseMoveCallback)
        {
            this.mouseMoveCallback.call(this.callbackContext, event);
        }

        if (this.game.input.disabled || this.disabled)
        {
            return;
        }

        event['identifier'] = 0;

        this.game.input.mousePointer.move(event);

    },

	/**
    * The internal method that handles the mouse up event from the browser.
	* @method Phaser.Mouse#onMouseUp
    * @param {MouseEvent} event
    */
    onMouseUp: function (event) {

        this.event = event;

        event.preventDefault();

    	this.button = Phaser.Mouse.NO_BUTTON;

        if (this.mouseUpCallback)
        {
            this.mouseUpCallback.call(this.callbackContext, event);
        }

        if (this.game.input.disabled || this.disabled)
        {
            return;
        }

        event['identifier'] = 0;

        this.game.input.mousePointer.stop(event);

    },

    /**
    * If the browser supports it you can request that the pointer be locked to the browser window.
    * This is classically known as 'FPS controls', where the pointer can't leave the browser until the user presses an exit key.
    * If the browser successfully enters a locked state the event Phaser.Mouse.pointerLock will be dispatched and the first parameter will be 'true'.
	* @method Phaser.Mouse#requestPointerLock
    */
    requestPointerLock: function () {

        if (this.game.device.pointerLock)
        {
            var element = this.game.stage.canvas;

            element.requestPointerLock = element.requestPointerLock || element.mozRequestPointerLock || element.webkitRequestPointerLock;

            element.requestPointerLock();

            var _this = this;

            this._pointerLockChange = function (event) {
                return _this.pointerLockChange(event);
            };

            document.addEventListener('pointerlockchange', this._pointerLockChange, true);
            document.addEventListener('mozpointerlockchange', this._pointerLockChange, true);
            document.addEventListener('webkitpointerlockchange', this._pointerLockChange, true);
        }

    },

	/**
	* Internal pointerLockChange handler.
	* @method Phaser.Mouse#pointerLockChange
    * @param {MouseEvent} event
    */
    pointerLockChange: function (event) {

        var element = this.game.stage.canvas;

        if (document.pointerLockElement === element || document.mozPointerLockElement === element || document.webkitPointerLockElement === element)
        {
            //  Pointer was successfully locked
            this.locked = true;
            this.pointerLock.dispatch(true);
        }
        else
        {
            //  Pointer was unlocked
            this.locked = false;
            this.pointerLock.dispatch(false);
        }

    },

	/**
	* Internal release pointer lock handler.
	* @method Phaser.Mouse#releasePointerLock
    */
    releasePointerLock: function () {

        document.exitPointerLock = document.exitPointerLock || document.mozExitPointerLock || document.webkitExitPointerLock;

        document.exitPointerLock();

        document.removeEventListener('pointerlockchange', this._pointerLockChange, true);
        document.removeEventListener('mozpointerlockchange', this._pointerLockChange, true);
        document.removeEventListener('webkitpointerlockchange', this._pointerLockChange, true);

    },

	/**
    * Stop the event listeners.
    * @method Phaser.Mouse#stop
    */
    stop: function () {

        // this.game.stage.canvas.removeEventListener('mousedown', this._onMouseDown, true);
        // this.game.stage.canvas.removeEventListener('mousemove', this._onMouseMove, true);
        // this.game.stage.canvas.removeEventListener('mouseup', this._onMouseUp, true);

        document.removeEventListener('mousedown', this._onMouseDown, true);
        document.removeEventListener('mousemove', this._onMouseMove, true);
        document.removeEventListener('mouseup', this._onMouseUp, true);

    }

};


* Internal function called by preUpdate.

keep keep keep keep add keep keep keep keep keep

/**
* @author       Richard Davey <rich@photonstorm.com>
* @copyright    2013 Photon Storm Ltd.
* @license      {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/

/**
* "This world is but a canvas to our imagination." - Henry David Thoreau
*
* A game has only one world. The world is an abstract place in which all game objects live. It is not bound
* by stage limits and can be any size. You look into the world via cameras. All game objects live within
* the world at world-based coordinates. By default a world is created the same size as your Stage.
*
* @class Phaser.World
* @constructor
* @param {Phaser.Game} game - Reference to the current game instance.
*/
Phaser.World = function (game) {

    Phaser.Group.call(this, game, null, '__world', false);

    /**
    * @property {Phaser.Point} scale - Replaces the PIXI.Point with a slightly more flexible one.
    */
    this.scale = new Phaser.Point(1, 1);

    /**
    * The World has no fixed size, but it does have a bounds outside of which objects are no longer considered as being "in world" and you should use this to clean-up the display list and purge dead objects.
    * By default we set the Bounds to be from 0,0 to Game.width,Game.height. I.e. it will match the size given to the game constructor with 0,0 representing the top-left of the display.
    * However 0,0 is actually the center of the world, and if you rotate or scale the world all of that will happen from 0,0.
    * So if you want to make a game in which the world itself will rotate you should adjust the bounds so that 0,0 is the center point, i.e. set them to -1000,-1000,2000,2000 for a 2000x2000 sized world centered around 0,0.
	* @property {Phaser.Rectangle} bounds - Bound of this world that objects can not escape from.
	*/
	this.bounds = new Phaser.Rectangle(0, 0, game.width, game.height);

    /**
	* @property {Phaser.Camera} camera - Camera instance.
	*/
	this.camera = null;

    /**
	* @property {number} currentRenderOrderID - Reset each frame, keeps a count of the total number of objects updated.
	*/
	this.currentRenderOrderID = 0;

};

Phaser.World.prototype = Object.create(Phaser.Group.prototype);
Phaser.World.prototype.constructor = Phaser.World;

/**
* Initialises the game world.
*
* @method Phaser.World#boot
* @protected
*/
Phaser.World.prototype.boot = function () {

    this.camera = new Phaser.Camera(this.game, 0, 0, 0, this.game.width, this.game.height);

    this.camera.displayObject = this._container;

    this.game.camera = this.camera;

}

/**
* This is called automatically every frame, and is where main logic happens.
*
* @method Phaser.World#update
*/
Phaser.World.prototype.update = function () {

	this.currentRenderOrderID = 0;

	if (this.game.stage._stage.first._iNext)
	{
		var currentNode = this.game.stage._stage.first._iNext;

		do
		{
			if (currentNode['preUpdate'])
			{
				currentNode.preUpdate();
			}

			if (currentNode['update'])
			{
				currentNode.update();
			}

			currentNode = currentNode._iNext;
		}
		while (currentNode != this.game.stage._stage.last._iNext)
	}

}

/**
* This is called automatically every frame, and is where main logic happens.
* @method Phaser.World#postUpdate
*/
Phaser.World.prototype.postUpdate = function () {

    this.camera.update();

    if (this.game.stage._stage.first._iNext)
    {
        var currentNode = this.game.stage._stage.first._iNext;

        do
        {
            if (currentNode['postUpdate'])
            {
                currentNode.postUpdate();
            }

            currentNode = currentNode._iNext;
        }
        while (currentNode != this.game.stage._stage.last._iNext)
    }

}

/**
* Updates the size of this world. Note that this doesn't modify the world x/y coordinates, just the width and height.
* If you need to adjust the bounds of the world
* @method Phaser.World#setBounds
* @param {number} x - Top left most corner of the world.
* @param {number} y - Top left most corner of the world.
* @param {number} width - New width of the world.
* @param {number} height - New height of the world.
*/
Phaser.World.prototype.setBounds = function (x, y, width, height) {

    this.bounds.setTo(x, y, width, height);

    if (this.camera.bounds)
    {
        this.camera.bounds.setTo(x, y, width, height);
    }

}

/**
* Destroyer of worlds.
* @method Phaser.World#destroy
*/
Phaser.World.prototype.destroy = function () {

    this.camera.x = 0;
    this.camera.y = 0;

    this.game.input.reset(true);

    this.removeAll();

}

/**
* @name Phaser.World#width
* @property {number} width - Gets or sets the current width of the game world.
*/
Object.defineProperty(Phaser.World.prototype, "width", {

    get: function () {
        return this.bounds.width;
    },

    set: function (value) {
        this.bounds.width = value;
    }

});

/**
* @name Phaser.World#height
* @property {number} height - Gets or sets the current height of the game world.
*/
Object.defineProperty(Phaser.World.prototype, "height", {

    get: function () {
        return this.bounds.height;
    },

    set: function (value) {
        this.bounds.height = value;
    }

});

/**
* @name Phaser.World#centerX
* @property {number} centerX - Gets the X position corresponding to the center point of the world.
* @readonly
*/
Object.defineProperty(Phaser.World.prototype, "centerX", {

    get: function () {
        return this.bounds.halfWidth;
    }

});

/**
* @name Phaser.World#centerY
* @property {number} centerY - Gets the Y position corresponding to the center point of the world.
* @readonly
*/
Object.defineProperty(Phaser.World.prototype, "centerY", {

    get: function () {
        return this.bounds.halfHeight;
    }

});

/**
* @name Phaser.World#randomX
* @property {number} randomX - Gets a random integer which is lesser than or equal to the current width of the game world.
* @readonly
*/
Object.defineProperty(Phaser.World.prototype, "randomX", {

    get: function () {

        if (this.bounds.x < 0)
        {
            return this.game.rnd.integerInRange(this.bounds.x, (this.bounds.width - Math.abs(this.bounds.x)));
        }
        else
        {
            return this.game.rnd.integerInRange(this.bounds.x, this.bounds.width);
        }

    }

});

/**
* @name Phaser.World#randomY
* @property {number} randomY - Gets a random integer which is lesser than or equal to the current height of the game world.
* @readonly
*/
Object.defineProperty(Phaser.World.prototype, "randomY", {

    get: function () {

        if (this.bounds.y < 0)
        {
            return this.game.rnd.integerInRange(this.bounds.y, (this.bounds.height - Math.abs(this.bounds.y)));
        }
        else
        {
            return this.game.rnd.integerInRange(this.bounds.y, this.bounds.height);
        }

    }

});

/**
* @name Phaser.World#visible
* @property {boolean} visible - Gets or sets the visible state of the World.
*/
Object.defineProperty(Phaser.World.prototype, "visible", {

    get: function () {
        return this._container.visible;
    },

    set: function (value) {
        this._container.visible = value;
    }

});


<!DOCTYPE HTML>
<html>
<head>
	<title>phaser.js - a new beginning</title>
	<?php
		require('js.php');
	?>
</head>
<body>

<script type="text/javascript">

(function () {

	var game = new Phaser.Game(800, 600, Phaser.CANVAS, '', { preload: preload, create: create, update: update }, false, false);

	var bunny;

	function preload() {
		game.load.spritesheet('metalslug', 'assets/sprites/metalslug_monster39x40.png', 39, 40);
	}

	function create() {

		//	This is created by the very act of loading a sprite sheet in
		frameData = game.cache.getFrameData('metalslug');

		//	Both of these work - they look fugly I know, but they'll be hidden behind a Phaser Sprite anyway
		bunny = PIXI.Sprite.fromFrame(frameData.getFrame(0).uuid);

		bunny.anchor.x = 0.5;
		bunny.anchor.y = 0.5;

		bunny.scale.x = 6;
		bunny.scale.y = 6;

		bunny.position.x = game.world.centerX;
		bunny.position.y = game.world.centerY;

		game.world.add(bunny);

		n = game.time.now + 20;

	}

	var frameData;
	var f = 0;
	var n = 0;

	function update() {

		if (game.time.now > n)
		{
			f = game.math.wrapValue(f, 1, frameData.total);

			bunny.setTexture(PIXI.TextureCache[frameData.getFrame(f).uuid]);

			n = game.time.now + 40;
		}

	}

})();

</script>

</body>
</html>


<!DOCTYPE html>

	<meta charset="utf-8">
	<title>Phaser Source: core/Plugin.js</title>

	<link type="text/css" rel="stylesheet" href="styles/sunlight.default.css">

	<link type="text/css" rel="stylesheet" href="styles/site.cerulean.css">

			Phaser

					Namespaces

							Phaser

					Classes

							Animation

							AnimationManager

							AnimationParser

							BitmapText

							Button

							Cache

							Camera

							Canvas

							Circle

							Color

							Device

							Easing

							Back

							Bounce

							Circular

							Cubic

							Elastic

							Exponential

							Linear

							Quadratic

							Quartic

							Quintic

							Sinusoidal

							Events

							Frame

							FrameData

							Game

							GameObjectFactory

							Graphics

							Group

							Input

							InputHandler

							Key

							Keyboard

							LinkedList

							Loader

							LoaderParser

							Math

							Mouse

							MSPointer

							Net

							Particles

							Emitter

							Physics

							Arcade

							Body

							Plugin

							PluginManager

							Point

							Pointer

							Polygon

							QuadTree

							RandomDataGenerator

							Rectangle

							RenderTexture

							RequestAnimationFrame

							Signal

							Sound

							SoundManager

							Sprite

							Stage

							StageScaleMode

							State

							StateManager

							Text

							TileSprite

							Time

							Touch

							Tween

							TweenManager

							Utils

							Debug

							World

							SignalBinding

					Global

							bottom

							HEXtoRGB

							right

Source: core/Plugin.js

/**
* @author       Richard Davey <rich@photonstorm.com>
* @copyright    2013 Photon Storm Ltd.
* @license      {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/

/**
* This is a base Plugin template to use for any Phaser plugin development.
*
* @class Phaser.Plugin
* @classdesc Phaser - Plugin
* @constructor
* @param {Phaser.Game} game - A reference to the currently running game.
* @param {Any} parent - The object that owns this plugin, usually Phaser.PluginManager.
*/
Phaser.Plugin = function (game, parent) {

    if (typeof parent === 'undefined') { parent = null; }

	/**
	* @property {Phaser.Game} game - A reference to the currently running game.
	*/
    this.game = game;

    /**
	* @property {Any} parent - The parent of this plugin. If added to the PluginManager the parent will be set to that, otherwise it will be null.
	*/
    this.parent = parent;

    /**
	* @property {boolean} active - A Plugin with active=true has its preUpdate and update methods called by the parent, otherwise they are skipped.
	* @default
	*/
    this.active = false;

    /**
	* @property {boolean} visible - A Plugin with visible=true has its render and postRender methods called by the parent, otherwise they are skipped.
	* @default
	*/
    this.visible = false;

    /**
	* @property {boolean} hasPreUpdate - A flag to indicate if this plugin has a preUpdate method.
	* @default
	*/
    this.hasPreUpdate = false;

    /**
	* @property {boolean} hasUpdate - A flag to indicate if this plugin has an update method.
	* @default
	*/
    this.hasUpdate = false;

    /**
    * @property {boolean} hasPostUpdate - A flag to indicate if this plugin has a postUpdate method.
    * @default
    */
    this.hasPostUpdate = false;

    /**
	* @property {boolean} hasRender - A flag to indicate if this plugin has a render method.
	* @default
	*/
    this.hasRender = false;

    /**
	* @property {boolean} hasPostRender - A flag to indicate if this plugin has a postRender method.
	* @default
	*/
    this.hasPostRender = false;

};

Phaser.Plugin.prototype = {

    /**
    * Pre-update is called at the very start of the update cycle, before any other subsystems have been updated (including Physics).
    * It is only called if active is set to true.
    * @method Phaser.Plugin#preUpdate
    */
    preUpdate: function () {
    },

    /**
    * Update is called after all the core subsystems (Input, Tweens, Sound, etc) and the State have updated, but before the render.
    * It is only called if active is set to true.
    * @method Phaser.Plugin#update
    */
    update: function () {
    },

    /**
    * Render is called right after the Game Renderer completes, but before the State.render.
    * It is only called if visible is set to true.
    * @method Phaser.Plugin#render
    */
    render: function () {
    },

    /**
    * Post-render is called after the Game Renderer and State.render have run.
    * It is only called if visible is set to true.
    * @method Phaser.Plugin#postRender
    */
    postRender: function () {
    },

    /**
    * Clear down this Plugin and null out references
    * @method Phaser.Plugin#destroy
    */
    destroy: function () {

        this.game = null;
        this.parent = null;
        this.active = false;
        this.visible = false;

    }

};

		Phaser Copyright © 2012-2013 Photon Storm Ltd.

		Documentation generated by JSDoc 3.3.0-dev
		on Thu Nov 07 2013 06:07:33 GMT-0000 (GMT) using the DocStrap template.


jest.mock('fs')
jest.mock('vue-cli-plugin-foo', () => () => {}, { virtual: true })

const fs = require('fs')
const path = require('path')
const Service = require('../lib/Service')

const mockPkg = json => {
  fs.writeFileSync('/package.json', JSON.stringify(json, null, 2))
}

const createMockService = (plugins = [], init = true, mode) => {
  const service = new Service('/', {
    plugins,
    useBuiltIn: false
  })
  if (init) {
    service.init(mode)
  }
  return service
}

beforeEach(() => {
  mockPkg({})
  delete process.env.NODE_ENV
  delete process.env.BABEL_ENV
  delete process.env.FOO
  delete process.env.BAR
  delete process.env.BAZ
})

afterEach(() => {
  if (fs.existsSync('/vue.config.js')) {
    fs.unlinkSync('/vue.config.js')
  }
})

test('env loading', () => {
  process.env.FOO = 0
  fs.writeFileSync('/.env.local', `FOO=1\nBAR=2`)
  fs.writeFileSync('/.env', `BAR=3\nBAZ=4`)
  createMockService()

  expect(process.env.FOO).toBe('0')
  expect(process.env.BAR).toBe('2')
  expect(process.env.BAZ).toBe('4')

  fs.unlinkSync('/.env.local')
  fs.unlinkSync('/.env')
})

test('env loading for custom mode', () => {
  process.env.VUE_CLI_TEST_TESTING_ENV = true
  fs.writeFileSync('/.env', 'FOO=1')
  fs.writeFileSync('/.env.staging', 'FOO=2\nNODE_ENV=production')
  createMockService([], true, 'staging')

  expect(process.env.FOO).toBe('2')
  expect(process.env.NODE_ENV).toBe('production')

  process.env.VUE_CLI_TEST_TESTING_ENV = false
  fs.unlinkSync('/.env')
  fs.unlinkSync('/.env.staging')
})

test('loading plugins from package.json', () => {
  mockPkg({
    devDependencies: {
      bar: '^1.0.0',
      '@vue/cli-plugin-babel': '^4.4.0',
      'vue-cli-plugin-foo': '^1.0.0'
    }
  })
  const service = new Service('/') // this one needs to read from package.json
  expect(service.plugins.some(({ id }) => id === '@vue/cli-plugin-babel')).toBe(true)
  expect(service.plugins.some(({ id }) => id === 'vue-cli-plugin-foo')).toBe(true)
  expect(service.plugins.some(({ id }) => id === 'bar')).toBe(false)
})

test('load project options from package.json', () => {
  mockPkg({
    vue: {
      lintOnSave: 'default'
    }
  })
  const service = createMockService()
  expect(service.projectOptions.lintOnSave).toBe('default')
})

test('handle option publicPath and outputDir correctly', () => {
  mockPkg({
    vue: {
      publicPath: 'https://foo.com/bar',
      outputDir: '/public/'
    }
  })
  const service = createMockService()
  expect(service.projectOptions.publicPath).toBe('https://foo.com/bar/')
  expect(service.projectOptions.outputDir).toBe('/public')
})

test('normalize publicPath when relative', () => {
  mockPkg({
    vue: {
      publicPath: './foo/bar'
    }
  })
  const service = createMockService()
  expect(service.projectOptions.publicPath).toBe('foo/bar/')
})

test('allow custom protocol in publicPath', () => {
  mockPkg({
    vue: {
      publicPath: 'customprotocol://foo/bar'
    }
  })
  const service = createMockService()
  expect(service.projectOptions.publicPath).toBe('customprotocol://foo/bar/')
})

test('keep publicPath when empty', () => {
  mockPkg({
    vue: {
      publicPath: ''
    }
  })
  const service = createMockService()
  expect(service.projectOptions.publicPath).toBe('')
})

test('load project options from vue.config.js', () => {
  fs.writeFileSync(path.resolve('/', 'vue.config.js'), '')  // only to ensure fs.existsSync returns true
  jest.mock(path.resolve('/', 'vue.config.js'), () => ({ lintOnSave: false }), { virtual: true })
  mockPkg({
    vue: {
      lintOnSave: 'default'
    }
  })
  const service = createMockService()
  // vue.config.js has higher priority
  expect(service.projectOptions.lintOnSave).toBe(false)
})

test('load project options from vue.config.js as a function', () => {
  fs.writeFileSync(path.resolve('/', 'vue.config.js'), '')
  jest.mock(path.resolve('/', 'vue.config.js'), () => function () { return { lintOnSave: false } }, { virtual: true })
  mockPkg({
    vue: {
      lintOnSave: 'default'
    }
  })
  const service = createMockService()
  // vue.config.js has higher priority
  expect(service.projectOptions.lintOnSave).toBe(false)
})

test('api: assertVersion', () => {
  const plugin = {
    id: 'test-assertVersion',
    apply: api => {
      expect(() => api.assertVersion(4)).not.toThrow()
      expect(() => api.assertVersion('^4.0.0-0')).not.toThrow()
      // expect(() => api.assertVersion('>= 4')).not.toThrow()

      expect(() => api.assertVersion(4.1)).toThrow('Expected string or integer value')
      expect(() => api.assertVersion('^100')).toThrow('Require @vue/cli-service "^100"')
    }
  }
  createMockService([plugin], true /* init */)
})

test('api: registerCommand', () => {
  let args
  const service = createMockService([{
    id: 'test',
    apply: api => {
      api.registerCommand('foo', _args => {
        args = _args
      })
    }
  }])

  service.run('foo', { n: 1 })
  expect(args).toEqual({ _: [], n: 1 })
})

test('api: --skip-plugins', () => {
  let untouched = true
  const service = createMockService([{
    id: 'test-command',
    apply: api => {
      api.registerCommand('foo', _args => {

      })
    }
  },
  {
    id: 'vue-cli-plugin-test-plugin',
    apply: api => {
      untouched = false
    }
  }], false)

  service.run('foo', { 'skip-plugins': 'test-plugin' })
  expect(untouched).toEqual(true)
})

test('api: defaultModes', () => {
  fs.writeFileSync('/.env.foo', `FOO=5\nBAR=6`)
  fs.writeFileSync('/.env.foo.local', `FOO=7\nBAZ=8`)

  const plugin1 = {
    id: 'test-defaultModes',
    apply: api => {
      expect(process.env.FOO).toBe('7')
      expect(process.env.BAR).toBe('6')
      expect(process.env.BAZ).toBe('8')
      // for NODE_ENV & BABEL_ENV
      // any mode that is not test or production defaults to development
      expect(process.env.NODE_ENV).toBe('development')
      expect(process.env.BABEL_ENV).toBe('development')
      api.registerCommand('foo', () => {})
    }
  }
  plugin1.apply.defaultModes = {
    foo: 'foo'
  }

  createMockService([plugin1], false /* init */).run('foo')

  delete process.env.NODE_ENV
  delete process.env.BABEL_ENV

  const plugin2 = {
    id: 'test-defaultModes',
    apply: api => {
      expect(process.env.NODE_ENV).toBe('test')
      expect(process.env.BABEL_ENV).toBe('test')
      api.registerCommand('test', () => {})
    }
  }
  plugin2.apply.defaultModes = {
    test: 'test'
  }

  createMockService([plugin2], false /* init */).run('test')
})

test('api: chainWebpack', () => {
  const service = createMockService([{
    id: 'test',
    apply: api => {
      api.chainWebpack(config => {
        config.output.path('test-dist')
      })
    }
  }])

  const config = service.resolveWebpackConfig()
  expect(config.output.path).toBe('test-dist')
})

test('api: configureWebpack', () => {
  const service = createMockService([{
    id: 'test',
    apply: api => {
      api.configureWebpack(config => {
        config.output = {
          path: 'test-dist-2'
        }
      })
    }
  }])

  const config = service.resolveWebpackConfig()
  expect(config.output.path).toBe('test-dist-2')
})

test('api: configureWebpack returning object', () => {
  const service = createMockService([{
    id: 'test',
    apply: api => {
      api.configureWebpack(config => {
        return {
          output: {
            path: 'test-dist-3'
          }
        }
      })
    }
  }])

  const config = service.resolveWebpackConfig()
  expect(config.output.path).toBe('test-dist-3')
})

test('api: configureWebpack preserve ruleNames', () => {
  const service = createMockService([
    {
      id: 'babel',
      apply: require('@vue/cli-plugin-babel')
    },
    {
      id: 'test',
      apply: api => {
        api.configureWebpack({
          module: {
            rules: []
          }
        })
      }
    }
  ])

  const config = service.resolveWebpackConfig()
  expect(config.module.rules[0].__ruleNames).toEqual(['js'])
})

test('internal: should correctly set VUE_CLI_ENTRY_FILES', () => {
  const service = createMockService([{
    id: 'test',
    apply: api => {
      api.configureWebpack(config => {
        config.entry = {
          page1: './src/page1.js',
          page2: './src/page2.js'
        }
      })
    }
  }])

  service.resolveWebpackConfig()
  expect(process.env.VUE_CLI_ENTRY_FILES).toEqual(
    JSON.stringify([
      path.resolve('/', './src/page1.js'),
      path.resolve('/', './src/page2.js')
    ])
  )
})

test('api: configureDevServer', () => {
  const cb = () => {}
  const service = createMockService([{
    id: 'test',
    apply: api => {
      api.configureDevServer(cb)
    }
  }])
  expect(service.devServerConfigFns).toContain(cb)
})

test('api: resolve', () => {
  createMockService([{
    id: 'test',
    apply: api => {
      expect(api.resolve('foo.js')).toBe(path.resolve('/', 'foo.js'))
    }
  }])
})

test('api: hasPlugin', () => {
  createMockService([
    {
      id: 'vue-cli-plugin-foo',
      apply: api => {
        expect(api.hasPlugin('bar')).toBe(true)
        expect(api.hasPlugin('@vue/cli-plugin-bar')).toBe(true)
      }
    },
    {
      id: '@vue/cli-plugin-bar',
      apply: api => {
        expect(api.hasPlugin('foo')).toBe(true)
        expect(api.hasPlugin('vue-cli-plugin-foo')).toBe(true)
      }
    }
  ])
})


<template>
  <div class="dependency-updates">
    <StatusWidget
      v-if="status"
      :icon="icons[status.status]"
      :icon-class="iconClasses[status.status]"
      :title="$t(`org.vue.widgets.dependency-updates.messages.${status.status}`)"
      :status="status"
      @check="checkForUpdates()"
    >
      <template slot="more-actions">
        <VueButton
          :to="{ name: 'project-dependencies' }"
          :label="$t('org.vue.widgets.dependency-updates.page')"
          icon-left="collections_bookmark"
        />
      </template>
    </StatusWidget>
  </div>
</template>

<script>
import { UPDATES_ICONS, UPDATES_ICON_CLASSES } from '../util/consts'

import StatusWidget from './StatusWidget.vue'

export default {
  components: {
    StatusWidget
  },

  sharedData () {
    return mapSharedData('org.vue.widgets.dependency-updates.', {
      status: 'status'
    })
  },

  created () {
    this.icons = UPDATES_ICONS
    this.iconClasses = UPDATES_ICON_CLASSES
  },

  methods: {
    checkForUpdates () {
      // TODO
    }
  }
}
</script>


export function getImageUrl (url) {
  // Fix images in development
  if (process.env.VUE_APP_CLI_UI_DEV && url.charAt(0) === '/') {
    return `http://localhost:${process.env.VUE_APP_GRAPHQL_PORT}${url}`
  }
  return url
}


.DS_Store
node_modules
/dist

# local env files
.env.local
.env.*.local

# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln

/live/
/live-test/


keep keep keep keep keep replace keep keep keep keep

const fs = require('fs')
const path = require('path')
const debug = require('debug')
const merge = require('webpack-merge')
const Config = require('webpack-chain')
const PluginAPI = require('./PluginAPI')
const dotenv = require('dotenv')
const dotenvExpand = require('dotenv-expand')
const defaultsDeep = require('lodash.defaultsdeep')
const { chalk, warn, error, isPlugin, resolvePluginId, loadModule, resolvePkg } = require('@vue/cli-shared-utils')

const { defaults, validate } = require('./options')

module.exports = class Service {
  constructor (context, { plugins, pkg, inlineOptions, useBuiltIn } = {}) {
    process.VUE_CLI_SERVICE = this
    this.initialized = false
    this.context = context
    this.inlineOptions = inlineOptions
    this.webpackChainFns = []
    this.webpackRawConfigFns = []
    this.devServerConfigFns = []
    this.commands = {}
    // Folder containing the target package.json for plugins
    this.pkgContext = context
    // package.json containing the plugins
    this.pkg = this.resolvePkg(pkg)
    // If there are inline plugins, they will be used instead of those
    // found in package.json.
    // When useBuiltIn === false, built-in plugins are disabled. This is mostly
    // for testing.
    this.plugins = this.resolvePlugins(plugins, useBuiltIn)
    // pluginsToSkip will be populated during run()
    this.pluginsToSkip = new Set()
    // resolve the default mode to use for each command
    // this is provided by plugins as module.exports.defaultModes
    // so we can get the information without actually applying the plugin.
    this.modes = this.plugins.reduce((modes, { apply: { defaultModes }}) => {
      return Object.assign(modes, defaultModes)
    }, {})
  }

  resolvePkg (inlinePkg, context = this.context) {
    if (inlinePkg) {
      return inlinePkg
    }
    const pkg = resolvePkg(context)
    if (pkg.vuePlugins && pkg.vuePlugins.resolveFrom) {
      this.pkgContext = path.resolve(context, pkg.vuePlugins.resolveFrom)
      return this.resolvePkg(null, this.pkgContext)
    }
    return pkg
  }

  init (mode = process.env.VUE_CLI_MODE) {
    if (this.initialized) {
      return
    }
    this.initialized = true
    this.mode = mode

    // load mode .env
    if (mode) {
      this.loadEnv(mode)
    }
    // load base .env
    this.loadEnv()

    // load user config
    const userOptions = this.loadUserOptions()
    this.projectOptions = defaultsDeep(userOptions, defaults())

    debug('vue:project-config')(this.projectOptions)

    // apply plugins.
    this.plugins.forEach(({ id, apply }) => {
      if (this.pluginsToSkip.has(id)) return
      apply(new PluginAPI(id, this), this.projectOptions)
    })

    // apply webpack configs from project config file
    if (this.projectOptions.chainWebpack) {
      this.webpackChainFns.push(this.projectOptions.chainWebpack)
    }
    if (this.projectOptions.configureWebpack) {
      this.webpackRawConfigFns.push(this.projectOptions.configureWebpack)
    }
  }

  loadEnv (mode) {
    const logger = debug('vue:env')
    const basePath = path.resolve(this.context, `.env${mode ? `.${mode}` : ``}`)
    const localPath = `${basePath}.local`

    const load = envPath => {
      try {
        const env = dotenv.config({ path: envPath, debug: process.env.DEBUG })
        dotenvExpand(env)
        logger(envPath, env)
      } catch (err) {
        // only ignore error if file is not found
        if (err.toString().indexOf('ENOENT') < 0) {
          error(err)
        }
      }
    }

    load(localPath)
    load(basePath)

    // by default, NODE_ENV and BABEL_ENV are set to "development" unless mode
    // is production or test. However the value in .env files will take higher
    // priority.
    if (mode) {
      // always set NODE_ENV during tests
      // as that is necessary for tests to not be affected by each other
      const shouldForceDefaultEnv = (
        process.env.VUE_CLI_TEST &&
        !process.env.VUE_CLI_TEST_TESTING_ENV
      )
      const defaultNodeEnv = (mode === 'production' || mode === 'test')
        ? mode
        : 'development'
      if (shouldForceDefaultEnv || process.env.NODE_ENV == null) {
        process.env.NODE_ENV = defaultNodeEnv
      }
      if (shouldForceDefaultEnv || process.env.BABEL_ENV == null) {
        process.env.BABEL_ENV = defaultNodeEnv
      }
    }
  }

  setPluginsToSkip (args) {
    const skipPlugins = args['skip-plugins']
    const pluginsToSkip = skipPlugins
      ? new Set(skipPlugins.split(',').map(id => resolvePluginId(id)))
      : new Set()

    this.pluginsToSkip = pluginsToSkip
  }

  resolvePlugins (inlinePlugins, useBuiltIn) {
    const idToPlugin = id => ({
      id: id.replace(/^.\//, 'built-in:'),
      apply: require(id)
    })

    let plugins

    const builtInPlugins = [
      './commands/serve',
      './commands/build',
      './commands/inspect',
      './commands/help',
      // config plugins are order sensitive
      './config/base',
      './config/css',
      './config/prod',
      './config/app'
    ].map(idToPlugin)

    if (inlinePlugins) {
      plugins = useBuiltIn !== false
        ? builtInPlugins.concat(inlinePlugins)
        : inlinePlugins
    } else {
      const projectPlugins = Object.keys(this.pkg.devDependencies || {})
        .concat(Object.keys(this.pkg.dependencies || {}))
        .filter(isPlugin)
        .map(id => {
          if (
            this.pkg.optionalDependencies &&
            id in this.pkg.optionalDependencies
          ) {
            let apply = () => {}
            try {
              apply = require(id)
            } catch (e) {
              warn(`Optional dependency ${id} is not installed.`)
            }

            return { id, apply }
          } else {
            return idToPlugin(id)
          }
        })
      plugins = builtInPlugins.concat(projectPlugins)
    }

    // Local plugins
    if (this.pkg.vuePlugins && this.pkg.vuePlugins.service) {
      const files = this.pkg.vuePlugins.service
      if (!Array.isArray(files)) {
        throw new Error(`Invalid type for option 'vuePlugins.service', expected 'array' but got ${typeof files}.`)
      }
      plugins = plugins.concat(files.map(file => ({
        id: `local:${file}`,
        apply: loadModule(`./${file}`, this.pkgContext)
      })))
    }

    return plugins
  }

  async run (name, args = {}, rawArgv = []) {
    // resolve mode
    // prioritize inline --mode
    // fallback to resolved default modes from plugins or development if --watch is defined
    const mode = args.mode || (name === 'build' && args.watch ? 'development' : this.modes[name])

    // --skip-plugins arg may have plugins that should be skipped during init()
    this.setPluginsToSkip(args)

    // load env variables, load user config, apply plugins
    this.init(mode)

    args._ = args._ || []
    let command = this.commands[name]
    if (!command && name) {
      error(`command "${name}" does not exist.`)
      process.exit(1)
    }
    if (!command || args.help || args.h) {
      command = this.commands.help
    } else {
      args._.shift() // remove command itself
      rawArgv.shift()
    }
    const { fn } = command
    return fn(args, rawArgv)
  }

  resolveChainableWebpackConfig () {
    const chainableConfig = new Config()
    // apply chains
    this.webpackChainFns.forEach(fn => fn(chainableConfig))
    return chainableConfig
  }

  resolveWebpackConfig (chainableConfig = this.resolveChainableWebpackConfig()) {
    if (!this.initialized) {
      throw new Error('Service must call init() before calling resolveWebpackConfig().')
    }
    // get raw config
    let config = chainableConfig.toConfig()
    const original = config
    // apply raw config fns
    this.webpackRawConfigFns.forEach(fn => {
      if (typeof fn === 'function') {
        // function with optional return value
        const res = fn(config)
        if (res) config = merge(config, res)
      } else if (fn) {
        // merge literal values
        config = merge(config, fn)
      }
    })

    // #2206 If config is merged by merge-webpack, it discards the __ruleNames
    // information injected by webpack-chain. Restore the info so that
    // vue inspect works properly.
    if (config !== original) {
      cloneRuleNames(
        config.module && config.module.rules,
        original.module && original.module.rules
      )
    }

    // check if the user has manually mutated output.publicPath
    const target = process.env.VUE_CLI_BUILD_TARGET
    if (
      !process.env.VUE_CLI_TEST &&
      (target && target !== 'app') &&
      config.output.publicPath !== this.projectOptions.publicPath
    ) {
      throw new Error(
        `Do not modify webpack output.publicPath directly. ` +
        `Use the "publicPath" option in vue.config.js instead.`
      )
    }

    if (typeof config.entry !== 'function') {
      let entryFiles
      if (typeof config.entry === 'string') {
        entryFiles = [config.entry]
      } else if (Array.isArray(config.entry)) {
        entryFiles = config.entry
      } else {
        entryFiles = Object.values(config.entry || []).reduce((allEntries, curr) => {
          return allEntries.concat(curr)
        }, [])
      }

      entryFiles = entryFiles.map(file => path.resolve(this.context, file))
      process.env.VUE_CLI_ENTRY_FILES = JSON.stringify(entryFiles)
    }

    return config
  }

  loadUserOptions () {
    // vue.config.c?js
    let fileConfig, pkgConfig, resolved, resolvedFrom
    const esm = this.pkg.type && this.pkg.type === 'module'

    const possibleConfigPaths = [
      process.env.VUE_CLI_SERVICE_CONFIG_PATH,
      './vue.config.js',
      './vue.config.cjs'
    ]

    let fileConfigPath
    for (const p of possibleConfigPaths) {
      const resolvedPath = p && path.resolve(this.context, p)
      if (resolvedPath && fs.existsSync(resolvedPath)) {
        fileConfigPath = resolvedPath
        break
      }
    }

    if (fileConfigPath) {
      if (esm && fileConfigPath === './vue.config.js') {
        throw new Error(`Please rename ${chalk.bold('vue.config.js')} to ${chalk.bold('vue.config.cjs')} when ECMAScript modules is enabled`)
      }

      try {
        fileConfig = loadModule(fileConfigPath, this.context)

        if (typeof fileConfig === 'function') {
          fileConfig = fileConfig()
        }

        if (!fileConfig || typeof fileConfig !== 'object') {
          // TODO: show throw an Error here, to be fixed in v5
          error(
            `Error loading ${chalk.bold(fileConfigPath)}: should export an object or a function that returns object.`
          )
          fileConfig = null
        }
      } catch (e) {
        error(`Error loading ${chalk.bold(fileConfigPath)}:`)
        throw e
      }
    }

    // package.vue
    pkgConfig = this.pkg.vue
    if (pkgConfig && typeof pkgConfig !== 'object') {
      error(
        `Error loading vue-cli config in ${chalk.bold(`package.json`)}: ` +
        `the "vue" field should be an object.`
      )
      pkgConfig = null
    }

    if (fileConfig) {
      if (pkgConfig) {
        warn(
          `"vue" field in package.json ignored ` +
          `due to presence of ${chalk.bold('vue.config.js')}.`
        )
        warn(
          `You should migrate it into ${chalk.bold('vue.config.js')} ` +
          `and remove it from package.json.`
        )
      }
      resolved = fileConfig
      resolvedFrom = 'vue.config.js'
    } else if (pkgConfig) {
      resolved = pkgConfig
      resolvedFrom = '"vue" field in package.json'
    } else {
      resolved = this.inlineOptions || {}
      resolvedFrom = 'inline options'
    }

    if (resolved.css && typeof resolved.css.modules !== 'undefined') {
      if (typeof resolved.css.requireModuleExtension !== 'undefined') {
        warn(
          `You have set both "css.modules" and "css.requireModuleExtension" in ${chalk.bold('vue.config.js')}, ` +
          `"css.modules" will be ignored in favor of "css.requireModuleExtension".`
        )
      } else {
        warn(
          `"css.modules" option in ${chalk.bold('vue.config.js')} ` +
          `is deprecated now, please use "css.requireModuleExtension" instead.`
        )
        resolved.css.requireModuleExtension = !resolved.css.modules
      }
    }

    // normalize some options
    ensureSlash(resolved, 'publicPath')
    if (typeof resolved.publicPath === 'string') {
      resolved.publicPath = resolved.publicPath.replace(/^\.\//, '')
    }
    removeSlash(resolved, 'outputDir')

    // validate options
    validate(resolved, msg => {
      error(
        `Invalid options in ${chalk.bold(resolvedFrom)}: ${msg}`
      )
    })

    return resolved
  }
}

function ensureSlash (config, key) {
  const val = config[key]
  if (typeof val === 'string') {
    config[key] = val.replace(/([^/])$/, '$1/')
  }
}

function removeSlash (config, key) {
  if (typeof config[key] === 'string') {
    config[key] = config[key].replace(/\/$/g, '')
  }
}

function cloneRuleNames (to, from) {
  if (!to || !from) {
    return
  }
  from.forEach((r, i) => {
    if (to[i]) {
      Object.defineProperty(to[i], '__ruleNames', {
        value: r.__ruleNames
      })
      cloneRuleNames(to[i].oneOf, r.oneOf)
    }
  })
}


<script>
import ListItemInfo from '@/components/content/ListItemInfo.vue'

export default {
  components: {
    ListItemInfo
  },

  props: {
    prompt: {
      type: Object,
      required: true
    }
  },

  data () {
    return {
      modifiedValue: undefined
    }
  },

  methods: {
    value (value) {
      if (this.$options.buffer && typeof this.modifiedValue !== 'undefined') return this.modifiedValue
      return JSON.parse(value)
    },

    answer (value) {
      this.modifiedValue = value
      this.$emit('answer', value)
    }
  }
}
</script>

<style lang="stylus">
.prompt
  list-item()
  .prompt-content
    display grid
    grid-template-columns auto 300px
    grid-template-rows auto
    grid-template-areas "info input"
    padding $padding-item

    > .list-item-info
      grid-area info

    > .prompt-input
      grid-area input
      v-box()
      align-items stretch
      justify-content center

    &.vertical
      grid-template-columns auto
      grid-template-areas "info" "input"
      grid-gap $padding-item

  .list-item-info
    margin-right ($padding-item * 2)
</style>


const fs = require('fs')
const path = require('path')

const pluginsDirPath = path.resolve(__dirname, '../packages', '@vue')
const pluginRegEx = new RegExp('cli-plugin-')

function generatePluginDoc (plugin) {
  const entryPath = path.resolve(__dirname, '../packages', '@vue', plugin, 'README.md')
  const entryContent = fs.readFileSync(entryPath)
  const docPath = path.resolve(__dirname, '../docs', 'core-plugins', `${plugin.replace('cli-plugin-', '')}.md`)
  fs.writeFile(docPath, entryContent, () => { })
}

const genDocs = (module.exports = async () => {
  fs.readdir(pluginsDirPath, (_, files) => {
    files.forEach(file => {
      if (pluginRegEx.test(file)) {
        generatePluginDoc(file)
      }
    })
  })
})

genDocs().catch(err => {
  console.error(err)
  process.exit(1)
})


const channels = require('../channels')

function requestRoute (route, context) {
  context.pubsub.publish(channels.ROUTE_REQUESTED, { routeRequested: route })
}

module.exports = {
  requestRoute
}


config.entry = { app: api.resolve(args.entry) }

module.exports = (api, args, options) => {
  const config = api.resolveChainableWebpackConfig()
  const targetDir = api.resolve(args.dest || options.outputDir)

  // respect inline build destination in copy plugin
  if (args.dest && config.plugins.has('copy')) {
    config.plugin('copy').tap(pluginArgs => {
      pluginArgs[0][0].to = targetDir
      return pluginArgs
    })
  }

  if (args.modern) {
    const ModernModePlugin = require('../../webpack/ModernModePlugin')
    if (!args.modernBuild) {
      // Inject plugin to extract build stats and write to disk
      config
        .plugin('modern-mode-legacy')
        .use(ModernModePlugin, [{
          targetDir,
          isModernBuild: false,
          unsafeInline: args['unsafe-inline']
        }])
    } else {
      // Inject plugin to read non-modern build stats and inject HTML
      config
        .plugin('modern-mode-modern')
        .use(ModernModePlugin, [{
          targetDir,
          isModernBuild: true,
          unsafeInline: args['unsafe-inline'],
          // as we may generate an addition file asset (if `no-unsafe-inline` specified)
          // we need to provide the correct directory for that file to place in
          jsDirectory: require('../../util/getAssetPath')(options, 'js')
        }])
    }
  }

  const rawConfig = api.resolveWebpackConfig(config)

  // respect inline entry
  if (args.entry && !options.pages) {
    const entry = api.resolve(args.entry)
    rawConfig.entry = { app: entry }
    process.env.VUE_CLI_ENTRY_FILES = JSON.stringify([entry])
  }

  return rawConfig
}


import LOADING from '@/graphql/loading/loading.gql'

export default {
  Mutation: {
    connectedSet: (root, { value }, { cache }) => {
      const data = {
        connected: value
      }
      cache.writeData({ data })
      return null
    },

    loadingChange: (root, { mod }, { cache }) => {
      const { loading } = cache.readQuery({ query: LOADING })
      const data = {
        loading: loading + mod
      }
      cache.writeData({ data })
      return null
    },

    darkModeSet: (root, { enabled }, { cache }) => {
      const data = {
        darkMode: enabled
      }
      cache.writeData({ data })
      const el = document.getElementsByTagName('html')[0]
      if (enabled) {
        el.classList.add('vue-ui-dark-mode')
      } else {
        el.classList.remove('vue-ui-dark-mode')
      }
      return null
    },

    currentProjectIdSet: (root, { projectId }, { cache }) => {
      const data = {
        currentProjectId: projectId
      }
      cache.writeData({ data })
      return null
    }
  }
}


describe('Plugins', () => {
  it('Should display the plugins', () => {
    cy.visit('/plugins')
    cy.get('.project-plugin-item').should('have.length', 6)
  })

  it('Should add a plugin', () => {
    cy.visit('/plugins')
    cy.get('[data-testid="add-plugin"]').click()
    cy.get('.project-plugins-add').should('be.visible')
    // Search
    cy.get('.instant-search-input input').clear().type('pwa')
    cy.get('.package-search-item:contains("@vue/cli-plugin-pwa")').should('be.visible')
    cy.get('.instant-search-input input').clear().type('unit-jest')
    cy.get('.package-search-item:contains("@vue/cli-plugin-pwa")').should('be.not.visible')
    cy.get('.package-search-item:contains("@vue/cli-plugin-unit-jest")').should('be.visible')
    cy.get('.instant-search-input input').clear()
    // Install
    cy.get('.instant-search-input input').clear().type('pwa')
    cy.get('.package-search-item:contains("@vue/cli-plugin-pwa") [data-testid="name"]').should('be.visible').click()
    cy.get('[data-testid="download-plugin"]:contains("@vue/cli-plugin-pwa")').should('not.have.class', 'disabled').click()
    cy.get('.loading-screen .vue-ui-loading-indicator').should('be.visible')
    cy.get('.prompts-list', { timeout: 250000 }).should('be.visible')
    cy.get('[data-testid="finish-install"]').should('not.have.class', 'disabled').click({ force: true })
    cy.get('.loading-screen .vue-ui-loading-indicator', { timeout: 3000 }).should('be.visible')
    cy.get('.file-diff-view', { timeout: 250000 }).should('be.visible')
    cy.get('[data-testid="skip-button"]', { timeout: 3000 })
      .should('be.visible')
      .should('not.have.class', 'disabled')
      .click()
    cy.get('.project-plugin-item').should('have.length', 6)
  })
})


const rawConfig = api.resolveWebpackConfig(config)

keep keep keep replace replace replace replace replace replace replace replace replace replace keep


#import "./taskFragment.gql"

query tasks {
  tasks {
    ...task
  }
}


<template>

      <VueIcon icon="cached"/>

{{ $t('org.vue.components.file-diff-view.files-changed') }}

{{ fileDiffs && fileDiffs.length }}

      <transition name="vue-ui-fade">
        <VueLoadingIndicator
          v-if="loading && fileDiffs.length"
          class="small accent"
        />
      </transition>

      <VueInput
        v-model="search"
        icon-left="search"
        :placeholder="$t('org.vue.components.file-diff-view.search-file')"
      />
      <VueButton
        :icon-left="allCollapsed ? 'keyboard_arrow_down' : 'keyboard_arrow_up'"
        :label="$t(`org.vue.components.file-diff-view.actions.${allCollapsed ? 'expand-all' : 'collapse-all'}`)"
        @click="setCollapsedToAll(!allCollapsed)"
      />
      <VueButton
        icon-left="refresh"
        class="icon-button"
        @click="refresh()"
      />

        <VueIcon icon="error" class="empty-icon"/>
        {{ $t('org.vue.components.file-diff-view.error') }}

        <VueIcon icon="check_circle" class="empty-icon"/>
        {{ $t('org.vue.components.file-diff-view.empty') }}

      <template v-else>
        <FileDiff
          v-for="fileDiff of filteredList"
          :key="fileDiff.id"
          :file-diff="fileDiff"
          :collapsed="!!collapsed[fileDiff.id]"
          @update:collapsed="value => $set(collapsed, fileDiff.id, value)"
        />
      </template>

      <template v-if="!error && fileDiffs && fileDiffs.length">
        <VueButton
          icon-left="vertical_align_bottom"
          :label="$t('org.vue.components.file-diff-view.actions.commit')"
          class="big primary"
          @click="showCommitModal = true"
        />
        <VueButton
          :label="$t('org.vue.components.file-diff-view.actions.skip')"
          class="big"
          data-testid="skip-button"
          @click="skip()"
        />
      </template>
      <template v-else>
        <VueButton
          icon-left="done"
          :label="$t('org.vue.components.file-diff-view.actions.continue')"
          class="big primary"
          data-testid="skip-button"
          @click="skip()"
        />
        <VueButton
          :label="$t('org.vue.components.file-diff-view.actions.refresh')"
          icon-left="refresh"
          class="big"
          @click="refresh()"
        />
      </template>

    <transition name="vue-ui-fade">
      <VueLoadingIndicator
        v-if="loading && !fileDiffs.length"
        class="accent big overlay"
      />
    </transition>

    <VueModal
      v-if="showCommitModal"
      :title="$t('org.vue.components.file-diff-view.modals.commit.title')"
      class="medium"
      @close="showCommitModal = false"
    >

        <VueFormField
          :title="$t('org.vue.components.file-diff-view.modals.commit.input')"
          :subtitle="$t('org.vue.components.file-diff-view.modals.commit.subtitle')"
        >
          <VueInput
            v-model="commitMessage"
            icon-left="local_offer"
            v-focus
            @keyup.enter="commitMessage && commit()"
          />
        </VueFormField>

        <VueButton
          :label="$t('org.vue.components.file-diff-view.modals.commit.actions.cancel')"
          class="flat"
          @click="showCommitModal = false"
        />
        <VueButton
          :label="$t('org.vue.components.file-diff-view.modals.commit.actions.commit')"
          class="primary"
          icon-left="vertical_align_bottom"
          :disabled="!commitMessage"
          @click="commit()"
        />

    </VueModal>

</template>


<template>
  <div class="app-loading">
    <transition name="vue-ui-fade" appear>
      <VueLoadingIndicator
        v-if="loading"
        class="primary"
      />
    </transition>
  </div>
</template>

<script>
import LOADING from '@/graphql/loading/loading.gql'

export default {
  apollo: {
    loading: {
      query: LOADING,
      fetchPolicy: 'cache-only'
    }
  }
}
</script>