<commit_before>/*jshint -W030 */
var requirejs = require('requirejs');
var assert = require('assert');
var should = require('should');
requirejs.config({
  baseUrl: 'js',
  nodeRequire: require
});

describe('Actions Testing', function() {
  // Load modules with requirejs before tests
  var Event, Game, Card, Action, game;
  before(function(done) {
    requirejs(['game', 'event', 'card', 'action'], function(game, event, card, action) {
      Game = game;
      Event = event;
      Action = action;
      Card = card;
      done();
    });
  });

  before(function(done) {
    game = Game.new();
    done();
  });

  describe('#Event based actions', function(){
    it('should work with a single event action');
    it('should work with a multi event action');
    it('should fail if event is invalid');
  });
  describe('#Function based actions', function(){
    it('should work with a single function action');
    it('should work with a multi function action');
    it('should fail if function is invalid');
  });
  describe('#Mixed actions', function(){
    it('should work with actions with one event and one function');
    it('should work with actions with one event and multiple functions');
    it('should work with actions with multiple events and one function');
  });
  describe('#Prefixed actions', function(){
    it('should work with a "choose" functions cards');
    it('should work with a "choose" events cards');
    it('should work with a "choose" with mixed events and functions cards');
    it('should work with a "random" card');
  });
  describe('#Recursive actions', function(){
    it('should randomly branch');
    it('should wisely branch (choose)');
  });

});
<commit_msg>added tests for actions, none of them pass at the moment so I skip them<commit_after>/*jshint -W030 */
var requirejs = require('requirejs');
var assert = require('assert');
var should = require('should');
requirejs.config({
  baseUrl: 'js',
  nodeRequire: require
});

describe.skip('Actions Testing', function() {
  // Load modules with requirejs before tests
  var Event, Game, Card, Action, game, Gold, Silver, Duchy, base;
  before(function(done) {
    requirejs(['game', 'event', 'card', 'action', 'cards/gold', 'cards/silver', 'cards/duchy', 'modes/base'], function(game, event, card, action, gold, silver, duchy, B) {
      Game = game;
      Event = event;
      Action = action;
      Card = card;
      Gold = gold;
      Silver = silver;
      Duchy = duchy;
      base = B;
      done();
    });
  });

  before(function(done) {
    game = Game.new();
    done();
  });
  beforeEach(function() {
    game.startGame({
      players: 2,
      cards: [Gold, Silver, Duchy],
      mode: base
    });
  });

  describe('#Event based actions', function(){
    it('should work with a single event action', function() {
      var A = Card.extend(Action, {
        initialize: function(game)  {
          Card.initialize.call(this, {
            name: 'name',
            text: 'text',
            cost: 1,
            img: ''
          });
          Action.initialize.call(this, [
            Event.new(game, 'cards 2')
          ]);
        },
      });
      A.new.bind(A, game).should.not.throw();
      var a = A.new(game);
      var p = game.currentPlayer();
      // add it to the player hand (what a cheater!)
      p.hand.push(a);
      game.play.bind(game, 5).should.not.throw(); // play card at index 5 in players hand
      p.hand.should.have.lengthOf(6); // he played one card and drew 2
    });
    it('should work with a multi event action', function() {
      var A = Card.extend(Action, {
        initialize: function(game)  {
          Card.initialize.call(this, {
            name: 'name',
            text: 'text',
            cost: 1,
            img: ''
          });
          Action.initialize.call(this, [
            Event.new(game, 'cards 2'),
            Event.new(game, 'buys 2')
          ]);
        },
      });
      A.new.bind(A, game).should.not.throw();
      var a = A.new(game);
      var p = game.currentPlayer();
      p.hand.push(a);
      game.play.bind(game, 5).should.not.throw();
      p.hand.should.have.lengthOf(6);
      game.buys.should.be.eql(3);
    });
    it('should fail if event is invalid', function() {
      var A = Card.extend(Action, {
        initialize: function(game)  {
          Card.initialize.call(this, {
            name: 'name',
            text: 'text',
            cost: 1,
            img: ''
          });
          Action.initialize.call(this, [
            Event.new(game, 'wrong 2'),
            Event.new(game, 'buys 2')
          ]);
        },
      });
      A.new.bind(A, game).should.throw(/wrong.*valid.*event/);
    });
  });
  describe('#Function based actions', function(){
    it('should work with a single function action', function() {
      var A = Card.extend(Action, {
        initialize: function(game)  {
          Card.initialize.call(this, {
            name: 'name',
            text: 'text',
            cost: 1,
            img: ''
          });
          Action.initialize.call(this, [
            (function(game) {
              game.currentPlayer().graveyard.push(Gold.new());
            }).bind(null, game)
          ]);
        },
      });
      A.new.bind(A, game).should.not.throw();
      var a = A.new(game);
      var p = game.currentPlayer();
      p.hand.push(a);
      game.play.bind(game, 5).should.not.throw();
      p.graveyard.should.containEql(Gold.new());
    });
    it('should work with a multi function action', function() {
      var A = Card.extend(Action, {
        initialize: function(game)  {
          Card.initialize.call(this, {
            name: 'name',
            text: 'text',
            cost: 1,
            img: ''
          });
          Action.initialize.call(this, [
            (function(game) {
              game.currentPlayer().graveyard.push(Gold.new());
            }).bind(null, game),
            (function(game) {
              game.currentPlayer().graveyard.push(Silver.new());
            }).bind(null, game),
          ]);
        },
      });
      A.new.bind(A, game).should.not.throw();
      var a = A.new(game);
      var p = game.currentPlayer();
      p.hand.push(a);
      game.play.bind(game, 5).should.not.throw();
      p.graveyard.should.containEql(Gold.new());
      p.graveyard.should.containEql(Silver.new());
    });
    it('should be able to detect if function is invalid', function() {
      var A = Card.extend(Action, {
        initialize: function(game)  {
          Card.initialize.call(this, {
            name: 'name',
            text: 'text',
            cost: 1,
            img: ''
          });
          Action.initialize.call(this, [
            null // not a valid thing
          ]);
        },
      });
      A.new.bind(A, game).should.not.throw();
      var a = A.new(game);
      a.checkEventArray.bind(a).should.throw(/invalid.*event/);
    });
  });
  describe('#Mixed actions', function(){
    it('should work with actions with one event and one function', function() {
      var A = Card.extend(Action, {
        initialize: function(game)  {
          Card.initialize.call(this, {
            name: 'name',
            text: 'text',
            cost: 1,
            img: ''
          });
          Action.initialize.call(this, [
            Event.new(game, 'cards 1'),
            (function(game) {
              game.currentPlayer().graveyard.push(Silver.new());
            }).bind(null, game),
          ]);
        },
      });
      A.new.bind(A, game).should.not.throw();
      var a = A.new(game);
      var p = game.currentPlayer();
      p.hand.push(a);
      game.play.bind(game, 5).should.not.throw();
      p.graveyard.should.containEql(Silver.new());
      p.hand.should.have.lengthOf(5);
    });
    it('should work with actions with one event and multiple functions', function() {
      var A = Card.extend(Action, {
        initialize: function(game)  {
          Card.initialize.call(this, {
            name: 'name',
            text: 'text',
            cost: 1,
            img: ''
          });
          Action.initialize.call(this, [
            Event.new(game, 'cards 1'),
            (function(game) {
              game.currentPlayer().graveyard.push(Silver.new());
            }).bind(null, game),
            (function(game) {
              game.currentPlayer().graveyard.push(Gold.new());
            }).bind(null, game),
          ]);
        },
      });
      A.new.bind(A, game).should.not.throw();
      var a = A.new(game);
      var p = game.currentPlayer();
      p.hand.push(a);
      game.play.bind(game, 5).should.not.throw();
      p.graveyard.should.containEql(Silver.new());
      p.graveyard.should.containEql(Gold.new());
      p.hand.should.have.lengthOf(5);
    });
    it('should work with actions with multiple events and one function', function() {
      var A = Card.extend(Action, {
        initialize: function(game)  {
          Card.initialize.call(this, {
            name: 'name',
            text: 'text',
            cost: 1,
            img: ''
          });
          Action.initialize.call(this, [
            Event.new(game, 'cards 1'),
            Event.new(game, 'buys 1'),
            (function(game) {
              game.currentPlayer().graveyard.push(Gold.new());
            }).bind(null, game),
          ]);
        },
      });
      A.new.bind(A, game).should.not.throw();
      var a = A.new(game);
      var p = game.currentPlayer();
      p.hand.push(a);
      game.play.bind(game, 5).should.not.throw();
      p.graveyard.should.containEql(Gold.new());
      p.hand.should.have.lengthOf(5);
      game.buys.should.be.eql(2);
    });
    it('should work with actions with multiple events and multiple functions', function() {
      var A = Card.extend(Action, {
        initialize: function(game)  {
          Card.initialize.call(this, {
            name: 'name',
            text: 'text',
            cost: 1,
            img: ''
          });
          Action.initialize.call(this, [
            Event.new(game, 'cards 1'),
            Event.new(game, 'buys 1'),
            (function(game) {
              game.currentPlayer().graveyard.push(Gold.new());
            }).bind(null, game),
            (function(game) {
              game.currentPlayer().graveyard.push(Silver.new());
            }).bind(null, game),
          ]);
        },
      });
      A.new.bind(A, game).should.not.throw();
      var a = A.new(game);
      var p = game.currentPlayer();
      p.hand.push(a);
      game.play.bind(game, 5).should.not.throw();
      p.graveyard.should.containEql(Gold.new());
      p.graveyard.should.containEql(Silver.new());
      p.hand.should.have.lengthOf(5);
      game.buys.should.be.eql(2);
    });
  });
  describe('#Prefixed actions', function(){
    it('should work with "choose" functions cards', function() {
      var A = Card.extend(Action, {
        initialize: function(game)  {
          Card.initialize.call(this, {
            name: 'name',
            text: 'text',
            cost: 1,
            img: ''
          });
          Action.initialize.call(this, [
            'choose', // eq to choose 1
            (function(game) {
              game.currentPlayer().graveyard.push(Gold.new());
            }).bind(null, game),
            (function(game) {
              game.currentPlayer().graveyard.push(Silver.new());
            }).bind(null, game),
          ]);
        },
      });
      A.new.bind(A, game).should.not.throw();
      var a = A.new(game);
      var p = game.currentPlayer();
      p.hand.push(a);
      game.play.bind(game, 5).should.not.throw();
      game.choose(0);
      p.graveyard.should.containEql(Gold.new());
      p.graveyard.should.not.containEql(Silver.new());

      // play again
      p.hand.push(a);
      game.addActions(1); // or we won't be able to play
      game.play.bind(game, 5).should.not.throw();
      game.choose(1);
      p.graveyard.should.containEql(Gold.new());
      p.graveyard.should.containEql(Silver.new());
    });
    it('should work with "choose" events cards', function() {
      var A = Card.extend(Action, {
        initialize: function(game)  {
          Card.initialize.call(this, {
            name: 'name',
            text: 'text',
            cost: 1,
            img: ''
          });
          Action.initialize.call(this, [
            'choose',
            Event.new(game, 'cards 1'),
            Event.new(game, 'buys 1'),
          ]);
        },
      });
      A.new.bind(A, game).should.not.throw();
      var a = A.new(game);
      var p = game.currentPlayer();
      p.hand.push(a);
      game.play.bind(game, 5).should.not.throw();
      game.choose(0);
      p.hand.should.have.lengthOf(5);
      game.buys.should.be.eql(1);

      game.addActions(1); // or we won't be able to play
      p.hand.push(a);
      game.play.bind(game, 5).should.not.throw();
      game.choose(1);
      game.buys.should.be.eql(2);
    });
    it('should work with a "choose" with mixed events and functions cards', function() {
      var A = Card.extend(Action, {
        initialize: function(game)  {
          Card.initialize.call(this, {
            name: 'name',
            text: 'text',
            cost: 1,
            img: ''
          });
          Action.initialize.call(this, [
            'choose',
            Event.new(game, 'cards 1'),
            Event.new(game, 'buys 1'),
            (function(game) {
              game.currentPlayer().graveyard.push(Gold.new());
            }).bind(null, game),
          ]);
        },
      });
      A.new.bind(A, game).should.not.throw();
      var a = A.new(game);
      var p = game.currentPlayer();
      p.hand.push(a);
      game.play.bind(game, 5).should.not.throw();
      game.choose(0);
      p.hand.should.have.lengthOf(5);
      game.buys.should.be.eql(1);
      p.graveyard.should.not.containEql(Gold.new());

      game.addActions(1); // or we won't be able to play
      p.hand.push(a);
      game.play.bind(game, 5).should.not.throw();
      game.choose(2);
      p.graveyard.should.containEql(Gold.new());
    });
    it('should work with a "random" card');
  });
  describe('#Recursive actions', function(){
    it('should fire all events in a simple recursive array');
    it('should randomly branch');
    it('should wisely branch (choose)');
  });

});
<|endoftext|>


<commit_before>"use strict"

if (typeof (require) != 'undefined') {
	var loader = require('../../../../node_modules/Butterfly-js/dist/butterfly.min.js').loader;
}

/**
 * This module contains the definition of the Warrior entitie
 */
loader.addModule('GameEntities',
'Canvas',
function (canvas) {
	var entities = {},
		Warrior;

	(function () {
		var _radius = 10;

		/* Warrior Class */
		Warrior = function (name, x, y) {
			this.name = name;
			this.keys = 0;
			this.x  = this.originalX = x;
			this.y = this.originalY = y;
			this.r = _radius;
			this.angle = this.originalAngle = 0;
			this.speed = this.originalSpeed = 0;
			this.graphicLoaded = false;
		};

		function _walk (warrior, enable) {
			if (enable && warrior.speed !== 0) {
				// already walking
				return;
			}
			else if (enable) {
				warrior.speed = 5;
			}
			else {
				warrior.speed = 0;
			}
		}

		Warrior.prototype.walkLeft = function (enable) {
			_walk(this, enable);
			this.angle = Math.PI;
		};

		Warrior.prototype.walkRight = function (enable) {
			_walk(this, enable);
			this.angle = 0;
		};

		Warrior.prototype.walkUp = function (enable) {
			_walk(this, enable);
			this.angle = 3 * Math.PI / 2;
		};

		Warrior.prototype.walkDown = function (enable) {
			_walk(this, enable);
			this.angle = Math.PI / 2;
		};

		/**
		 * Method to update the warrior position according to its speed
		 */
		Warrior.prototype.updatePosition = function () {
			this.x += Math.cos(this.angle) * this.speed;
			this.y += Math.sin(this.angle) * this.speed;
		};

		Warrior.prototype.bumpBack = function () {
			this.x -= Math.cos(this.angle) * this.speed;
			this.y -= Math.sin(this.angle) * this.speed;

			this.speed = 0;
		};

		Warrior.prototype.setGraphic = function (graphic) {
			this.graphic = graphic;
		};

		/**
		 * Reset the warrior to its original values
		 */
		Warrior.prototype.reset = function () {
			this.x  = this.originalX;
			this.y = this.originalY;
			this.angle = this.originalAngle;
			this.speed = this.originalSpeed;
		};

		/**
		 * Draw the warrior on the screen
		 */
		Warrior.prototype.draw = function (x, y) {
			canvas.drawImage(
				this.graphic,
				x - this.graphic.width / 2,
				y - this.graphic.height / 2
			);
		};

		Warrior.prototype.addKey = function () {
			this.keys++;
		};

		Warrior.prototype.useKey = function () {
			this.keys--;
		};

		Warrior.prototype.getNumberOfKeys = function () {
			return this.keys;
		};
		/* End Warrior Class */
	})();

	entities.Warrior = Warrior;

	return entities;
});
<commit_msg>ADVENTURE - guidelines<commit_after>"use strict"

if (typeof (require) != 'undefined') {
	var loader = require('../../../../node_modules/Butterfly-js/dist/butterfly.min.js').loader;
}

/**
 * This module contains the definition of the Warrior entitie
 */
loader.addModule('GameEntities',
'Canvas',
function (canvas) {
	var entities = {},
		Warrior;

	(function () {
		var _radius = 10;

		/* Warrior Class */
		Warrior = function (name, x, y) {
			this.name = name;
			this.keys = 0;
			this.x = this.originalX = x;
			this.y = this.originalY = y;
			this.r = _radius;
			this.angle = this.originalAngle = 0;
			this.speed = this.originalSpeed = 0;
			this.graphicLoaded = false;
		};

		function _walk (warrior, enable) {
			if (enable && warrior.speed !== 0) {
				// already walking
				return;
			}
			else if (enable) {
				warrior.speed = 5;
			}
			else {
				warrior.speed = 0;
			}
		}

		Warrior.prototype.walkLeft = function (enable) {
			_walk(this, enable);
			this.angle = Math.PI;
		};

		Warrior.prototype.walkRight = function (enable) {
			_walk(this, enable);
			this.angle = 0;
		};

		Warrior.prototype.walkUp = function (enable) {
			_walk(this, enable);
			this.angle = 3 * Math.PI / 2;
		};

		Warrior.prototype.walkDown = function (enable) {
			_walk(this, enable);
			this.angle = Math.PI / 2;
		};

		/**
		 * Method to update the warrior position according to its speed
		 */
		Warrior.prototype.updatePosition = function () {
			this.x += Math.cos(this.angle) * this.speed;
			this.y += Math.sin(this.angle) * this.speed;
		};

		Warrior.prototype.bumpBack = function () {
			this.x -= Math.cos(this.angle) * this.speed;
			this.y -= Math.sin(this.angle) * this.speed;

			this.speed = 0;
		};

		Warrior.prototype.setGraphic = function (graphic) {
			this.graphic = graphic;
		};

		/**
		 * Reset the warrior to its original values
		 */
		Warrior.prototype.reset = function () {
			this.x  = this.originalX;
			this.y = this.originalY;
			this.angle = this.originalAngle;
			this.speed = this.originalSpeed;
		};

		/**
		 * Draw the warrior on the screen
		 */
		Warrior.prototype.draw = function (x, y) {
			canvas.drawImage(
				this.graphic,
				x - this.graphic.width / 2,
				y - this.graphic.height / 2
			);
		};

		Warrior.prototype.addKey = function () {
			this.keys++;
		};

		Warrior.prototype.useKey = function () {
			this.keys--;
		};

		Warrior.prototype.getNumberOfKeys = function () {
			return this.keys;
		};
		/* End Warrior Class */
	})();

	entities.Warrior = Warrior;

	return entities;
});
<|endoftext|>


<commit_before>import { trackEvent } from './analytics';

import { computeHtml, computeCss, computeJs } from './computes';
import { JsModes } from './codeModes';
import { deferred } from './deferred';
import { getExtensionFromFileName } from './fileUtils';
const esprima = require('esprima');

window.DEBUG = document.cookie.indexOf('wmdebug') > -1;
window.$ = document.querySelector.bind(document);

window.chrome = window.chrome || {};
window.chrome.i18n = {
	getMessage: () => {}
};

window.$all = selector => [...document.querySelectorAll(selector)];
window.IS_EXTENSION = !!window.chrome.extension;
export const BASE_PATH =
	window.chrome.extension ||
	window.DEBUG ||
	process.env.NODE_ENV === 'development'
		? '/'
		: '/app';

var alphaNum = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';

/**
 * The following 2 functions are supposed to find the next/previous sibling until the
 * passed `selector` is matched. But for now it actually finds the next/previous
 * element of `this` element in the list of `selector` matched element inside `this`'s
 * parent.
 * @param  Selector that should match for next siblings
 * @return element Next element that mathes `selector`
 */
Node.prototype.nextUntil = function(selector) {
	const siblings = Array.from(this.parentNode.querySelectorAll(selector));
	const index = siblings.indexOf(this);
	return siblings[index + 1];
};

/*
 * @param  Selector that should match for next siblings
 * @return element Next element that mathes `selector`
 */
Node.prototype.previousUntil = function(selector) {
	const siblings = Array.from(this.parentNode.querySelectorAll(selector));
	const index = siblings.indexOf(this);
	return siblings[index - 1];
};

// Safari doesn't have this!
window.requestIdleCallback =
	window.requestIdleCallback ||
	function(fn) {
		setTimeout(fn, 10);
	};

// https://github.com/substack/semver-compare/blob/master/index.js
export function semverCompare(a, b) {
	var pa = a.split('.');
	var pb = b.split('.');
	for (var i = 0; i < 3; i++) {
		var na = Number(pa[i]);
		var nb = Number(pb[i]);
		if (na > nb) {
			return 1;
		}
		if (nb > na) {
			return -1;
		}
		if (!isNaN(na) && isNaN(nb)) {
			return 1;
		}
		if (isNaN(na) && !isNaN(nb)) {
			return -1;
		}
	}
	return 0;
}

export function generateRandomId(len) {
	var length = len || 10;
	var id = '';
	for (var i = length; i--; ) {
		id += alphaNum[~~(Math.random() * alphaNum.length)];
	}
	return id;
}

export function onButtonClick(btn, listener) {
	btn.addEventListener('click', function buttonClickListener(e) {
		listener(e);
		return false;
	});
}

export function log() {
	if (window.DEBUG) {
		console.log(Date.now(), ...arguments);
	}
}

/**
 * Adds timed limit on the loops found in the passed code.
 * Contributed by Ariya Hidayat!
 * @param code {string}	Code to be protected from infinite loops.
 */
export function addInfiniteLoopProtection(code, { timeout }) {
	var loopId = 1;
	var patches = [];
	var varPrefix = '_wmloopvar';
	var varStr = 'var %d = Date.now();\n';
	var checkStr = `\nif (Date.now() - %d > ${timeout}) { window.top.previewException(new Error("Infinite loop")); break;}\n`;

	esprima.parse(
		code,
		{
			tolerant: true,
			range: true,
			jsx: true
		},
		function(node) {
			switch (node.type) {
				case 'DoWhileStatement':
				case 'ForStatement':
				case 'ForInStatement':
				case 'ForOfStatement':
				case 'WhileStatement':
					var start = 1 + node.body.range[0];
					var end = node.body.range[1];
					var prolog = checkStr.replace('%d', varPrefix + loopId);
					var epilog = '';

					if (node.body.type !== 'BlockStatement') {
						// `while(1) doThat()` becomes `while(1) {doThat()}`
						prolog = '{' + prolog;
						epilog = '}';
						--start;
					}

					patches.push({
						pos: start,
						str: prolog
					});
					patches.push({
						pos: end,
						str: epilog
					});
					patches.push({
						pos: node.range[0],
						str: varStr.replace('%d', varPrefix + loopId)
					});
					++loopId;
					break;

				default:
					break;
			}
		}
	);

	/* eslint-disable no-param-reassign */
	patches
		.sort(function(a, b) {
			return b.pos - a.pos;
		})
		.forEach(function(patch) {
			code = code.slice(0, patch.pos) + patch.str + code.slice(patch.pos);
		});

	/* eslint-disable no-param-reassign */
	return code;
}

export function getHumanDate(timestamp) {
	var d = new Date(timestamp);
	var retVal =
		d.getDate() +
		' ' +
		[
			'January',
			'February',
			'March',
			'April',
			'May',
			'June',
			'July',
			'August',
			'September',
			'October',
			'November',
			'December'
		][d.getMonth()] +
		' ' +
		d.getFullYear();
	return retVal;
}

// create a one-time event
export function once(node, type, callback) {
	// create event
	node.addEventListener(type, function(e) {
		// remove event
		e.target.removeEventListener(type, arguments.callee);
		// call handler
		return callback(e);
	});
}

export function downloadFile(fileName, blob) {
	function downloadWithAnchor() {
		var a = document.createElement('a');
		a.href = window.URL.createObjectURL(blob);
		a.download = fileName;
		a.style.display = 'none';
		document.body.appendChild(a);
		a.click();
		a.remove();
	}

	// HACK: because chrome.downloads isn't working on optional permissions
	// anymore.
	downloadWithAnchor();

	/* if (false && window.IS_EXTENSION) {
		chrome.downloads.download({
				url: window.URL.createObjectURL(blob),
				filename: fileName,
				saveAs: true
			},
			() => {
				// If there was an error, just download the file using ANCHOR method.
				if (chrome.runtime.lastError) {
					downloadWithAnchor();
				}
			}
		);
	} else {
		downloadWithAnchor();
	} */
}

export function writeFile(name, blob, cb) {
	var fileWritten = false;

	function getErrorHandler(type) {
		return function() {
			log(arguments);
			trackEvent('fn', 'error', type);
			// When there are too many write errors, show a message.
			writeFile.errorCount = (writeFile.errorCount || 0) + 1;
			if (writeFile.errorCount === 4) {
				setTimeout(function() {
					alert(
						"Oops! Seems like your preview isn't updating. It's recommended to switch to the web app: https://webmakerapp.com/app/.\n\n If you still want to get the extension working, please try the following steps until it fixes:\n - Refresh Web Maker\n - Restart browser\n - Update browser\n - Reinstall Web Maker (don't forget to export all your creations from saved items pane (click the OPEN button) before reinstalling)\n\nIf nothing works, please tweet out to @webmakerApp."
					);
					trackEvent('ui', 'writeFileMessageSeen');
				}, 1000);
			}
		};
	}

	// utils.log('writing file ', name);
	window.webkitRequestFileSystem(
		window.TEMPORARY,
		1024 * 1024 * 5,
		function(fs) {
			fs.root.getFile(
				name,
				{
					create: true
				},
				function(fileEntry) {
					fileEntry.createWriter(fileWriter => {
						function onWriteComplete() {
							if (fileWritten) {
								// utils.log('file written ', name);
								return cb();
							}
							fileWritten = true;
							// Set the write pointer to starting of file
							fileWriter.seek(0);
							fileWriter.write(blob);
							return false;
						}
						fileWriter.onwriteend = onWriteComplete;
						// Empty the file contents
						fileWriter.truncate(0);
						// utils.log('truncating file ', name);
					}, getErrorHandler('createWriterFail'));
				},
				getErrorHandler('getFileFail')
			);
		},
		getErrorHandler('webkitRequestFileSystemFail')
	);
}

export function loadJS(src) {
	var d = deferred();
	var ref = window.document.getElementsByTagName('script')[0];
	var script = window.document.createElement('script');
	script.src = src;
	script.async = true;
	ref.parentNode.insertBefore(script, ref);
	script.onload = function() {
		d.resolve();
	};
	return d.promise;
}

export function loadCss({ url, id }) {
	var d = deferred();
	var style = window.document.createElement('link');
	style.setAttribute('href', url);
	style.setAttribute('rel', 'stylesheet');
	if (id) {
		style.setAttribute('id', id);
	}
	document.head.appendChild(style);
	style.onload = function() {
		d.resolve();
	};
	return d.promise;
}

/* eslint-disable max-params */
export function getCompleteHtml(html, css, js, item, isForExport) {
	/* eslint-enable max-params */

	if (!item) {
		return '';
	}
	var externalJs = '',
		externalCss = '';
	if (item.externalLibs) {
		externalJs = item.externalLibs.js
			.split('\n')
			.reduce(function(scripts, url) {
				return scripts + (url ? '\n' : '');
			}, '');
		externalCss = item.externalLibs.css
			.split('\n')
			.reduce(function(links, url) {
				return (
					links +
					(url ? '\n<link rel="stylesheet" href="' + url + '"></link>' : '')
				);
			}, '');
	}
	var contents =
		'<!DOCTYPE html>\n' +
		'\n\n' +
		'<meta charset="UTF-8" />\n' +
		externalCss +
		'\n' +
		'\n' +
		'\n' +
		'\n' +
		html +
		'\n' +
		externalJs +
		'\n';

	if (!isForExport) {
		contents +=
			'';
	}

	if (item.jsMode === JsModes.ES6) {
		contents +=
			'';
	}

	if (typeof js === 'string') {
		contents += '\n\n';

	return contents;
}

export function saveAsHtml(item) {
	var htmlPromise = computeHtml(item.html, item.htmlMode);
	var cssPromise = computeCss(item.css, item.cssMode);
	var jsPromise = computeJs(item.js, item.jsMode, false);
	Promise.all([htmlPromise, cssPromise, jsPromise]).then(result => {
		var html = result[0].code,
			css = result[1].code,
			js = result[2].code;

		var fileContent = getCompleteHtml(html, css, js, item, true);

		var d = new Date();
		var fileName = [
			'web-maker',
			d.getFullYear(),
			d.getMonth() + 1,
			d.getDate(),
			d.getHours(),
			d.getMinutes(),
			d.getSeconds()
		].join('-');

		if (item.title) {
			fileName = item.title;
		}
		fileName += '.html';

		var blob = new Blob([fileContent], {
			type: 'text/html;charset=UTF-8'
		});
		downloadFile(fileName, blob);

		trackEvent('fn', 'saveFileComplete');
	});
}

export function handleDownloadsPermission() {
	var d = deferred();
	if (!window.IS_EXTENSION) {
		d.resolve();
		return d.promise;
	}
	chrome.permissions.contains(
		{
			permissions: ['downloads']
		},
		function(result) {
			if (result) {
				d.resolve();
			} else {
				chrome.permissions.request(
					{
						permissions: ['downloads']
					},
					function(granted) {
						if (granted) {
							trackEvent('fn', 'downloadsPermGiven');
							d.resolve();
						} else {
							d.reject();
						}
					}
				);
			}
		}
	);
	return d.promise;
}

/**
 * Return the filename from a passed url.
 * http://a.com/path/file.png  -> file.png
 */
export function getFilenameFromUrl(url) {
	if (!url) {
		return '';
	}
	return url.match(/\/([^/]*)$/)[1];
}

export function prettify({ file, content, type }) {
	const d = deferred();
	if (file) {
		type = getExtensionFromFileName(file.name);
		content = file.content;
	}
	const worker = new Worker(
		chrome.extension
			? chrome.extension.getURL('lib/prettier-worker.js')
			: `${BASE_PATH !== '/' ? BASE_PATH : ''}/lib/prettier-worker.js`
	);
	worker.postMessage({ content, type });
	worker.addEventListener('message', e => {
		d.resolve(e.data);
		worker.terminate();
	});
	return d.promise;
}

if (window.IS_EXTENSION) {
	document.body.classList.add('is-extension');
} else {
	document.body.classList.add('is-app');
}
<commit_msg>utils: eslint fix<commit_after>import { trackEvent } from './analytics';

import { computeHtml, computeCss, computeJs } from './computes';
import { JsModes } from './codeModes';
import { deferred } from './deferred';
import { getExtensionFromFileName } from './fileUtils';
const esprima = require('esprima');

window.DEBUG = document.cookie.indexOf('wmdebug') > -1;
window.$ = document.querySelector.bind(document);

window.chrome = window.chrome || {};
window.chrome.i18n = {
	getMessage: () => {}
};

window.$all = selector => [...document.querySelectorAll(selector)];
window.IS_EXTENSION = !!window.chrome.extension;

/* eslint-disable no-process-env */
export const BASE_PATH =
	window.chrome.extension ||
	window.DEBUG ||
	process.env.NODE_ENV === 'development'
		? '/'
		: '/app';
/* eslint-enable no-process-env */

var alphaNum = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';

/**
 * The following 2 functions are supposed to find the next/previous sibling until the
 * passed `selector` is matched. But for now it actually finds the next/previous
 * element of `this` element in the list of `selector` matched element inside `this`'s
 * parent.
 * @param  Selector that should match for next siblings
 * @return element Next element that mathes `selector`
 */
Node.prototype.nextUntil = function(selector) {
	const siblings = Array.from(this.parentNode.querySelectorAll(selector));
	const index = siblings.indexOf(this);
	return siblings[index + 1];
};

/*
 * @param  Selector that should match for next siblings
 * @return element Next element that mathes `selector`
 */
Node.prototype.previousUntil = function(selector) {
	const siblings = Array.from(this.parentNode.querySelectorAll(selector));
	const index = siblings.indexOf(this);
	return siblings[index - 1];
};

// Safari doesn't have this!
window.requestIdleCallback =
	window.requestIdleCallback ||
	function(fn) {
		setTimeout(fn, 10);
	};

// https://github.com/substack/semver-compare/blob/master/index.js
export function semverCompare(a, b) {
	var pa = a.split('.');
	var pb = b.split('.');
	for (var i = 0; i < 3; i++) {
		var na = Number(pa[i]);
		var nb = Number(pb[i]);
		if (na > nb) {
			return 1;
		}
		if (nb > na) {
			return -1;
		}
		if (!isNaN(na) && isNaN(nb)) {
			return 1;
		}
		if (isNaN(na) && !isNaN(nb)) {
			return -1;
		}
	}
	return 0;
}

export function generateRandomId(len) {
	var length = len || 10;
	var id = '';
	for (var i = length; i--; ) {
		id += alphaNum[~~(Math.random() * alphaNum.length)];
	}
	return id;
}

export function onButtonClick(btn, listener) {
	btn.addEventListener('click', function buttonClickListener(e) {
		listener(e);
		return false;
	});
}

export function log() {
	if (window.DEBUG) {
		console.log(Date.now(), ...arguments);
	}
}

/**
 * Adds timed limit on the loops found in the passed code.
 * Contributed by Ariya Hidayat!
 * @param code {string}	Code to be protected from infinite loops.
 */
export function addInfiniteLoopProtection(code, { timeout }) {
	var loopId = 1;
	var patches = [];
	var varPrefix = '_wmloopvar';
	var varStr = 'var %d = Date.now();\n';
	var checkStr = `\nif (Date.now() - %d > ${timeout}) { window.top.previewException(new Error("Infinite loop")); break;}\n`;

	esprima.parse(
		code,
		{
			tolerant: true,
			range: true,
			jsx: true
		},
		function(node) {
			switch (node.type) {
				case 'DoWhileStatement':
				case 'ForStatement':
				case 'ForInStatement':
				case 'ForOfStatement':
				case 'WhileStatement':
					var start = 1 + node.body.range[0];
					var end = node.body.range[1];
					var prolog = checkStr.replace('%d', varPrefix + loopId);
					var epilog = '';

					if (node.body.type !== 'BlockStatement') {
						// `while(1) doThat()` becomes `while(1) {doThat()}`
						prolog = '{' + prolog;
						epilog = '}';
						--start;
					}

					patches.push({
						pos: start,
						str: prolog
					});
					patches.push({
						pos: end,
						str: epilog
					});
					patches.push({
						pos: node.range[0],
						str: varStr.replace('%d', varPrefix + loopId)
					});
					++loopId;
					break;

				default:
					break;
			}
		}
	);

	/* eslint-disable no-param-reassign */
	patches
		.sort(function(a, b) {
			return b.pos - a.pos;
		})
		.forEach(function(patch) {
			code = code.slice(0, patch.pos) + patch.str + code.slice(patch.pos);
		});

	/* eslint-disable no-param-reassign */
	return code;
}

export function getHumanDate(timestamp) {
	var d = new Date(timestamp);
	var retVal =
		d.getDate() +
		' ' +
		[
			'January',
			'February',
			'March',
			'April',
			'May',
			'June',
			'July',
			'August',
			'September',
			'October',
			'November',
			'December'
		][d.getMonth()] +
		' ' +
		d.getFullYear();
	return retVal;
}

// create a one-time event
export function once(node, type, callback) {
	// create event
	node.addEventListener(type, function(e) {
		// remove event
		e.target.removeEventListener(type, arguments.callee);
		// call handler
		return callback(e);
	});
}

export function downloadFile(fileName, blob) {
	function downloadWithAnchor() {
		var a = document.createElement('a');
		a.href = window.URL.createObjectURL(blob);
		a.download = fileName;
		a.style.display = 'none';
		document.body.appendChild(a);
		a.click();
		a.remove();
	}

	// HACK: because chrome.downloads isn't working on optional permissions
	// anymore.
	downloadWithAnchor();

	/* if (false && window.IS_EXTENSION) {
		chrome.downloads.download({
				url: window.URL.createObjectURL(blob),
				filename: fileName,
				saveAs: true
			},
			() => {
				// If there was an error, just download the file using ANCHOR method.
				if (chrome.runtime.lastError) {
					downloadWithAnchor();
				}
			}
		);
	} else {
		downloadWithAnchor();
	} */
}

export function writeFile(name, blob, cb) {
	var fileWritten = false;

	function getErrorHandler(type) {
		return function() {
			log(arguments);
			trackEvent('fn', 'error', type);
			// When there are too many write errors, show a message.
			writeFile.errorCount = (writeFile.errorCount || 0) + 1;
			if (writeFile.errorCount === 4) {
				setTimeout(function() {
					alert(
						"Oops! Seems like your preview isn't updating. It's recommended to switch to the web app: https://webmakerapp.com/app/.\n\n If you still want to get the extension working, please try the following steps until it fixes:\n - Refresh Web Maker\n - Restart browser\n - Update browser\n - Reinstall Web Maker (don't forget to export all your creations from saved items pane (click the OPEN button) before reinstalling)\n\nIf nothing works, please tweet out to @webmakerApp."
					);
					trackEvent('ui', 'writeFileMessageSeen');
				}, 1000);
			}
		};
	}

	// utils.log('writing file ', name);
	window.webkitRequestFileSystem(
		window.TEMPORARY,
		1024 * 1024 * 5,
		function(fs) {
			fs.root.getFile(
				name,
				{
					create: true
				},
				function(fileEntry) {
					fileEntry.createWriter(fileWriter => {
						function onWriteComplete() {
							if (fileWritten) {
								// utils.log('file written ', name);
								return cb();
							}
							fileWritten = true;
							// Set the write pointer to starting of file
							fileWriter.seek(0);
							fileWriter.write(blob);
							return false;
						}
						fileWriter.onwriteend = onWriteComplete;
						// Empty the file contents
						fileWriter.truncate(0);
						// utils.log('truncating file ', name);
					}, getErrorHandler('createWriterFail'));
				},
				getErrorHandler('getFileFail')
			);
		},
		getErrorHandler('webkitRequestFileSystemFail')
	);
}

export function loadJS(src) {
	var d = deferred();
	var ref = window.document.getElementsByTagName('script')[0];
	var script = window.document.createElement('script');
	script.src = src;
	script.async = true;
	ref.parentNode.insertBefore(script, ref);
	script.onload = function() {
		d.resolve();
	};
	return d.promise;
}

export function loadCss({ url, id }) {
	var d = deferred();
	var style = window.document.createElement('link');
	style.setAttribute('href', url);
	style.setAttribute('rel', 'stylesheet');
	if (id) {
		style.setAttribute('id', id);
	}
	document.head.appendChild(style);
	style.onload = function() {
		d.resolve();
	};
	return d.promise;
}

/* eslint-disable max-params */
export function getCompleteHtml(html, css, js, item, isForExport) {
	/* eslint-enable max-params */

	if (!item) {
		return '';
	}
	var externalJs = '',
		externalCss = '';
	if (item.externalLibs) {
		externalJs = item.externalLibs.js
			.split('\n')
			.reduce(function(scripts, url) {
				return scripts + (url ? '\n' : '');
			}, '');
		externalCss = item.externalLibs.css
			.split('\n')
			.reduce(function(links, url) {
				return (
					links +
					(url ? '\n<link rel="stylesheet" href="' + url + '"></link>' : '')
				);
			}, '');
	}
	var contents =
		'<!DOCTYPE html>\n' +
		'\n\n' +
		'<meta charset="UTF-8" />\n' +
		externalCss +
		'\n' +
		'\n' +
		'\n' +
		'\n' +
		html +
		'\n' +
		externalJs +
		'\n';

	if (!isForExport) {
		contents +=
			'';
	}

	if (item.jsMode === JsModes.ES6) {
		contents +=
			'';
	}

	if (typeof js === 'string') {
		contents += '\n\n';

	return contents;
}

export function saveAsHtml(item) {
	var htmlPromise = computeHtml(item.html, item.htmlMode);
	var cssPromise = computeCss(item.css, item.cssMode);
	var jsPromise = computeJs(item.js, item.jsMode, false);
	Promise.all([htmlPromise, cssPromise, jsPromise]).then(result => {
		var html = result[0].code,
			css = result[1].code,
			js = result[2].code;

		var fileContent = getCompleteHtml(html, css, js, item, true);

		var d = new Date();
		var fileName = [
			'web-maker',
			d.getFullYear(),
			d.getMonth() + 1,
			d.getDate(),
			d.getHours(),
			d.getMinutes(),
			d.getSeconds()
		].join('-');

		if (item.title) {
			fileName = item.title;
		}
		fileName += '.html';

		var blob = new Blob([fileContent], {
			type: 'text/html;charset=UTF-8'
		});
		downloadFile(fileName, blob);

		trackEvent('fn', 'saveFileComplete');
	});
}

export function handleDownloadsPermission() {
	var d = deferred();
	if (!window.IS_EXTENSION) {
		d.resolve();
		return d.promise;
	}
	chrome.permissions.contains(
		{
			permissions: ['downloads']
		},
		function(result) {
			if (result) {
				d.resolve();
			} else {
				chrome.permissions.request(
					{
						permissions: ['downloads']
					},
					function(granted) {
						if (granted) {
							trackEvent('fn', 'downloadsPermGiven');
							d.resolve();
						} else {
							d.reject();
						}
					}
				);
			}
		}
	);
	return d.promise;
}

/**
 * Return the filename from a passed url.
 * http://a.com/path/file.png  -> file.png
 */
export function getFilenameFromUrl(url) {
	if (!url) {
		return '';
	}
	return url.match(/\/([^/]*)$/)[1];
}

export function prettify({ file, content, type }) {
	const d = deferred();
	if (file) {
		type = getExtensionFromFileName(file.name);
		content = file.content;
	}
	const worker = new Worker(
		chrome.extension
			? chrome.extension.getURL('lib/prettier-worker.js')
			: `${BASE_PATH !== '/' ? BASE_PATH : ''}/lib/prettier-worker.js`
	);
	worker.postMessage({ content, type });
	worker.addEventListener('message', e => {
		d.resolve(e.data);
		worker.terminate();
	});
	return d.promise;
}

if (window.IS_EXTENSION) {
	document.body.classList.add('is-extension');
} else {
	document.body.classList.add('is-app');
}
<|endoftext|>


<commit_before>/*
 * == BSD2 LICENSE ==
 * Copyright (c) 2016, Tidepool Project
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the associated License, which is identical to the BSD 2-Clause
 * License as published by the Open Source Initiative at opensource.org.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the License for more details.
 *
 * You should have received a copy of the License along with this program; if
 * not, you can obtain one from Tidepool Project at tidepool.org.
 * == BSD2 LICENSE ==
 */

/* global chai */
/* global sinon */
/* global describe */
/* global it */
/* global expect */

import _ from 'lodash';

import { sentEmailVerification as reducer } from '../../../../app/redux/reducers/misc';

import * as actions from '../../../../app/redux/actions/index';

import { sentEmailVerification as initialState } from '../../../../app/redux/reducers/initialState';

var expect = chai.expect;

describe('sentEmailVerification', () => {
  describe('signupSuccess', () => {
    it('should set state to true', () => {
      let initialStateForTest = false;

      let action = actions.sync.signupSuccess();

      let state = reducer(initialStateForTest, action);

      expect(state).to.be.true;
    });
  });
});
<commit_msg>Updated sentEmailVerification reducer test<commit_after>/*
 * == BSD2 LICENSE ==
 * Copyright (c) 2016, Tidepool Project
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the associated License, which is identical to the BSD 2-Clause
 * License as published by the Open Source Initiative at opensource.org.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the License for more details.
 *
 * You should have received a copy of the License along with this program; if
 * not, you can obtain one from Tidepool Project at tidepool.org.
 * == BSD2 LICENSE ==
 */

/* global chai */
/* global sinon */
/* global describe */
/* global it */
/* global expect */

import _ from 'lodash';

import { sentEmailVerification as reducer } from '../../../../app/redux/reducers/misc';

import * as actions from '../../../../app/redux/actions/index';

import { sentEmailVerification as initialState } from '../../../../app/redux/reducers/initialState';

var expect = chai.expect;

describe('sentEmailVerification', () => {
  describe('signupSuccess', () => {
    it('should set state to the email address of the user', () => {
      const initialStateForTest = false;
      const user = {
        emails: [
          'email@address.com',
        ],
      };

      const action = actions.sync.signupSuccess(user);

      const state = reducer(initialStateForTest, action);

      expect(state).to.equal('email@address.com');
    });
  });
});
<|endoftext|>


<commit_before>/*
 * Increase version number
 *
 * grunt bump
 * grunt bump:patch
 * grunt bump:minor
 * grunt bump:major
 *
 * @author Vojta Jina <vojta.jina@gmail.com>
 * @author Mathias Paumgarten <mail@mathias-paumgarten.com>
 */
var semver = require('semver');
var exec = require('child_process').exec;

module.exports = function(grunt) {
  grunt.registerTask('bump', 'Increment the version number.', function(versionType) {
    var opts = this.options({
      files: ['package.json'],
      updateConfigs: [], // array of config properties to update (with files)
      commit: true,
      commitMessage: 'Release v${version}',
      commitFiles: ['package.json'], // '-a' for all files
      createTag: true,
      tagName: 'v${version}',
      tagMessage: 'Version ${version}',
      push: true,
      pushTo: 'origin'
    });

    var done = this.async();
    var queue = [];
    var next = function() {
      if (!queue.length) {
        return done();
      }

      queue.shift()();
    };
    var runIf = function(condition, behavior) {
      if (condition) {
        queue.push(behavior);
      }
    };

    var globalVersion; // when bumping multiple files
    var VERSION_REGEXP = /([\'|\"]version[\'|\"][ ]*:[ ]*[\'|\"])([\d|.]*)([\'|\"])/i;

    // BUMP ALL FILES
    opts.files.forEach(function(file, idx) {
      var version = null;
      var content = grunt.file.read(file).replace(VERSION_REGEXP, function(match, prefix, parsedVersion, suffix) {
        version = semver.inc(parsedVersion, versionType || 'patch');
        return prefix + version + suffix;
      });

      if (!version) {
        grunt.fatal('Can not find a version to bump in ' + file);
      }

      grunt.file.write(file, content);
      grunt.log.ok('Version bumped to ' + version + (opts.files.length > 1 ? ' (in ' + file + ')' : ''));

      if (!globalVersion) {
        globalVersion = version;
      } else if (globalVersion !== version) {
        grunt.warn('Bumping multiple files with different versions!');
      }

      var configProperty = opts.updateConfigs[idx];
      if (!configProperty) {
        return;
      }

      var cfg = grunt.config(configProperty);
      if (!cfg) {
        return grunt.warn('Can not update "' + configProperty + '" config, it does not exist!');
      }

      cfg.version = version;
      grunt.config(configProperty, cfg);
      grunt.log.ok(configProperty + '\'s version updated');
    });

    var template = grunt.util._.template;

    // COMMIT
    runIf(opts.commit, function() {
      var commitMessage = template(opts.commitMessage, {version: globalVersion});

      exec('git commit ' + opts.commitFiles.join(' ') + ' -m "' + commitMessage + '"', function(err, stdout, stderr) {
        if (err) {
          grunt.fatal('Can not create the commit:\n  ' + stderr);
        }
        grunt.log.ok('Committed as "' + commitMessage + '"');
        next();
      });
    });

    // CREATE TAG
    runIf(opts.createTag, function() {
      var tagName = template(opts.tagName, {version: globalVersion});
      var tagMessage = template(opts.tagMessage, {version: globalVersion});

      exec('git tag -a ' + tagName + ' -m "' + tagMessage + '"' , function(err, stdout, stderr) {
        if (err) {
          grunt.fatal('Can not create the tag:\n  ' + stderr);
        }
        grunt.log.ok('Tagged as "' + tagName + '"');
        next();
      });
    });

    // PUSH CHANGES
    runIf(opts.push, function() {
      exec('git push ' + opts.pushTo + ' --tags', function(err, stdout, stderr) {
        if (err) {
          grunt.fatal('Can not push to ' + opts.pushTo + ':\n  ' + stderr);
        }
        grunt.log.ok('Pushed to ' + opts.pushTo);
        next();
      });
    });

    next();
  });
};

<commit_msg>push files to remote<commit_after>/*
 * Increase version number
 *
 * grunt bump
 * grunt bump:patch
 * grunt bump:minor
 * grunt bump:major
 *
 * @author Vojta Jina <vojta.jina@gmail.com>
 * @author Mathias Paumgarten <mail@mathias-paumgarten.com>
 */
var semver = require('semver');
var exec = require('child_process').exec;

module.exports = function(grunt) {
  grunt.registerTask('bump', 'Increment the version number.', function(versionType) {
    var opts = this.options({
      files: ['package.json'],
      updateConfigs: [], // array of config properties to update (with files)
      commit: true,
      commitMessage: 'Release v${version}',
      commitFiles: ['package.json'], // '-a' for all files
      createTag: true,
      tagName: 'v${version}',
      tagMessage: 'Version ${version}',
      push: true,
      pushTo: 'origin'
    });

    var done = this.async();
    var queue = [];
    var next = function() {
      if (!queue.length) {
        return done();
      }

      queue.shift()();
    };
    var runIf = function(condition, behavior) {
      if (condition) {
        queue.push(behavior);
      }
    };

    var globalVersion; // when bumping multiple files
    var VERSION_REGEXP = /([\'|\"]version[\'|\"][ ]*:[ ]*[\'|\"])([\d|.]*)([\'|\"])/i;

    // BUMP ALL FILES
    opts.files.forEach(function(file, idx) {
      var version = null;
      var content = grunt.file.read(file).replace(VERSION_REGEXP, function(match, prefix, parsedVersion, suffix) {
        version = semver.inc(parsedVersion, versionType || 'patch');
        return prefix + version + suffix;
      });

      if (!version) {
        grunt.fatal('Can not find a version to bump in ' + file);
      }

      grunt.file.write(file, content);
      grunt.log.ok('Version bumped to ' + version + (opts.files.length > 1 ? ' (in ' + file + ')' : ''));

      if (!globalVersion) {
        globalVersion = version;
      } else if (globalVersion !== version) {
        grunt.warn('Bumping multiple files with different versions!');
      }

      var configProperty = opts.updateConfigs[idx];
      if (!configProperty) {
        return;
      }

      var cfg = grunt.config(configProperty);
      if (!cfg) {
        return grunt.warn('Can not update "' + configProperty + '" config, it does not exist!');
      }

      cfg.version = version;
      grunt.config(configProperty, cfg);
      grunt.log.ok(configProperty + '\'s version updated');
    });

    var template = grunt.util._.template;

    // COMMIT
    runIf(opts.commit, function() {
      var commitMessage = template(opts.commitMessage, {version: globalVersion});

      exec('git commit ' + opts.commitFiles.join(' ') + ' -m "' + commitMessage + '"', function(err, stdout, stderr) {
        if (err) {
          grunt.fatal('Can not create the commit:\n  ' + stderr);
        }
        grunt.log.ok('Committed as "' + commitMessage + '"');
        next();
      });
    });

    // CREATE TAG
    runIf(opts.createTag, function() {
      var tagName = template(opts.tagName, {version: globalVersion});
      var tagMessage = template(opts.tagMessage, {version: globalVersion});

      exec('git tag -a ' + tagName + ' -m "' + tagMessage + '"' , function(err, stdout, stderr) {
        if (err) {
          grunt.fatal('Can not create the tag:\n  ' + stderr);
        }
        grunt.log.ok('Tagged as "' + tagName + '"');
        next();
      });
    });

    // PUSH CHANGES
    runIf(opts.push, function() {
      exec('git push ' + opts.pushTo + ' && git push ' + opts.pushTo + ' --tags', function(err, stdout, stderr) {
        if (err) {
          grunt.fatal('Can not push to ' + opts.pushTo + ':\n  ' + stderr);
        }
        grunt.log.ok('Pushed to ' + opts.pushTo);
        next();
      });
    });

    next();
  });
};

<|endoftext|>


<commit_before>/* eslint-disable no-case-declarations */

import BasePlugin from '@appium/base-plugin';
import B from 'bluebird';

export default class FakePlugin extends BasePlugin {
  constructor (pluginName, opts = {}) {
    super(pluginName, opts);
  }

  async getFakePluginArgs () {
    await B.delay(1);
    return this.opts.pluginArgs;
  }

  static newMethodMap = {
    '/session/:sessionId/fake_data': {
      GET: {command: 'getFakeSessionData', neverProxy: true},
      POST: {command: 'setFakeSessionData', payloadParams: {required: ['data']}, neverProxy: true}
    },
    '/session/:sessionId/fakepluginargs': {
      GET: {command: 'getFakePluginArgs', neverProxy: true}
    },
  };

  static fakeRoute (req, res) {
    res.send(JSON.stringify({fake: 'fakeResponse'}));
  }

  static async updateServer (expressApp/*, httpServer*/) { // eslint-disable-line require-await
    expressApp.all('/fake', FakePlugin.fakeRoute);
  }

  async getPageSource (next, driver, ...args) {
    await B.delay(10);
    return `<Fake>${JSON.stringify(args)}</Fake>`;
  }

  async findElement (next, driver, ...args) {
    this.logger.info(`Before findElement is run with args ${JSON.stringify(args)}`);
    const originalRes = await next();
    this.logger.info(`After findElement is run`);
    originalRes.fake = true;
    return originalRes;
  }

  async getFakeSessionData (next, driver) {
    await B.delay(1);
    return driver.fakeSessionData || null;
  }

  async setFakeSessionData (next, driver, ...args) {
    await B.delay(1);
    driver.fakeSessionData = args[0];
    return null;
  }

  async getWindowHandle (next) {
    const handle = await next();
    return `<<${handle}>>`;
  }
}

export { FakePlugin };
<commit_msg>feat(fake): add unexpected shutdown handler<commit_after>/* eslint-disable no-case-declarations */

import BasePlugin from '@appium/base-plugin';
import B from 'bluebird';

export default class FakePlugin extends BasePlugin {
  constructor (pluginName, opts = {}) {
    super(pluginName, opts);
  }

  async getFakePluginArgs () {
    await B.delay(1);
    return this.opts.pluginArgs;
  }

  static newMethodMap = {
    '/session/:sessionId/fake_data': {
      GET: {command: 'getFakeSessionData', neverProxy: true},
      POST: {command: 'setFakeSessionData', payloadParams: {required: ['data']}, neverProxy: true}
    },
    '/session/:sessionId/fakepluginargs': {
      GET: {command: 'getFakePluginArgs', neverProxy: true}
    },
  };

  static _unexpectedData = null;

  static fakeRoute (req, res) {
    res.send(JSON.stringify({fake: 'fakeResponse'}));
  }

  static unexpectedData (req, res) {
    res.send(JSON.stringify(FakePlugin._unexpectedData));
    FakePlugin._unexpectedData = null;
  }

  static async updateServer (expressApp/*, httpServer*/) { // eslint-disable-line require-await
    expressApp.all('/fake', FakePlugin.fakeRoute);
    expressApp.all('/unexpected', FakePlugin.unexpectedData);
  }

  async getPageSource (next, driver, ...args) {
    await B.delay(10);
    return `<Fake>${JSON.stringify(args)}</Fake>`;
  }

  async findElement (next, driver, ...args) {
    this.logger.info(`Before findElement is run with args ${JSON.stringify(args)}`);
    const originalRes = await next();
    this.logger.info(`After findElement is run`);
    originalRes.fake = true;
    return originalRes;
  }

  async getFakeSessionData (next, driver) {
    await B.delay(1);
    return driver.fakeSessionData || null;
  }

  async setFakeSessionData (next, driver, ...args) {
    await B.delay(1);
    driver.fakeSessionData = args[0];
    return null;
  }

  async getWindowHandle (next) {
    const handle = await next();
    return `<<${handle}>>`;
  }

  onUnexpectedShutdown (driver, cause) {
    FakePlugin._unexpectedData = `Session ended because ${cause}`;
  }
}

export { FakePlugin };
<|endoftext|>


<commit_before>'use strict';

import HashSubscriber from 'javascript/api';

describe('HashSubscriber', () => {
  describe('ensureInitialization', () => {
    beforeEach(() => {
      spyOn(HashSubscriber, 'init');
    });

    it('calls init function when not initialized', () => {
      HashSubscriber.initialized = false;

      HashSubscriber.ensureInitialization();

      expect(HashSubscriber.init).toHaveBeenCalled();
    });

    it('calls init function when initialized', () => {
      HashSubscriber.initialized = true;

      HashSubscriber.ensureInitialization();

      expect(HashSubscriber.init).not.toHaveBeenCalled();
    });
  });
});
<commit_msg>adds test for HashSubscriber#init<commit_after>'use strict';

import HashSubscriber from 'javascript/api';

describe('HashSubscriber', () => {
  describe('ensureInitialization', () => {
    beforeEach(() => {
      spyOn(HashSubscriber, 'init');
    });

    it('calls init function when not initialized', () => {
      HashSubscriber.initialized = false;

      HashSubscriber.ensureInitialization();

      expect(HashSubscriber.init).toHaveBeenCalled();
    });

    it('calls init function when initialized', () => {
      HashSubscriber.initialized = true;

      HashSubscriber.ensureInitialization();

      expect(HashSubscriber.init).not.toHaveBeenCalled();
    });
  });

  describe('init', () => {
    beforeEach(() => {
      spyOn(window, 'addEventListener');
    });

    it('adds hashChangeHandler as an event handler for hashchange', () => {
      HashSubscriber.init();

      expect(window.addEventListener).toHaveBeenCalled();
    });
  });
});
<|endoftext|>


<commit_before>// Controller for recipe details page and edit form
angular.module('timecrunch').controller('RecipesCtrl', [
  '$scope',
  '$state',
  'ingredients',
  'recipe',
  'recipes',
  'auth',
  function($scope, $state, ingredients, recipe, recipes, auth){
    $scope.recipe = recipe;
    $scope.voted = (recipe.vote != null)
    // Set containing ingredient names
    let ingNames = new Set();

    $scope.metric_units = [
      "ml",
      "L",
      "mg",
      "g",
      "kg",
      "cm",
      "mm"
    ];

    $scope.imperial_units = [
      "lb",
      "oz",
      "fl.oz",
      "gal",
      "qt",
      "pt",
      "c",
      "tbsp",
      "tsp"
    ];

    // Determines if a user is allowed to edit this
    // recipe
    $scope.canEdit = function() {
      return recipe.author == auth.currentUser();
    };

    // Remove an ingredient from a recipe
    $scope.removeIngredient = function(ingredient) {
      if($scope.recipe.ingredients.length == 1) {
        $scope.recipe.ingredients = [];
        ingNames.clear();
      }
      else {
        let i = $scope.recipe.ingredients.indexOf(ingredient);
        // Remove ingredient name from set
        ingNames.delete(ingredient.name);
        $scope.recipe.ingredients.splice(i, 1);
      }
    };

    // Add an ingredient to a recipe
    $scope.addIngredient = function() {
      // Load existing ingredients into ingNames
      if(ingNames.size < $scope.recipe.ingredients.length) {
        for(i = 0; i < $scope.recipe.ingredients.length; i++) {
          ingNames.add($scope.recipe.ingredients[i].name);
        }
      }
      // If ingredient already exists show alert
      if(ingNames.has($scope.ing_name)) {
        $scope.error = {
          message: "Duplicate ingredient, please change ingredient name"
        };
        return;
      }
      // Build the ingredient string
      let ing = {
        name: $scope.ing_name,
        amount: $scope.ing_amount,
        unit: $scope.ing_unit
      };
      if(ing.name) {
        ($scope.recipe.ingredients).push(ing);
        // Add new ingredient name to the set
        ingNames.add(ing.name);
      }
      else {
        $scope.error = {
          message: "Missing ingredient name"
        };
      }
    };

    // Edit an existing recipe
    $scope.editRecipe = function() {
      for(i = 0; i < $scope.recipe.ingredients.length; i++) {
        if(!$scope.recipe.ingredients[i]._id) {
          ingredients.create($scope.recipe._id, $scope.recipe.ingredients[i]);
        }
      }
      recipes.update($scope.recipe).then(function success() {
        // Return to the recipe list
        $state.go('recipe', {id: recipe._id});
      }, function failure(error) {
        // Show error message
        $scope.error = error.data;
      });
    };

    // Upvote this recipe
    $scope.upvoteRecipe = function() {
      if(auth.isLoggedIn()) {
        if($scope.voted) {
          recipes.unvote($scope.recipe);
          $scope.voted = false;
        }
        else {
          recipes.upvote($scope.recipe);
          $scope.voted = true;
        }
      }
      else {
        $scope.error = {
          message: "You must be logged in to vote"
        };
      }
    };

    // Deletes the recipe
    $scope.deleteRecipe = function() {
      // Make sure user is authorized
      if($scope.canEdit()) {
        recipes.delete(recipe);
        $state.go('recipes');
      }
      else {
        // If not allowed show an alert
        alert('You cannot delete this recipe!');
      }
    };
}]);<commit_msg>Added imperial boolean to act as unit toggle<commit_after>// Controller for recipe details page and edit form
angular.module('timecrunch').controller('RecipesCtrl', [
  '$scope',
  '$state',
  'ingredients',
  'recipe',
  'recipes',
  'auth',
  function($scope, $state, ingredients, recipe, recipes, auth){
    $scope.recipe = recipe;
    $scope.voted = (recipe.vote != null)
    $scope.imperial = false;
    // Set containing ingredient names
    let ingNames = new Set();

    $scope.metric_units = [
      "ml",
      "L",
      "mg",
      "g",
      "kg",
      "cm",
      "mm"
    ];

    $scope.imperial_units = [
      "lb",
      "oz",
      "fl.oz",
      "gal",
      "qt",
      "pt",
      "c",
      "tbsp",
      "tsp"
    ];

    // Determines if a user is allowed to edit this
    // recipe
    $scope.canEdit = function() {
      return recipe.author == auth.currentUser();
    };

    // Remove an ingredient from a recipe
    $scope.removeIngredient = function(ingredient) {
      if($scope.recipe.ingredients.length == 1) {
        $scope.recipe.ingredients = [];
        ingNames.clear();
      }
      else {
        let i = $scope.recipe.ingredients.indexOf(ingredient);
        // Remove ingredient name from set
        ingNames.delete(ingredient.name);
        $scope.recipe.ingredients.splice(i, 1);
      }
    };

    // Add an ingredient to a recipe
    $scope.addIngredient = function() {
      // Load existing ingredients into ingNames
      if(ingNames.size < $scope.recipe.ingredients.length) {
        for(i = 0; i < $scope.recipe.ingredients.length; i++) {
          ingNames.add($scope.recipe.ingredients[i].name);
        }
      }
      // If ingredient already exists show alert
      if(ingNames.has($scope.ing_name)) {
        $scope.error = {
          message: "Duplicate ingredient, please change ingredient name"
        };
        return;
      }
      // Build the ingredient string
      let ing = {
        name: $scope.ing_name,
        amount: $scope.ing_amount,
        unit: $scope.ing_unit
      };
      if(ing.name) {
        ($scope.recipe.ingredients).push(ing);
        // Add new ingredient name to the set
        ingNames.add(ing.name);
      }
      else {
        $scope.error = {
          message: "Missing ingredient name"
        };
      }
    };

    // Edit an existing recipe
    $scope.editRecipe = function() {
      for(i = 0; i < $scope.recipe.ingredients.length; i++) {
        if(!$scope.recipe.ingredients[i]._id) {
          ingredients.create($scope.recipe._id, $scope.recipe.ingredients[i]);
        }
      }
      recipes.update($scope.recipe).then(function success() {
        // Return to the recipe list
        $state.go('recipe', {id: recipe._id});
      }, function failure(error) {
        // Show error message
        $scope.error = error.data;
      });
    };

    // Upvote this recipe
    $scope.upvoteRecipe = function() {
      if(auth.isLoggedIn()) {
        if($scope.voted) {
          recipes.unvote($scope.recipe);
          $scope.voted = false;
        }
        else {
          recipes.upvote($scope.recipe);
          $scope.voted = true;
        }
      }
      else {
        $scope.error = {
          message: "You must be logged in to vote"
        };
      }
    };

    // Deletes the recipe
    $scope.deleteRecipe = function() {
      // Make sure user is authorized
      if($scope.canEdit()) {
        recipes.delete(recipe);
        $state.go('recipes');
      }
      else {
        // If not allowed show an alert
        alert('You cannot delete this recipe!');
      }
    };
}]);<|endoftext|>


<commit_before>import computeScrollIntoView from 'compute-scroll-into-view'
import {isPreact} from './is.macro'

let idCounter = 0

/**
 * Accepts a parameter and returns it if it's a function
 * or a noop function if it's not. This allows us to
 * accept a callback, but not worry about it if it's not
 * passed.
 * @param {Function} cb the callback
 * @return {Function} a function
 */
function cbToCb(cb) {
  return typeof cb === 'function' ? cb : noop
}

function noop() {}

/**
 * Scroll node into view if necessary
 * @param {HTMLElement} node the element that should scroll into view
 * @param {HTMLElement} menuNode the menu element of the component
 */
function scrollIntoView(node, menuNode) {
  if (node === null) {
    return
  }

  const actions = computeScrollIntoView(node, {
    boundary: menuNode,
    block: 'nearest',
    scrollMode: 'if-needed',
  })
  actions.forEach(({el, top, left}) => {
    el.scrollTop = top
    el.scrollLeft = left
  })
}

/**
 * @param {HTMLElement} parent the parent node
 * @param {HTMLElement} child the child node
 * @return {Boolean} whether the parent is the child or the child is in the parent
 */
function isOrContainsNode(parent, child) {
  return parent === child || (parent.contains && parent.contains(child))
}

/**
 * Simple debounce implementation. Will call the given
 * function once after the time given has passed since
 * it was last called.
 * @param {Function} fn the function to call after the time
 * @param {Number} time the time to wait
 * @return {Function} the debounced function
 */
function debounce(fn, time) {
  let timeoutId

  function cancel() {
    if (timeoutId) {
      clearTimeout(timeoutId)
    }
  }

  function wrapper(...args) {
    cancel()
    timeoutId = setTimeout(() => {
      timeoutId = null
      fn(...args)
    }, time)
  }

  wrapper.cancel = cancel

  return wrapper
}

/**
 * This is intended to be used to compose event handlers.
 * They are executed in order until one of them sets
 * `event.preventDownshiftDefault = true`.
 * @param {...Function} fns the event handler functions
 * @return {Function} the event handler to add to an element
 */
function callAllEventHandlers(...fns) {
  return (event, ...args) =>
    fns.some(fn => {
      if (fn) {
        fn(event, ...args)
      }
      return (
        event.preventDownshiftDefault ||
        (event.hasOwnProperty('nativeEvent') &&
          event.nativeEvent.preventDownshiftDefault)
      )
    })
}

function handleRefs(...refs) {
  return node => {
    refs.forEach(ref => {
      if (typeof ref === 'function') {
        ref(node)
      } else if (ref) {
        ref.current = node
      }
    })
  }
}

/**
 * This generates a unique ID for an instance of Downshift
 * @return {String} the unique ID
 */
function generateId() {
  return String(idCounter++)
}

/**
 * This is only used in tests
 * @param {Number} num the number to set the idCounter to
 */
function setIdCounter(num) {
  idCounter = num
}

/**
 * Resets idCounter to 0. Used for SSR.
 */
function resetIdCounter() {
  idCounter = 0
}

/**
 * Default implementation for status message. Only added when menu is open.
 * Will specift if there are results in the list, and if so, how many,
 * and what keys are relevant.
 *
 * @param {Object} param the downshift state and other relevant properties
 * @return {String} the a11y status message
 */
function getA11yStatusMessage({isOpen, resultCount, previousResultCount}) {
  if (!isOpen) {
    return ''
  }

  if (!resultCount) {
    return 'No results are available.'
  }

  if (resultCount !== previousResultCount) {
    return `${resultCount} result${
      resultCount === 1 ? ' is' : 's are'
    } available, use up and down arrow keys to navigate. Press Enter key to select.`
  }

  return ''
}

/**
 * Takes an argument and if it's an array, returns the first item in the array
 * otherwise returns the argument
 * @param {*} arg the maybe-array
 * @param {*} defaultValue the value if arg is falsey not defined
 * @return {*} the arg or it's first item
 */
function unwrapArray(arg, defaultValue) {
  arg = Array.isArray(arg) ? /* istanbul ignore next (preact) */ arg[0] : arg
  if (!arg && defaultValue) {
    return defaultValue
  } else {
    return arg
  }
}

/**
 * @param {Object} element (P)react element
 * @return {Boolean} whether it's a DOM element
 */
function isDOMElement(element) {
  /* istanbul ignore if */
  if (isPreact) {
    // then this is preact or preact X
    return (
      typeof element.type === 'string' || typeof element.nodeName === 'string'
    )
  }

  // then we assume this is react
  return typeof element.type === 'string'
}

/**
 * @param {Object} element (P)react element
 * @return {Object} the props
 */
function getElementProps(element) {
  // props for react, attributes for preact

  /* istanbul ignore if */
  if (isPreact) {
    return element.props || element.attributes
  }

  return element.props
}

/**
 * Throws a helpful error message for required properties. Useful
 * to be used as a default in destructuring or object params.
 * @param {String} fnName the function name
 * @param {String} propName the prop name
 */
function requiredProp(fnName, propName) {
  // eslint-disable-next-line no-console
  console.error(`The property "${propName}" is required in "${fnName}"`)
}

const stateKeys = [
  'highlightedIndex',
  'inputValue',
  'isOpen',
  'selectedItem',
  'type',
]
/**
 * @param {Object} state the state object
 * @return {Object} state that is relevant to downshift
 */
function pickState(state = {}) {
  const result = {}
  stateKeys.forEach(k => {
    if (state.hasOwnProperty(k)) {
      result[k] = state[k]
    }
  })
  return result
}

/**
 * This will perform a shallow merge of the given state object
 * with the state coming from props
 * (for the controlled component scenario)
 * This is used in state updater functions so they're referencing
 * the right state regardless of where it comes from.
 *
 * @param {Object} state The state of the component/hook.
 * @param {Object} props The props that may contain controlled values.
 * @returns {Object} The merged controlled state.
 */
function getState(state, props) {
  return Object.keys(state).reduce((prevState, key) => {
    prevState[key] = isControlledProp(props, key) ? props[key] : state[key]

    return prevState
  }, {})
}

/**
 * This determines whether a prop is a "controlled prop" meaning it is
 * state which is controlled by the outside of this component rather
 * than within this component.
 *
 * @param {Object} props The props that may contain controlled values.
 * @param {String} key the key to check
 * @return {Boolean} whether it is a controlled controlled prop
 */
function isControlledProp(props, key) {
  return props[key] !== undefined
}

/**
 * Normalizes the 'key' property of a KeyboardEvent in IE/Edge
 * @param {Object} event a keyboardEvent object
 * @return {String} keyboard key
 */
function normalizeArrowKey(event) {
  const {key, keyCode} = event
  /* istanbul ignore next (ie) */
  if (keyCode >= 37 && keyCode <= 40 && key.indexOf('Arrow') !== 0) {
    return `Arrow${key}`
  }
  return key
}

/**
 * Simple check if the value passed is object literal
 * @param {*} obj any things
 * @return {Boolean} whether it's object literal
 */
function isPlainObject(obj) {
  return Object.prototype.toString.call(obj) === '[object Object]'
}

/**
 * Returns the new index in the list, in a circular way. If next value is out of bonds from the total,
 * it will wrap to either 0 or itemCount - 1.
 *
 * @param {number} moveAmount Number of positions to move. Negative to move backwards, positive forwards.
 * @param {number} baseIndex The initial position to move from.
 * @param {number} itemCount The total number of items.
 * @param {Function} getItemNodeFromIndex Used to check if item is disabled.
 * @param {boolean} circular Specify if navigation is circular. Default is true.
 * @returns {number} The new index after the move.
 */
function getNextWrappingIndex(
  moveAmount,
  baseIndex,
  itemCount,
  getItemNodeFromIndex,
  circular = true,
) {
  if (itemCount === 0) {
    return -1
  }

  const itemsLastIndex = itemCount - 1

  if (
    typeof baseIndex !== 'number' ||
    baseIndex < 0 ||
    baseIndex >= itemCount
  ) {
    baseIndex = moveAmount > 0 ? -1 : itemsLastIndex + 1
  }

  let newIndex = baseIndex + moveAmount

  if (newIndex < 0) {
    newIndex = circular ? itemsLastIndex : 0
  } else if (newIndex > itemsLastIndex) {
    newIndex = circular ? 0 : itemsLastIndex
  }

  const nonDisabledNewIndex = getNextNonDisabledIndex(
    moveAmount,
    newIndex,
    itemCount,
    getItemNodeFromIndex,
    circular,
  )

  return nonDisabledNewIndex === -1 ? baseIndex : nonDisabledNewIndex
}

/**
 * Returns the next index in the list of an item that is not disabled.
 *
 * @param {number} moveAmount Number of positions to move. Negative to move backwards, positive forwards.
 * @param {number} baseIndex The initial position to move from.
 * @param {number} itemCount The total number of items.
 * @param {Function} getItemNodeFromIndex Used to check if item is disabled.
 * @param {boolean} circular Specify if navigation is circular. Default is true.
 * @returns {number} The new index. Returns baseIndex if item is not disabled. Returns next non-disabled item otherwise. If no non-disabled found it will return -1.
 */
function getNextNonDisabledIndex(
  moveAmount,
  baseIndex,
  itemCount,
  getItemNodeFromIndex,
  circular,
) {
  const currentElementNode = getItemNodeFromIndex(baseIndex)
  if (!currentElementNode || !currentElementNode.hasAttribute('disabled')) {
    return baseIndex
  }

  if (moveAmount > 0) {
    for (let index = baseIndex + 1; index < itemCount; index++) {
      if (!getItemNodeFromIndex(index).hasAttribute('disabled')) {
        return index
      }
    }
  } else {
    for (let index = baseIndex - 1; index >= 0; index--) {
      if (!getItemNodeFromIndex(index).hasAttribute('disabled')) {
        return index
      }
    }
  }

  if (circular) {
    return moveAmount > 0
      ? getNextNonDisabledIndex(1, 0, itemCount, getItemNodeFromIndex, false)
      : getNextNonDisabledIndex(
          -1,
          itemCount - 1,
          itemCount,
          getItemNodeFromIndex,
          false,
        )
  }

  return -1
}

/**
 * Checks if event target is within the downshift elements.
 *
 * @param {EventTarget} target Target to check.
 * @param {HTMLElement[]} downshiftElements The elements that form downshift (list, toggle button etc).
 * @param {Document} document The document.
 * @param {boolean} checkActiveElement Whether to also check activeElement.
 *
 * @returns {boolean} Whether or not the target is within downshift elements.
 */
function targetWithinDownshift(
  target,
  downshiftElements,
  document,
  checkActiveElement = true,
) {
  return downshiftElements.some(
    contextNode =>
      contextNode &&
      (isOrContainsNode(contextNode, target) ||
        (checkActiveElement &&
          isOrContainsNode(contextNode, document.activeElement))),
  )
}

export function validateControlledUnchanged(state, prevProps, nextProps) {
  if (process.env.NODE_ENV === 'production') {
    return
  }

  const warningDescription = `This prop should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled Downshift element for the lifetime of the component. More info: https://github.com/downshift-js/downshift#control-props`

  Object.keys(state).forEach(propKey => {
    if (prevProps[propKey] !== undefined && nextProps[propKey] === undefined) {
      // eslint-disable-next-line no-console
      console.error(
        `downshift: A component has changed the controlled prop "${propKey}" to be uncontrolled. ${warningDescription}`,
      )
    } else if (
      prevProps[propKey] === undefined &&
      nextProps[propKey] !== undefined
    ) {
      // eslint-disable-next-line no-console
      console.error(
        `downshift: A component has changed the uncontrolled prop "${propKey}" to be controlled. ${warningDescription}`,
      )
    }
  })
}

export {
  cbToCb,
  callAllEventHandlers,
  handleRefs,
  debounce,
  scrollIntoView,
  generateId,
  getA11yStatusMessage,
  unwrapArray,
  isDOMElement,
  getElementProps,
  noop,
  requiredProp,
  setIdCounter,
  resetIdCounter,
  pickState,
  isPlainObject,
  normalizeArrowKey,
  getNextWrappingIndex,
  getNextNonDisabledIndex,
  targetWithinDownshift,
  getState,
  isControlledProp,
}
<commit_msg>fix(scrollIntoView): do not scroll falsy node (#1047)<commit_after>import computeScrollIntoView from 'compute-scroll-into-view'
import {isPreact} from './is.macro'

let idCounter = 0

/**
 * Accepts a parameter and returns it if it's a function
 * or a noop function if it's not. This allows us to
 * accept a callback, but not worry about it if it's not
 * passed.
 * @param {Function} cb the callback
 * @return {Function} a function
 */
function cbToCb(cb) {
  return typeof cb === 'function' ? cb : noop
}

function noop() {}

/**
 * Scroll node into view if necessary
 * @param {HTMLElement} node the element that should scroll into view
 * @param {HTMLElement} menuNode the menu element of the component
 */
function scrollIntoView(node, menuNode) {
  if (!node) {
    return
  }

  const actions = computeScrollIntoView(node, {
    boundary: menuNode,
    block: 'nearest',
    scrollMode: 'if-needed',
  })
  actions.forEach(({el, top, left}) => {
    el.scrollTop = top
    el.scrollLeft = left
  })
}

/**
 * @param {HTMLElement} parent the parent node
 * @param {HTMLElement} child the child node
 * @return {Boolean} whether the parent is the child or the child is in the parent
 */
function isOrContainsNode(parent, child) {
  return parent === child || (parent.contains && parent.contains(child))
}

/**
 * Simple debounce implementation. Will call the given
 * function once after the time given has passed since
 * it was last called.
 * @param {Function} fn the function to call after the time
 * @param {Number} time the time to wait
 * @return {Function} the debounced function
 */
function debounce(fn, time) {
  let timeoutId

  function cancel() {
    if (timeoutId) {
      clearTimeout(timeoutId)
    }
  }

  function wrapper(...args) {
    cancel()
    timeoutId = setTimeout(() => {
      timeoutId = null
      fn(...args)
    }, time)
  }

  wrapper.cancel = cancel

  return wrapper
}

/**
 * This is intended to be used to compose event handlers.
 * They are executed in order until one of them sets
 * `event.preventDownshiftDefault = true`.
 * @param {...Function} fns the event handler functions
 * @return {Function} the event handler to add to an element
 */
function callAllEventHandlers(...fns) {
  return (event, ...args) =>
    fns.some(fn => {
      if (fn) {
        fn(event, ...args)
      }
      return (
        event.preventDownshiftDefault ||
        (event.hasOwnProperty('nativeEvent') &&
          event.nativeEvent.preventDownshiftDefault)
      )
    })
}

function handleRefs(...refs) {
  return node => {
    refs.forEach(ref => {
      if (typeof ref === 'function') {
        ref(node)
      } else if (ref) {
        ref.current = node
      }
    })
  }
}

/**
 * This generates a unique ID for an instance of Downshift
 * @return {String} the unique ID
 */
function generateId() {
  return String(idCounter++)
}

/**
 * This is only used in tests
 * @param {Number} num the number to set the idCounter to
 */
function setIdCounter(num) {
  idCounter = num
}

/**
 * Resets idCounter to 0. Used for SSR.
 */
function resetIdCounter() {
  idCounter = 0
}

/**
 * Default implementation for status message. Only added when menu is open.
 * Will specift if there are results in the list, and if so, how many,
 * and what keys are relevant.
 *
 * @param {Object} param the downshift state and other relevant properties
 * @return {String} the a11y status message
 */
function getA11yStatusMessage({isOpen, resultCount, previousResultCount}) {
  if (!isOpen) {
    return ''
  }

  if (!resultCount) {
    return 'No results are available.'
  }

  if (resultCount !== previousResultCount) {
    return `${resultCount} result${
      resultCount === 1 ? ' is' : 's are'
    } available, use up and down arrow keys to navigate. Press Enter key to select.`
  }

  return ''
}

/**
 * Takes an argument and if it's an array, returns the first item in the array
 * otherwise returns the argument
 * @param {*} arg the maybe-array
 * @param {*} defaultValue the value if arg is falsey not defined
 * @return {*} the arg or it's first item
 */
function unwrapArray(arg, defaultValue) {
  arg = Array.isArray(arg) ? /* istanbul ignore next (preact) */ arg[0] : arg
  if (!arg && defaultValue) {
    return defaultValue
  } else {
    return arg
  }
}

/**
 * @param {Object} element (P)react element
 * @return {Boolean} whether it's a DOM element
 */
function isDOMElement(element) {
  /* istanbul ignore if */
  if (isPreact) {
    // then this is preact or preact X
    return (
      typeof element.type === 'string' || typeof element.nodeName === 'string'
    )
  }

  // then we assume this is react
  return typeof element.type === 'string'
}

/**
 * @param {Object} element (P)react element
 * @return {Object} the props
 */
function getElementProps(element) {
  // props for react, attributes for preact

  /* istanbul ignore if */
  if (isPreact) {
    return element.props || element.attributes
  }

  return element.props
}

/**
 * Throws a helpful error message for required properties. Useful
 * to be used as a default in destructuring or object params.
 * @param {String} fnName the function name
 * @param {String} propName the prop name
 */
function requiredProp(fnName, propName) {
  // eslint-disable-next-line no-console
  console.error(`The property "${propName}" is required in "${fnName}"`)
}

const stateKeys = [
  'highlightedIndex',
  'inputValue',
  'isOpen',
  'selectedItem',
  'type',
]
/**
 * @param {Object} state the state object
 * @return {Object} state that is relevant to downshift
 */
function pickState(state = {}) {
  const result = {}
  stateKeys.forEach(k => {
    if (state.hasOwnProperty(k)) {
      result[k] = state[k]
    }
  })
  return result
}

/**
 * This will perform a shallow merge of the given state object
 * with the state coming from props
 * (for the controlled component scenario)
 * This is used in state updater functions so they're referencing
 * the right state regardless of where it comes from.
 *
 * @param {Object} state The state of the component/hook.
 * @param {Object} props The props that may contain controlled values.
 * @returns {Object} The merged controlled state.
 */
function getState(state, props) {
  return Object.keys(state).reduce((prevState, key) => {
    prevState[key] = isControlledProp(props, key) ? props[key] : state[key]

    return prevState
  }, {})
}

/**
 * This determines whether a prop is a "controlled prop" meaning it is
 * state which is controlled by the outside of this component rather
 * than within this component.
 *
 * @param {Object} props The props that may contain controlled values.
 * @param {String} key the key to check
 * @return {Boolean} whether it is a controlled controlled prop
 */
function isControlledProp(props, key) {
  return props[key] !== undefined
}

/**
 * Normalizes the 'key' property of a KeyboardEvent in IE/Edge
 * @param {Object} event a keyboardEvent object
 * @return {String} keyboard key
 */
function normalizeArrowKey(event) {
  const {key, keyCode} = event
  /* istanbul ignore next (ie) */
  if (keyCode >= 37 && keyCode <= 40 && key.indexOf('Arrow') !== 0) {
    return `Arrow${key}`
  }
  return key
}

/**
 * Simple check if the value passed is object literal
 * @param {*} obj any things
 * @return {Boolean} whether it's object literal
 */
function isPlainObject(obj) {
  return Object.prototype.toString.call(obj) === '[object Object]'
}

/**
 * Returns the new index in the list, in a circular way. If next value is out of bonds from the total,
 * it will wrap to either 0 or itemCount - 1.
 *
 * @param {number} moveAmount Number of positions to move. Negative to move backwards, positive forwards.
 * @param {number} baseIndex The initial position to move from.
 * @param {number} itemCount The total number of items.
 * @param {Function} getItemNodeFromIndex Used to check if item is disabled.
 * @param {boolean} circular Specify if navigation is circular. Default is true.
 * @returns {number} The new index after the move.
 */
function getNextWrappingIndex(
  moveAmount,
  baseIndex,
  itemCount,
  getItemNodeFromIndex,
  circular = true,
) {
  if (itemCount === 0) {
    return -1
  }

  const itemsLastIndex = itemCount - 1

  if (
    typeof baseIndex !== 'number' ||
    baseIndex < 0 ||
    baseIndex >= itemCount
  ) {
    baseIndex = moveAmount > 0 ? -1 : itemsLastIndex + 1
  }

  let newIndex = baseIndex + moveAmount

  if (newIndex < 0) {
    newIndex = circular ? itemsLastIndex : 0
  } else if (newIndex > itemsLastIndex) {
    newIndex = circular ? 0 : itemsLastIndex
  }

  const nonDisabledNewIndex = getNextNonDisabledIndex(
    moveAmount,
    newIndex,
    itemCount,
    getItemNodeFromIndex,
    circular,
  )

  return nonDisabledNewIndex === -1 ? baseIndex : nonDisabledNewIndex
}

/**
 * Returns the next index in the list of an item that is not disabled.
 *
 * @param {number} moveAmount Number of positions to move. Negative to move backwards, positive forwards.
 * @param {number} baseIndex The initial position to move from.
 * @param {number} itemCount The total number of items.
 * @param {Function} getItemNodeFromIndex Used to check if item is disabled.
 * @param {boolean} circular Specify if navigation is circular. Default is true.
 * @returns {number} The new index. Returns baseIndex if item is not disabled. Returns next non-disabled item otherwise. If no non-disabled found it will return -1.
 */
function getNextNonDisabledIndex(
  moveAmount,
  baseIndex,
  itemCount,
  getItemNodeFromIndex,
  circular,
) {
  const currentElementNode = getItemNodeFromIndex(baseIndex)
  if (!currentElementNode || !currentElementNode.hasAttribute('disabled')) {
    return baseIndex
  }

  if (moveAmount > 0) {
    for (let index = baseIndex + 1; index < itemCount; index++) {
      if (!getItemNodeFromIndex(index).hasAttribute('disabled')) {
        return index
      }
    }
  } else {
    for (let index = baseIndex - 1; index >= 0; index--) {
      if (!getItemNodeFromIndex(index).hasAttribute('disabled')) {
        return index
      }
    }
  }

  if (circular) {
    return moveAmount > 0
      ? getNextNonDisabledIndex(1, 0, itemCount, getItemNodeFromIndex, false)
      : getNextNonDisabledIndex(
          -1,
          itemCount - 1,
          itemCount,
          getItemNodeFromIndex,
          false,
        )
  }

  return -1
}

/**
 * Checks if event target is within the downshift elements.
 *
 * @param {EventTarget} target Target to check.
 * @param {HTMLElement[]} downshiftElements The elements that form downshift (list, toggle button etc).
 * @param {Document} document The document.
 * @param {boolean} checkActiveElement Whether to also check activeElement.
 *
 * @returns {boolean} Whether or not the target is within downshift elements.
 */
function targetWithinDownshift(
  target,
  downshiftElements,
  document,
  checkActiveElement = true,
) {
  return downshiftElements.some(
    contextNode =>
      contextNode &&
      (isOrContainsNode(contextNode, target) ||
        (checkActiveElement &&
          isOrContainsNode(contextNode, document.activeElement))),
  )
}

export function validateControlledUnchanged(state, prevProps, nextProps) {
  if (process.env.NODE_ENV === 'production') {
    return
  }

  const warningDescription = `This prop should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled Downshift element for the lifetime of the component. More info: https://github.com/downshift-js/downshift#control-props`

  Object.keys(state).forEach(propKey => {
    if (prevProps[propKey] !== undefined && nextProps[propKey] === undefined) {
      // eslint-disable-next-line no-console
      console.error(
        `downshift: A component has changed the controlled prop "${propKey}" to be uncontrolled. ${warningDescription}`,
      )
    } else if (
      prevProps[propKey] === undefined &&
      nextProps[propKey] !== undefined
    ) {
      // eslint-disable-next-line no-console
      console.error(
        `downshift: A component has changed the uncontrolled prop "${propKey}" to be controlled. ${warningDescription}`,
      )
    }
  })
}

export {
  cbToCb,
  callAllEventHandlers,
  handleRefs,
  debounce,
  scrollIntoView,
  generateId,
  getA11yStatusMessage,
  unwrapArray,
  isDOMElement,
  getElementProps,
  noop,
  requiredProp,
  setIdCounter,
  resetIdCounter,
  pickState,
  isPlainObject,
  normalizeArrowKey,
  getNextWrappingIndex,
  getNextNonDisabledIndex,
  targetWithinDownshift,
  getState,
  isControlledProp,
}
<|endoftext|>