<commit_before>define([
  'jquery',
  'underscore',
  'backbone',
  'templates',
  'views/select_profile',
  'views/select_address',
  'views/select_items',
  'views/new_profile',
  'views/new_address',
  'views/new_item',
  'views/edit_item',
  'models/profile',
  'collections/profile',
  'collections/address',
  'collections/item'
], function ($, _, Backbone, JST, SelectProfileView, SelectAddressView, SelectItemsView, NewProfileView, NewAddressView, NewItemView, EditItemView, Profile, ProfileCollection, AddressCollection, ItemCollection) {
  'use strict';

  var EditResumeView = Backbone.View.extend({
    template: JST['app/scripts/templates/edit_resume.ejs'],

    tagName: 'section',

    id: function() {
      return 'edit_resume_' + this.model.id;
    },

    events: {
      'click .done_editing': 'doneEditing'
    },

    initialize: function(options) {
      this.auth = options.auth;
      this.vent = options.vent;

      if (options.item) { this.item = options.item; }

      this.listenTo(this.model, 'sync', this.resumeSaved);
      this.listenTo(this.model, 'error', this.resumeSaveError);

      this.profiles = new ProfileCollection();
      this.addresses = new AddressCollection();
      this.items = new ItemCollection();

      $.when(
        this.profiles.fetch(),
        this.addresses.fetch(),
        this.items.fetch()
      ).then(this.loaded.bind(this));
    },

    loaded: function() {
      this.initSelectProfileView();
      this.initSelectAddressView();
      this.initSelectItemsView();
      this.trigger('resume:edit:ready');
    },

    render: function() {
      this.$el.html(this.template());
      this.renderSelectProfileView();
      this.renderSelectAddressView();
      if (this.item) {
        this.showEditItem(this.item);
      } else {
        this.renderSelectItemsView();
      }

      return this;
    },

    initSelectProfileView: function() {
      this.selectProfileView = new SelectProfileView({
        collection: this.profiles
      });
      this.listenTo(this.selectProfileView, 'show:new:profile', this.showNewProfile);
    },

    initSelectAddressView: function() {
      this.selectAddressView = new SelectAddressView({
        collection: this.addresses
      });
      this.listenTo(this.selectAddressView, 'show:new:address', this.showNewAddress);
    },

    initSelectItemsView: function() {
      this.selectItemsView = new SelectItemsView({
        collection: this.items,
        resume: this.model,
        auth: this.auth,
        vent: this.vent
      });
      this.listenTo(this.selectItemsView, 'show:new:item', this.showNewItem);
      this.listenTo(this.selectItemsView, 'edit:item:show', this.showEditItem);
    },

    setSelectedProfileId: function(profileId) {
      this.selectProfileView.setSelectedProfile(profileId);
    },

    setSelectedAddressId: function(addressId) {
      this.selectAddressView.setSelectedAddress(addressId);
    },

    getSelectedProfileId: function() {
      return this.selectProfileView.getSelectedProfileId();
    },

    getSelectedAddressId: function() {
      return this.selectAddressView.getSelectedAddressId();
    },

    getSelectedItemIds: function() {
      return this.selectItemsView.getSelectedItemIds();
    },

    renderSelectProfileView: function() {
      this.$('#profile').html(this.selectProfileView.render().el);
      if (this.model.get('profile')) {
        this.setSelectedProfileId(this.model.get('profile').id);
      }
    },

    renderSelectAddressView: function() {
      this.$('#address').html(this.selectAddressView.render().el);
      if (this.model.get('address')) {
        this.setSelectedAddressId(this.model.get('address').id);
      }
    },

    renderSelectItemsView: function() {
      this.$('#items').html(this.selectItemsView.render().el);
    },

    showNewProfile: function() {
      var newProfileView = new NewProfileView({
        resume: this.model,
        collection: this.profiles,
        auth: this.auth
      });
      this.listenTo(newProfileView, 'profile:new:saved', this.newProfileSaved);
      this.listenTo(newProfileView, 'profile:new:cancel', this.cancelNewProfile);
      this.$('#profile').html(newProfileView.render().el);
    },

    showNewAddress: function() {
      var newAddressView = new NewAddressView({
        resume: this.model,
        collection: this.addresses,
        auth: this.auth
      });
      this.listenTo(newAddressView, 'address:new:saved', this.newAddressSaved);
      this.listenTo(newAddressView, 'address:new:cancel', this.cancelNewAddress);
      this.$('#address').html(newAddressView.render().el);
    },

    showNewItem: function() {
      var newItemView = new NewItemView({
        resume: this.model,
        collection: this.items,
        auth: this.auth
      });
      this.listenTo(newItemView, 'item:new:saved', this.newItemSaved);
      this.listenTo(newItemView, 'item:new:cancel', this.cancelItem);
      this.$('#items').html(newItemView.render().el);
    },

    showEditItem: function(item) {
      Backbone.history.navigate('resumes/' + this.model.id + '/items/' + item.id + '/edit');
      this.listenToOnce(item, 'item:loaded', function() {
        var editItemView = new EditItemView({
          model: item,
          resume: this.model,
          vent: this.vent,
          auth: this.auth
        });
        this.listenTo(editItemView, 'item:edit:cancel', this.cancelItem);
        this.listenTo(editItemView, 'item:updated', this.itemUpdated);
        this.listenToOnce(editItemView, 'item:edit:ready', function() {
          this.$('#items').html(editItemView.render().el);
        });
      });
      item.load();
    },

    doneEditing: function(e) {
      e.preventDefault();
      // name
      var newName = this.$('input#resume_name').val();
      this.model.set('name', newName);
      // profile
      var profileId = this.getSelectedProfileId();
      var profile = this.profiles.get(profileId);
      if (profile) {
        this.model.set('profile', profile);
      }
      // address
      var addressId = this.getSelectedAddressId();
      var address = this.addresses.get(addressId);
      if (address) {
        this.model.set('address', address);
      }
      // items
      var itemIds = this.getSelectedItemIds();
      var items = _.map(itemIds, function(itemId) {
        return this.items.get(itemId);
      }, this);
      this.model.set('items', items);

      // header
      if (this.auth) {
        var header = { headers: { 'X-Toke-Key': this.auth.token.get('key') }};
        this.model.save({}, header);
      } else {
        console.log('Not Authorized');
      }
    },

    itemUpdated: function() {
      this.cancelItem();
    },

    resumeSaved: function() {
      this.trigger('show:resume', this.model.id);
    },

    resumeSaveError: function(model, xhr, options) {
      var errors = this.formatErrors(xhr.responseText);
      var errorsEl = this.$('#errors').empty();
      _.each(errors, function(element, index, list) {
        var er = $('<p>').text(element);
        errorsEl.append(er);
      }, this);
    },

    newProfileSaved: function(profile) {
      this.initSelectProfileView();
      this.renderSelectProfileView();
      this.setSelectedProfileId(profile.id);
    },

    cancelNewProfile: function() {
      this.initSelectProfileView();
      this.renderSelectProfileView();
    },

    newAddressSaved: function(address) {
      this.initSelectAddressView();
      this.renderSelectAddressView();
      this.setSelectedAddressId(address.id);
    },

    cancelNewAddress: function() {
      this.initSelectAddressView();
      this.renderSelectAddressView();
    },

    newItemSaved: function(address) {
      this.initSelectItemsView();
      this.renderSelectItemsView();
    },

    cancelItem: function() {
      this.initSelectItemsView();
      this.renderSelectItemsView();
    },

    formatErrors: function(errorText) {
      var error = JSON.parse(errorText);
      var errors = [];
      _.each(error, function(value, key, obj) {
        _.each(value, function(element, index, list) {
          var e = this.capitaliseFirst(key) + ' ' + element + '.';
          errors.push(e);
        }, this);
      }, this);
      return errors;
    },

    capitaliseFirst: function(str) {
      return str.charAt(0).toUpperCase() + str.slice(1);
    }
  });

  return EditResumeView;
});
<commit_msg>No need to load the item; already loaded.<commit_after>define([
  'jquery',
  'underscore',
  'backbone',
  'templates',
  'views/select_profile',
  'views/select_address',
  'views/select_items',
  'views/new_profile',
  'views/new_address',
  'views/new_item',
  'views/edit_item',
  'models/profile',
  'collections/profile',
  'collections/address',
  'collections/item'
], function ($, _, Backbone, JST, SelectProfileView, SelectAddressView, SelectItemsView, NewProfileView, NewAddressView, NewItemView, EditItemView, Profile, ProfileCollection, AddressCollection, ItemCollection) {
  'use strict';

  var EditResumeView = Backbone.View.extend({
    template: JST['app/scripts/templates/edit_resume.ejs'],

    tagName: 'section',

    id: function() {
      return 'edit_resume_' + this.model.id;
    },

    events: {
      'click .done_editing': 'doneEditing'
    },

    initialize: function(options) {
      this.auth = options.auth;
      this.vent = options.vent;

      if (options.item) { this.item = options.item; }

      this.listenTo(this.model, 'sync', this.resumeSaved);
      this.listenTo(this.model, 'error', this.resumeSaveError);

      this.profiles = new ProfileCollection();
      this.addresses = new AddressCollection();
      this.items = new ItemCollection();

      $.when(
        this.profiles.fetch(),
        this.addresses.fetch(),
        this.items.fetch()
      ).then(this.loaded.bind(this));
    },

    loaded: function() {
      this.initSelectProfileView();
      this.initSelectAddressView();
      this.initSelectItemsView();
      this.trigger('resume:edit:ready');
    },

    render: function() {
      this.$el.html(this.template());
      this.renderSelectProfileView();
      this.renderSelectAddressView();
      if (this.item) {
        this.showEditItem(this.item);
      } else {
        this.renderSelectItemsView();
      }

      return this;
    },

    initSelectProfileView: function() {
      this.selectProfileView = new SelectProfileView({
        collection: this.profiles
      });
      this.listenTo(this.selectProfileView, 'show:new:profile', this.showNewProfile);
    },

    initSelectAddressView: function() {
      this.selectAddressView = new SelectAddressView({
        collection: this.addresses
      });
      this.listenTo(this.selectAddressView, 'show:new:address', this.showNewAddress);
    },

    initSelectItemsView: function() {
      this.selectItemsView = new SelectItemsView({
        collection: this.items,
        resume: this.model,
        auth: this.auth,
        vent: this.vent
      });
      this.listenTo(this.selectItemsView, 'show:new:item', this.showNewItem);
      this.listenTo(this.selectItemsView, 'edit:item:show', this.showEditItem);
    },

    setSelectedProfileId: function(profileId) {
      this.selectProfileView.setSelectedProfile(profileId);
    },

    setSelectedAddressId: function(addressId) {
      this.selectAddressView.setSelectedAddress(addressId);
    },

    getSelectedProfileId: function() {
      return this.selectProfileView.getSelectedProfileId();
    },

    getSelectedAddressId: function() {
      return this.selectAddressView.getSelectedAddressId();
    },

    getSelectedItemIds: function() {
      return this.selectItemsView.getSelectedItemIds();
    },

    renderSelectProfileView: function() {
      this.$('#profile').html(this.selectProfileView.render().el);
      if (this.model.get('profile')) {
        this.setSelectedProfileId(this.model.get('profile').id);
      }
    },

    renderSelectAddressView: function() {
      this.$('#address').html(this.selectAddressView.render().el);
      if (this.model.get('address')) {
        this.setSelectedAddressId(this.model.get('address').id);
      }
    },

    renderSelectItemsView: function() {
      this.$('#items').html(this.selectItemsView.render().el);
    },

    showNewProfile: function() {
      var newProfileView = new NewProfileView({
        resume: this.model,
        collection: this.profiles,
        auth: this.auth
      });
      this.listenTo(newProfileView, 'profile:new:saved', this.newProfileSaved);
      this.listenTo(newProfileView, 'profile:new:cancel', this.cancelNewProfile);
      this.$('#profile').html(newProfileView.render().el);
    },

    showNewAddress: function() {
      var newAddressView = new NewAddressView({
        resume: this.model,
        collection: this.addresses,
        auth: this.auth
      });
      this.listenTo(newAddressView, 'address:new:saved', this.newAddressSaved);
      this.listenTo(newAddressView, 'address:new:cancel', this.cancelNewAddress);
      this.$('#address').html(newAddressView.render().el);
    },

    showNewItem: function() {
      var newItemView = new NewItemView({
        resume: this.model,
        collection: this.items,
        auth: this.auth
      });
      this.listenTo(newItemView, 'item:new:saved', this.newItemSaved);
      this.listenTo(newItemView, 'item:new:cancel', this.cancelItem);
      this.$('#items').html(newItemView.render().el);
    },

    showEditItem: function(item) {
      Backbone.history.navigate('resumes/' + this.model.id + '/items/' + item.id + '/edit');
      var editItemView = new EditItemView({
        model: item,
        resume: this.model,
        vent: this.vent,
        auth: this.auth
      });
      this.listenTo(editItemView, 'item:edit:cancel', this.cancelItem);
      this.listenTo(editItemView, 'item:updated', this.itemUpdated);
      this.listenToOnce(editItemView, 'item:edit:ready', function() {
        this.$('#items').html(editItemView.render().el);
      });
    },

    doneEditing: function(e) {
      e.preventDefault();
      // name
      var newName = this.$('input#resume_name').val();
      this.model.set('name', newName);
      // profile
      var profileId = this.getSelectedProfileId();
      var profile = this.profiles.get(profileId);
      if (profile) {
        this.model.set('profile', profile);
      }
      // address
      var addressId = this.getSelectedAddressId();
      var address = this.addresses.get(addressId);
      if (address) {
        this.model.set('address', address);
      }
      // items
      var itemIds = this.getSelectedItemIds();
      var items = _.map(itemIds, function(itemId) {
        return this.items.get(itemId);
      }, this);
      this.model.set('items', items);

      // header
      if (this.auth) {
        var header = { headers: { 'X-Toke-Key': this.auth.token.get('key') }};
        this.model.save({}, header);
      } else {
        console.log('Not Authorized');
      }
    },

    itemUpdated: function() {
      this.cancelItem();
    },

    resumeSaved: function() {
      this.trigger('show:resume', this.model.id);
    },

    resumeSaveError: function(model, xhr, options) {
      var errors = this.formatErrors(xhr.responseText);
      var errorsEl = this.$('#errors').empty();
      _.each(errors, function(element, index, list) {
        var er = $('<p>').text(element);
        errorsEl.append(er);
      }, this);
    },

    newProfileSaved: function(profile) {
      this.initSelectProfileView();
      this.renderSelectProfileView();
      this.setSelectedProfileId(profile.id);
    },

    cancelNewProfile: function() {
      this.initSelectProfileView();
      this.renderSelectProfileView();
    },

    newAddressSaved: function(address) {
      this.initSelectAddressView();
      this.renderSelectAddressView();
      this.setSelectedAddressId(address.id);
    },

    cancelNewAddress: function() {
      this.initSelectAddressView();
      this.renderSelectAddressView();
    },

    newItemSaved: function(address) {
      this.initSelectItemsView();
      this.renderSelectItemsView();
    },

    cancelItem: function() {
      this.initSelectItemsView();
      this.renderSelectItemsView();
    },

    formatErrors: function(errorText) {
      var error = JSON.parse(errorText);
      var errors = [];
      _.each(error, function(value, key, obj) {
        _.each(value, function(element, index, list) {
          var e = this.capitaliseFirst(key) + ' ' + element + '.';
          errors.push(e);
        }, this);
      }, this);
      return errors;
    },

    capitaliseFirst: function(str) {
      return str.charAt(0).toUpperCase() + str.slice(1);
    }
  });

  return EditResumeView;
});
<|endoftext|>


<commit_before>/*
 * Copyright 2015 Amadeus s.a.s.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

Aria.classDefinition({
    $classpath : "test.aria.widgets.wai.datePicker.DatePickerJawsTest1",
    $extends : "aria.jsunit.JawsTestCase",
    $constructor : function () {
        this.$JawsTestCase.constructor.call(this);
        this.setTestEnv({
            template : "test.aria.widgets.wai.datePicker.DatePickerTestTpl",
            data : {
                dpWaiEnabledValue : new Date(2016, 0, 1)
            }
        });
    },
    $prototype : {
        runTemplateTest : function () {
            this.synEvent.execute([
                ["click", this.getElementById("dpWaiEnabledNextInput")],
                ["pause", 1000],
                ["type", null, "[up][up][up]"],
                ["pause", 1000],
                ["type", null, "[space]"],
                ["pause", 1000],
                ["type", null, "[down]"],
                ["pause", 1000],
                ["type", null, "[down]"],
                ["pause", 1000],
                ["type", null, "[space]"],
                ["pause", 1000]
            ], {
                fn: function () {
                    this.assertJawsHistoryEquals("Next field Edit\nType in text.\nNext field\nDisplay calendar button menu collapsed\nEntering Application Region\nCalendar table. Use arrow keys to navigate and space to validate.\nFriday 1 January 2016\nFriday 8 January 2016\nFriday 15 January 2016\nLeaving Application Region\nTravel date Edit\n15/1/16\nType in text.", this.end);
                },
                scope: this
            });
        }
    }
});
<commit_msg>fix #1616 Fixing DatePickerJawsTest1 with Jaws 16<commit_after>/*
 * Copyright 2015 Amadeus s.a.s.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

Aria.classDefinition({
    $classpath : "test.aria.widgets.wai.datePicker.DatePickerJawsTest1",
    $extends : "aria.jsunit.JawsTestCase",
    $constructor : function () {
        this.$JawsTestCase.constructor.call(this);
        this.setTestEnv({
            template : "test.aria.widgets.wai.datePicker.DatePickerTestTpl",
            data : {
                dpWaiEnabledValue : new Date(2016, 0, 1)
            }
        });
    },
    $prototype : {
        runTemplateTest : function () {
            this.synEvent.execute([
                ["click", this.getElementById("dpWaiEnabledNextInput")],
                ["pause", 1000],
                ["type", null, "[up][up][up]"],
                ["pause", 1000],
                ["type", null, "[space]"],
                ["pause", 1000],
                ["type", null, "[down]"],
                ["pause", 1000],
                ["type", null, "[down]"],
                ["pause", 1000],
                ["type", null, "[space]"],
                ["pause", 1000]
            ], {
                fn: function () {
                    this.assertJawsHistoryEquals("Next field Edit\nType in text.\nNext field\nDisplay calendar button menu collapsed\nCalendar table. Use arrow keys to navigate and space to validate.\nFriday 1 January 2016\nFriday 8 January 2016\nFriday 15 January 2016\nTravel date Edit\n15/1/16\nType in text.", this.end);
                },
                scope: this
            });
        }
    }
});
<|endoftext|>


<commit_before>
this.CircularList = (function () {
	'use strict';

	/**
	 * @constructor
	 */
	function CircularList() {

		/**
		 * The length of the linked list
		 *
		 * @type number
		 */
		this.length = 0;

		/**
		 * The first item in the linked list
		 *
		 * @type Object
		 */
		this.first = null;

		/**
		 * The last item in the linked list
		 *
		 * @type Object
		 */
		this.last = null;
	}

	/**
	 * Explicit item object to allow items to belong to more than linked list at a time
	 *
	 * To hold a reference to a CircularList.Item within a completely different CircularList the CircularList.Item should be passed as the data to a new CircularList.Item to be used in the new CircularList.
	 * If you don't wrap the reference in a new Item, then if you append an already existing reference to a different CircularList the behaviour is undefined.
	 *
	 * @example
	 * myList.append(new CircularList.Item(someObject));
	 *
	 * @constructor
	 * @param {Object} data
	 */
	CircularList.Item = function (data) {
		this.prev = null;
		this.next = null;
		this.list = null;
		this.data = data;
	};

	/**
	 * Append an object to the linked list
	 *
	 * @param {Object} item The item to append
	 */
	CircularList.prototype.append = function (item) {
		if (this.first === null) {
			item.prev = item;
			item.next = item;
			this.first = item;
			this.last = item;
		} else {
			item.prev = this.last;
			item.next = this.first;
			this.first.prev = item;
			this.last.next = item;
			this.last = item;
		}

		item.list = this;
		this.length++;
	};

	/**
	 * Remove an item from the linked list
	 *
	 * @param {Object} item The item to remove
	 */
	CircularList.prototype.remove = function (item) {

		// Exit early if the item isn't in the list
		if (!this.length || this !== item.list) {
			return;
		}

		if (this.length > 1) {
			item.prev.next = item.next;
			item.next.prev = item.prev;

			if (item === this.first) {
				this.first = item.next;
			}

			if (item === this.last) {
				this.last = item.prev;
			}
		} else {
			this.first = null;
			this.last = null;
		}

		item.prev = null;
		item.next = null;
		this.length--;
	};

	/**
	 * Convert the linked list to an Array
	 *
	 * The first item in the list is the first item in the array.
	 *
	 * @return {Array}
	 */
	CircularList.prototype.toArray = function () {
		var i, item, array, length = this.length;

		array = new Array(length);
		item = this.first;

		for (i = 0; i < length; i++) {
			array[i] = item;
			item = item.next;
		}

		return array;
	};

	/**
	 * Insert an item after one already in the linked list
	 *
	 * @param {Object} item The reference item
	 * @param {Object} newItem The item to insert
	 */
	CircularList.prototype.insertAfter = function (item, newItem) {
		newItem.prev = item;
		newItem.next = item.next;
		item.next.prev = newItem;
		item.next = newItem;

		if (newItem.prev === this.last) {
			this.last = newItem;
		}

		newItem.list = this;
		this.length++;
	};

	return CircularList;

}());

/**
 * Create a DOM event delegator
 *
 * @fileOverview
 * @codingstandard ftlabs-jslint
 * @copyright The Financial Times Limited [All Rights Reserved]
 */

this.Delegate = (function(that) {
	"use strict";

	var

		/**
		 * Event listener separator
		 *
		 * @private
		 * @type string
		 */
		SEPARATOR = ' ',

		/**
		 * Event object property used to signal that event should be ignored by handler
		 *
		 * @private
		 * @type string
		 */
		EVENT_IGNORE = 'ftLabsDelegateIgnore',

		/**
		 * Circular list constructor
		 *
		 * @private
		 * @type function()
		 */
		CircularList = that.CircularList,

		/**
		 * Whether tag names are case sensitive (as in XML or XHTML documents)
		 *
		 * @type boolean
		 */
		tagsCaseSensitive = document.createElement('i').tagName === 'i',

		/**
		 * Check whether an element matches a generic selector
		 *
		 * @private
		 * @type function()
		 * @param {string} selector A CSS selector
		 */
		matches = (function(p) {
			return (p.matchesSelector || p.webkitMatchesSelector || p.mozMatchesSelector || p.msMatchesSelector || p.oMatchesSelector);
		}(HTMLElement.prototype)),

		/**
		 * Check whether an element matches a tag selector
		 *
		 * Tags are NOT case-sensitive, except in XML (and XML-based languages such as XHTML).
		 *
		 * @private
		 * @param {string} tagName The tag name to test against
		 * @param {Element} element The element to test with
		 */
		matchesTag = function(tagName, element) {
			return tagName === element.tagName;
		},

		/**
		 * Check whether the ID of the element in 'this' matches the given ID
		 *
		 * IDs are case-sensitive.
		 *
		 * @private
		 * @param {string} id The ID to test against
		 * @param {Element} element The element to test with
		 */
		matchesId = function(id, element) {
			return id === element.id;
		};

	/**
	 * Fire a listener on a target
	 *
	 * @private
	 * @param {Event} event
	 * @param {Node} target
	 * @param {Object} listener
	 */
	function fire(event, target, listener) {
		var returned, oldData;

		if (listener.d !== null) {
			oldData = event.data;
			event.data = listener.d;
			returned = listener.h.call(target, event, target);
			event.data = oldData;
		} else {
			returned = listener.h.call(target, event, target);
		}

		return returned;
	}

	/**
	 * Internal function proxied by Delegate#on
	 *
	 * @private
	 * @param {Object} lisenerList
	 * @param {Node|DOMWindow} root
	 * @param {Event} event
	 */
	function handle(listenerList, root, event) {
		var listener, returned, specificList, target;

		if (event[EVENT_IGNORE] === true) {
			return;
		}

		target = event.target;
		if (target.nodeType === Node.TEXT_NODE) {
			target = target.parentNode;
		}
		specificList = listenerList[event.type];

		while (target) {
			listener = specificList.first;
			do {

				// Check for match and fire the event if there's one
				// TODO:MCG:20120117: Need a way to check if event#stopImmediateProgagation was called. If so, break both loops.
				if (listener.m.call(target, listener.p, target)) {
					returned = fire(event, target, listener);
				}

				// Stop propagation to subsequent callbacks if the callback returned false
				if (returned === false) {
					event[EVENT_IGNORE] = true;
					return;
				}

				listener = listener.next;
			} while (listener !== specificList.first);

			// TODO:MCG:20120117: Need a way to check if event#stopProgagation was called. If so, break looping through the DOM.
			// Stop if the delegation root has been reached
			if (target === root) {
				break;
			}

			target = target.parentElement;
		}
	}

	/**
	 * Internal function proxied by Delegate#on
	 *
	 * @private
	 * @param {Delegate} that
	 * @param {Object} listenerList
	 * @param {Node|DOMWindow} root
	 */
	function on(that, listenerList, root, eventType, selector, eventData, handler) {
		var matcher, matcherParam;

		if (!eventType) {
			throw new TypeError('Invalid event type: ' + eventType);
		}

		if (!selector) {
			throw new TypeError('Invalid selector: ' + selector);
		}

		// Support a separated list of event types
		if (eventType.indexOf(SEPARATOR) !== -1) {
			eventType.split(SEPARATOR).forEach(function(eventType) {
				on.call(that, that, listenerList, root, eventType, selector, eventData, handler);
			});

			return;
		}

		if (handler === undefined) {
			handler = eventData;
			eventData = null;

		// Normalise undefined eventData to null
		} else if (eventData === undefined) {
			eventData = null;
		}

		if (typeof handler !== 'function') {
			throw new TypeError("Handler must be a type of Function");
		}

		// Add master handler for type if not created yet
		if (!listenerList[eventType]) {
			root.addEventListener(eventType, that.handle, (eventType === 'error'));
			listenerList[eventType] = new CircularList();
		}

		// Compile a matcher for the given selector
		if (/^[a-z]+$/i.test(selector)) {
			if (!tagsCaseSensitive) {
				matcherParam = selector.toUpperCase();
			} else {
				matcherParam = selector;
			}

			matcher = matchesTag;
		} else if (/^#[a-z0-9\-_]+$/i.test(selector)) {
			matcherParam = selector.slice(1);
			matcher = matchesId;
		} else {
			matcherParam = selector;
			matcher = matches;
		}

		// Add to the list of listeners
		listenerList[eventType].append({
			s: selector,
			d: eventData,
			h: handler,
			m: matcher,
			p: matcherParam
		});
	}

	/**
	 * Internal function proxied by Delegate#off
	 *
	 * @private
	 * @param {Delegate} that
	 * @param {Object} listenerList
	 * @param {Node|DOMWindow} root
	 */
	function off(that, listenerList, root, eventType, selector, handler) {
		var listener, nextListener, firstListener, specificList, singleEventType;

		if (!eventType) {
			for (singleEventType in listenerList) {
				if (listenerList.hasOwnProperty(singleEventType)) {
					off.call(that, that, listenerList, root, singleEventType, selector, handler);
				}
			}
			return;
		}
		specificList = listenerList[eventType];

		if (!specificList) {
			return;
		}

		// Support a separated list of event types
		if (eventType.indexOf(SEPARATOR) !== -1) {
			eventType.split(SEPARATOR).forEach(function(eventType) {
				off.call(that, that, listenerList, root, eventType, selector, handler);
			});
			return;
		}

		// Remove only parameter matches if specified
		listener = firstListener = specificList.first;
		do {
			if ((!selector || selector === listener.s) && (!handler || handler === listener.h)) {

				// listener.next will be undefined after listener is removed, so save a reference here
				nextListener = listener.next;
				specificList.remove(listener);
				listener = nextListener;
			} else {
				listener = listener.next;
			}
		} while (listener && listener !== firstListener);

		// All listeners removed
		if (!specificList.length) {
			delete listenerList[eventType];

			// Remove the main handler
			root.removeEventListener(eventType, that.handle, false);
		}
	}

	/**
	 * DOM event delegator
	 *
	 * The delegator will listen for events that bubble up to the root node.
	 *
	 * @constructor
	 * @param {Node|DOMWindow|string} root The root node, a window object or a selector string
	 */
	function Delegate(root) {
		var

			/**
			 * Keep a reference to the current instance
			 *
			 * @internal
			 * @type Delegate
			 */
			that = this,

			/**
			 * Maintain a list of listeners, indexed by event name
			 *
			 * @internal
			 * @type Object
			 */
			listenerList = {};

		if (typeof root === 'string') {
			root = document.querySelector(root);
		}

		if (!root || !root.addEventListener) {
			throw new TypeError('Root node not specified');
		}

		/**
		 * Attach a handler to one event for all elements that match the selector, now or in the future
		 *
		 * The handler function receives three arguments: the DOM event object, the node that matched the selector while the event was bubbling
		 * and a reference to itself. Within the handler, 'this' is equal to the second argument.
		 * The node that actually received the event can be accessed via 'event.target'.
		 *
		 * @param {string} eventType Listen for these events (in a space-separated list)
		 * @param {string} selector Only handle events on elements matching this selector
		 * @param {Object} [eventData] If this parameter is not specified, the third parameter must be the handler
		 * @param {function()} handler Handler function - event data passed here will be in event.data
		 * @returns {Delegate} This method is chainable
		 */
		this.on = function() {
			Array.prototype.unshift.call(arguments, that, listenerList, root);
			on.apply(that, arguments);
			return this;
		};

		/**
		 * Remove an event handler for elements that match the selector, forever
		 *
		 * @param {string} eventType Remove handlers for events matching this type, considering the other parameters
		 * @param {string} [selector] If this parameter is omitted, only handlers which match the other two will be removed
		 * @param {function()} [handler] If this parameter is omitted, only handlers which match the previous two will be removed
		 * @returns {Delegate} This method is chainable
		 */
		this.off = function() {
			Array.prototype.unshift.call(arguments, that, listenerList, root);
			off.apply(that, arguments);
			return this;
		};

		/**
		 * Handle an arbitrary event
		 *
		 * @private
		 * @param {Event} event
		 */
		this.handle = function(event) {
			handle.call(that, listenerList, root, event);
		};
	}

	return Delegate;

}(this));<commit_msg>- Update Delegate to latest version<commit_after>/**
 * Create a circularly-linked list
 *
 * Adapted from original version by James Coglan.
 *
 * @fileOverview
 * @codingstandard ftlabs-jsv2
 * @copyright The Financial Times Limited [All Rights Reserved]
 */

this.CircularList = (function () {
	'use strict';

	/**
	 * @constructor
	 */
	function CircularList() {

		/**
		 * The length of the linked list
		 *
		 * @type number
		 */
		this.length = 0;

		/**
		 * The first item in the linked list
		 *
		 * @type Object
		 */
		this.first = null;

		/**
		 * The last item in the linked list
		 *
		 * @type Object
		 */
		this.last = null;
	}

	/**
	 * Explicit item object to allow items to belong to more than linked list at a time
	 *
	 * To hold a reference to a CircularList.Item within a completely different CircularList the CircularList.Item should be passed as the data to a new CircularList.Item to be used in the new CircularList.
	 * If you don't wrap the reference in a new Item, then if you append an already existing reference to a different CircularList the behaviour is undefined.
	 *
	 * @example
	 * myList.append(new CircularList.Item(someObject));
	 *
	 * @constructor
	 * @param {Object} data
	 */
	CircularList.Item = function (data) {
		this.prev = null;
		this.next = null;
		this.list = null;
		this.data = data;
	};

	/**
	 * Append an object to the linked list
	 *
	 * @param {Object} item The item to append
	 */
	CircularList.prototype.append = function (item) {
		if (this.first === null) {
			item.prev = item;
			item.next = item;
			this.first = item;
			this.last = item;
		} else {
			item.prev = this.last;
			item.next = this.first;
			this.first.prev = item;
			this.last.next = item;
			this.last = item;
		}

		item.list = this;
		this.length++;
	};

	/**
	 * Remove an item from the linked list
	 *
	 * @param {Object} item The item to remove
	 */
	CircularList.prototype.remove = function (item) {

		// Exit early if the item isn't in the list
		if (!this.length || this !== item.list) {
			return;
		}

		if (this.length > 1) {
			item.prev.next = item.next;
			item.next.prev = item.prev;

			if (item === this.first) {
				this.first = item.next;
			}

			if (item === this.last) {
				this.last = item.prev;
			}
		} else {
			this.first = null;
			this.last = null;
		}

		item.prev = null;
		item.next = null;
		this.length--;
	};

	/**
	 * Convert the linked list to an Array
	 *
	 * The first item in the list is the first item in the array.
	 *
	 * @return {Array}
	 */
	CircularList.prototype.toArray = function () {
		var i, item, array, length = this.length;

		array = new Array(length);
		item = this.first;

		for (i = 0; i < length; i++) {
			array[i] = item;
			item = item.next;
		}

		return array;
	};

	/**
	 * Insert an item after one already in the linked list
	 *
	 * @param {Object} item The reference item
	 * @param {Object} newItem The item to insert
	 */
	CircularList.prototype.insertAfter = function (item, newItem) {
		newItem.prev = item;
		newItem.next = item.next;
		item.next.prev = newItem;
		item.next = newItem;

		if (newItem.prev === this.last) {
			this.last = newItem;
		}

		newItem.list = this;
		this.length++;
	};

	return CircularList;

}());

/**
 * Create a DOM event delegator
 *
 * @fileOverview
 * @codingstandard ftlabs-jsv2
 * @copyright The Financial Times Limited [All Rights Reserved]
 */

this.Delegate = (function(that) {
	"use strict";

	var

		/**
		 * Event listener separator
		 *
		 * @private
		 * @type string
		 */
		SEPARATOR = ' ',

		/**
		 * Event object property used to signal that event should be ignored by handler
		 *
		 * @private
		 * @type string
		 */
		EVENT_IGNORE = 'ftLabsDelegateIgnore',

		/**
		 * Circular list constructor
		 *
		 * @private
		 * @type function()
		 */
		CircularList = that.CircularList,

		/**
		 * Whether tag names are case sensitive (as in XML or XHTML documents)
		 *
		 * @type boolean
		 */
		tagsCaseSensitive = document.createElement('i').tagName === 'i',

		/**
		 * Check whether an element matches a generic selector
		 *
		 * @private
		 * @type function()
		 * @param {string} selector A CSS selector
		 */
		matches = (function(p) {
			return (p.matchesSelector || p.webkitMatchesSelector || p.mozMatchesSelector || p.msMatchesSelector || p.oMatchesSelector);
		}(HTMLElement.prototype)),

		/**
		 * Check whether an element matches a tag selector
		 *
		 * Tags are NOT case-sensitive, except in XML (and XML-based languages such as XHTML).
		 *
		 * @private
		 * @param {string} tagName The tag name to test against
		 * @param {Element} element The element to test with
		 */
		matchesTag = function(tagName, element) {
			return tagName === element.tagName;
		},

		/**
		 * Check whether the ID of the element in 'this' matches the given ID
		 *
		 * IDs are case-sensitive.
		 *
		 * @private
		 * @param {string} id The ID to test against
		 * @param {Element} element The element to test with
		 */
		matchesId = function(id, element) {
			return id === element.id;
		};

	/**
	 * Fire a listener on a target
	 *
	 * @private
	 * @param {Event} event
	 * @param {Node} target
	 * @param {Object} listener
	 */
	function fire(event, target, listener) {
		var returned, oldData;

		if (listener.d !== null) {
			oldData = event.data;
			event.data = listener.d;
			returned = listener.h.call(target, event, target);
			event.data = oldData;
		} else {
			returned = listener.h.call(target, event, target);
		}

		return returned;
	}

	/**
	 * Internal function proxied by Delegate#on
	 *
	 * @private
	 * @param {Object} lisenerList
	 * @param {Node|DOMWindow} root
	 * @param {Event} event
	 */
	function handle(listenerList, root, event) {
		var listener, returned, specificList, target;

		if (event[EVENT_IGNORE] === true) {
			return;
		}

		target = event.target;
		if (target.nodeType === Node.TEXT_NODE) {
			target = target.parentNode;
		}
		specificList = listenerList[event.type];

		// If the fire function actually causes the specific list to be destroyed,
		// Need check that the specific list is still populated
		while (target && specificList.length > 0) {
			listener = specificList.first;
			do {

				// Check for match and fire the event if there's one
				// TODO:MCG:20120117: Need a way to check if event#stopImmediateProgagation was called. If so, break both loops.
				if (listener.m.call(target, listener.p, target)) {
					returned = fire(event, target, listener);
				}

				// Stop propagation to subsequent callbacks if the callback returned false
				if (returned === false) {
					event[EVENT_IGNORE] = true;
					return;
				}

				listener = listener.next;

			// If the fire function actually causes the specific list object to be destroyed,
			// need a way of getting out of here so check listener is set
			} while (listener !== specificList.first && listener);

			// TODO:MCG:20120117: Need a way to check if event#stopProgagation was called. If so, break looping through the DOM.
			// Stop if the delegation root has been reached
			if (target === root) {
				break;
			}

			target = target.parentElement;
		}
	}

	/**
	 * Internal function proxied by Delegate#on
	 *
	 * @private
	 * @param {Delegate} that
	 * @param {Object} listenerList
	 * @param {Node|DOMWindow} root
	 */
	function on(that, listenerList, root, eventType, selector, eventData, handler) {
		var matcher, matcherParam;

		if (!eventType) {
			throw new TypeError('Invalid event type: ' + eventType);
		}

		if (!selector) {
			throw new TypeError('Invalid selector: ' + selector);
		}

		// Support a separated list of event types
		if (eventType.indexOf(SEPARATOR) !== -1) {
			eventType.split(SEPARATOR).forEach(function(eventType) {
				on.call(that, that, listenerList, root, eventType, selector, eventData, handler);
			});

			return;
		}

		if (handler === undefined) {
			handler = eventData;
			eventData = null;

		// Normalise undefined eventData to null
		} else if (eventData === undefined) {
			eventData = null;
		}

		if (typeof handler !== 'function') {
			throw new TypeError("Handler must be a type of Function");
		}

		// Add master handler for type if not created yet
		if (!listenerList[eventType]) {
			root.addEventListener(eventType, that.handle, (eventType === 'error'));
			listenerList[eventType] = new CircularList();
		}

		// Compile a matcher for the given selector
		if (/^[a-z]+$/i.test(selector)) {
			if (!tagsCaseSensitive) {
				matcherParam = selector.toUpperCase();
			} else {
				matcherParam = selector;
			}

			matcher = matchesTag;
		} else if (/^#[a-z0-9\-_]+$/i.test(selector)) {
			matcherParam = selector.slice(1);
			matcher = matchesId;
		} else {
			matcherParam = selector;
			matcher = matches;
		}

		// Add to the list of listeners
		listenerList[eventType].append({
			s: selector,
			d: eventData,
			h: handler,
			m: matcher,
			p: matcherParam
		});
	}

	/**
	 * Internal function proxied by Delegate#off
	 *
	 * @private
	 * @param {Delegate} that
	 * @param {Object} listenerList
	 * @param {Node|DOMWindow} root
	 */
	function off(that, listenerList, root, eventType, selector, handler) {
		var listener, nextListener, firstListener, specificList, singleEventType;

		if (!eventType) {
			for (singleEventType in listenerList) {
				if (listenerList.hasOwnProperty(singleEventType)) {
					off.call(that, that, listenerList, root, singleEventType, selector, handler);
				}
			}
			return;
		}
		specificList = listenerList[eventType];

		if (!specificList) {
			return;
		}

		// Support a separated list of event types
		if (eventType.indexOf(SEPARATOR) !== -1) {
			eventType.split(SEPARATOR).forEach(function(eventType) {
				off.call(that, that, listenerList, root, eventType, selector, handler);
			});
			return;
		}

		// Remove only parameter matches if specified
		listener = firstListener = specificList.first;
		do {
			if ((!selector || selector === listener.s) && (!handler || handler === listener.h)) {

				// listener.next will be undefined after listener is removed, so save a reference here
				nextListener = listener.next;
				specificList.remove(listener);
				listener = nextListener;
			} else {
				listener = listener.next;
			}
		} while (listener && listener !== firstListener);

		// All listeners removed
		if (!specificList.length) {
			delete listenerList[eventType];

			// Remove the main handler
			root.removeEventListener(eventType, that.handle, false);
		}
	}

	/**
	 * DOM event delegator
	 *
	 * The delegator will listen for events that bubble up to the root node.
	 *
	 * @constructor
	 * @param {Node|DOMWindow|string} root The root node, a window object or a selector string
	 */
	function Delegate(root) {
		var

			/**
			 * Keep a reference to the current instance
			 *
			 * @internal
			 * @type Delegate
			 */
			that = this,

			/**
			 * Maintain a list of listeners, indexed by event name
			 *
			 * @internal
			 * @type Object
			 */
			listenerList = {};

		if (typeof root === 'string') {
			root = document.querySelector(root);
		}

		if (!root || !root.addEventListener) {
			throw new TypeError('Root node not specified');
		}

		/**
		 * Attach a handler to one event for all elements that match the selector, now or in the future
		 *
		 * The handler function receives three arguments: the DOM event object, the node that matched the selector while the event was bubbling
		 * and a reference to itself. Within the handler, 'this' is equal to the second argument.
		 * The node that actually received the event can be accessed via 'event.target'.
		 *
		 * @param {string} eventType Listen for these events (in a space-separated list)
		 * @param {string} selector Only handle events on elements matching this selector
		 * @param {Object} [eventData] If this parameter is not specified, the third parameter must be the handler
		 * @param {function()} handler Handler function - event data passed here will be in event.data
		 * @returns {Delegate} This method is chainable
		 */
		this.on = function() {
			Array.prototype.unshift.call(arguments, that, listenerList, root);
			on.apply(that, arguments);
			return this;
		};

		/**
		 * Remove an event handler for elements that match the selector, forever
		 *
		 * @param {string} eventType Remove handlers for events matching this type, considering the other parameters
		 * @param {string} [selector] If this parameter is omitted, only handlers which match the other two will be removed
		 * @param {function()} [handler] If this parameter is omitted, only handlers which match the previous two will be removed
		 * @returns {Delegate} This method is chainable
		 */
		this.off = function() {
			Array.prototype.unshift.call(arguments, that, listenerList, root);
			off.apply(that, arguments);
			return this;
		};

		/**
		 * Handle an arbitrary event
		 *
		 * @private
		 * @param {Event} event
		 */
		this.handle = function(event) {
			handle.call(that, listenerList, root, event);
		};
	}

	return Delegate;

}(this));<|endoftext|>


<commit_before>const assert = require('assert')
const childProcess = require('child_process')
const fs = require('fs')
const http = require('http')
const multiparty = require('multiparty')
const path = require('path')
const temp = require('temp').track()
const url = require('url')
const {closeWindow} = require('./window-helpers')

const {remote} = require('electron')
const {app, BrowserWindow, crashReporter} = remote.require('electron')

describe('crashReporter module', () => {
  if (process.mas || process.env.DISABLE_CRASH_REPORTER_TESTS) return

  let originalTempDirectory = null
  let tempDirectory = null

  before(() => {
    tempDirectory = temp.mkdirSync('electronCrashReporterSpec-')
    originalTempDirectory = app.getPath('temp')
    app.setPath('temp', tempDirectory)
  })

  after(() => {
    app.setPath('temp', originalTempDirectory)
  })

  const fixtures = path.resolve(__dirname, 'fixtures')
  const generateSpecs = (description, browserWindowOpts) => {
    describe(description, () => {
      let w = null
      let stopServer = null

      beforeEach(() => {
        stopServer = null
        w = new BrowserWindow(Object.assign({ show: false }, browserWindowOpts))
      })

      afterEach(() => closeWindow(w).then(() => { w = null }))

      afterEach(() => {
        stopCrashService()
      })

      afterEach((done) => {
        if (stopServer != null) {
          stopServer(done)
        } else {
          done()
        }
      })

      it('should send minidump when renderer crashes', function (done) {
        if (process.env.APPVEYOR === 'True') return done()
        if (process.env.TRAVIS === 'true') return done()

        this.timeout(120000)

        stopServer = startServer({
          callback (port) {
            const crashUrl = url.format({
              protocol: 'file',
              pathname: path.join(fixtures, 'api', 'crash.html'),
              search: '?port=' + port
            })
            w.loadURL(crashUrl)
          },
          processType: 'renderer',
          done: done
        })
      })
      it('should send minidump when node processes crash', function (done) {
        if (process.env.APPVEYOR === 'True') return done()
        if (process.env.TRAVIS === 'true') return done()

        this.timeout(120000)

        stopServer = startServer({
          callback (port) {
            const crashesDir = path.join(app.getPath('temp'), `${process.platform === 'win32' ? 'Zombies' : app.getName()} Crashes`)
            const version = app.getVersion()
            const crashPath = path.join(fixtures, 'module', 'crash.js')

            if (process.platform === 'win32') {
              const crashServiceProcess = childProcess.spawn(process.execPath, [
                `--reporter-url=http://127.0.0.1:${port}`,
                '--application-name=Zombies',
                `--crashes-directory=${crashesDir}`
              ], {
                env: {
                  ELECTRON_INTERNAL_CRASH_SERVICE: 1
                },
                detached: true
              })
              remote.process.crashServicePid = crashServiceProcess.pid
            }

            childProcess.fork(crashPath, [port, version, crashesDir], {silent: true})
          },
          processType: 'browser',
          done: done
        })
      })
      it('should not send minidump if uploadToServer is false', function (done) {
        this.timeout(120000)

        let dumpFile
        let crashesDir = crashReporter.getCrashesDirectory()
        const existingDumpFiles = new Set()
        if (process.platform === 'darwin') {
          // crashpad puts the dump files in the "completed" subdirectory
          crashesDir = path.join(crashesDir, 'completed')
          crashReporter.setUploadToServer(false)
        }
        const testDone = (uploaded) => {
          if (uploaded) return done(new Error('Uploaded crash report'))
          if (process.platform === 'darwin') crashReporter.setUploadToServer(true)
          assert(fs.existsSync(dumpFile))
          done()
        }

        let pollInterval
        const pollDumpFile = () => {
          fs.readdir(crashesDir, (err, files) => {
            if (err) return
            const dumps = files.filter((file) => /\.dmp$/.test(file) && !existingDumpFiles.has(file))
            if (!dumps.length) return

            assert.equal(1, dumps.length)
            dumpFile = path.join(crashesDir, dumps[0])
            clearInterval(pollInterval)
            // dump file should not be deleted when not uploading, so we wait
            // 1s and assert it still exists in `testDone`
            setTimeout(testDone, 1000)
          })
        }

        remote.ipcMain.once('list-existing-dumps', (event) => {
          fs.readdir(crashesDir, (err, files) => {
            if (!err) {
              for (const file of files) {
                if (/\.dmp$/.test(file)) {
                  existingDumpFiles.add(file)
                }
              }
            }
            event.returnValue = null  // allow the renderer to crash
            pollInterval = setInterval(pollDumpFile, 100)
          })
        })

        stopServer = startServer({
          callback (port) {
            const crashUrl = url.format({
              protocol: 'file',
              pathname: path.join(fixtures, 'api', 'crash.html'),
              search: `?port=${port}&skipUpload=1`
            })
            w.loadURL(crashUrl)
          },
          processType: 'renderer',
          done: testDone.bind(null, true)
        })
      })
      it('should send minidump with updated extra parameters', function (done) {
        if (process.env.APPVEYOR === 'True') return done()
        if (process.env.TRAVIS === 'true') return done()

        this.timeout(120000)

        stopServer = startServer({
          callback (port) {
            const crashUrl = url.format({
              protocol: 'file',
              pathname: path.join(fixtures, 'api', 'crash-restart.html'),
              search: `?port=${port}`
            })
            w.loadURL(crashUrl)
          },
          processType: 'renderer',
          done: done()
        })
      })
    })
  }

  generateSpecs('without sandbox', {})
  generateSpecs('with sandbox', {
    webPreferences: {
      sandbox: true,
      preload: path.join(fixtures, 'module', 'preload-sandbox.js')
    }
  })

  describe('getProductName', () => {
    it('returns the product name if one is specified', () => {
      const name = crashReporter.getProductName()
      const expectedName = (process.platform === 'darwin') ? 'Electron Test' : 'Zombies'
      assert.equal(name, expectedName)
    })
  })

  describe('getTempDirectory', () => {
    it('returns temp directory for app if one is specified', () => {
      const tempDir = crashReporter.getTempDirectory()
      assert.equal(tempDir, app.getPath('temp'))
    })
  })

  describe('start(options)', () => {
    it('requires that the companyName and submitURL options be specified', () => {
      assert.throws(() => {
        crashReporter.start({companyName: 'Missing submitURL'})
      }, /submitURL is a required option to crashReporter\.start/)
      assert.throws(() => {
        crashReporter.start({submitURL: 'Missing companyName'})
      }, /companyName is a required option to crashReporter\.start/)
    })
    it('can be called multiple times', () => {
      assert.doesNotThrow(() => {
        crashReporter.start({
          companyName: 'Umbrella Corporation',
          submitURL: 'http://127.0.0.1/crashes'
        })

        crashReporter.start({
          companyName: 'Umbrella Corporation 2',
          submitURL: 'http://127.0.0.1/more-crashes'
        })
      })
    })
  })

  describe('getCrashesDirectory', () => {
    it('correctly returns the directory', () => {
      const crashesDir = crashReporter.getCrashesDirectory()
      let dir
      if (process.platform === 'win32') {
        dir = `${app.getPath('temp')}/Zombies Crashes`
      } else {
        dir = `${app.getPath('temp')}/Electron Test Crashes`
      }
      assert.equal(crashesDir, dir)
    })
  })

  describe('getUploadedReports', () => {
    it('returns an array of reports', () => {
      const reports = crashReporter.getUploadedReports()
      assert(typeof reports === 'object')
    })
  })

  describe('getLastCrashReport', () => {
    it('correctly returns the most recent report', () => {
      if (process.env.TRAVIS === 'True') return

      const reports = crashReporter.getUploadedReports()
      const lastReport = reports[0]
      assert(lastReport != null)
    })
  })

  describe('getUploadToServer()', () => {
    it('throws an error when called from the renderer process', () => {
      assert.throws(() => require('electron').crashReporter.getUploadToServer())
    })
    it('returns true when uploadToServer is set to true', () => {
      if (process.platform === 'darwin') {
        crashReporter.start({
          companyName: 'Umbrella Corporation',
          submitURL: 'http://127.0.0.1/crashes',
          uploadToServer: true
        })
        assert.equal(crashReporter.getUploadToServer(), true)
      }
    })
    it('returns false when uploadToServer is set to false', () => {
      if (process.platform === 'darwin') {
        crashReporter.start({
          companyName: 'Umbrella Corporation',
          submitURL: 'http://127.0.0.1/crashes',
          uploadToServer: true
        })
        crashReporter.setUploadToServer(false)
        assert.equal(crashReporter.getUploadToServer(), false)
      }
    })
  })

  describe('setUploadToServer(uploadToServer)', () => {
    it('throws an error when called from the renderer process', () => {
      assert.throws(() => require('electron').crashReporter.setUploadToServer('arg'))
    })
    it('sets uploadToServer false when called with false', () => {
      if (process.platform === 'darwin') {
        crashReporter.start({
          companyName: 'Umbrella Corporation',
          submitURL: 'http://127.0.0.1/crashes',
          uploadToServer: true
        })
        crashReporter.setUploadToServer(false)
        assert.equal(crashReporter.getUploadToServer(), false)
      }
    })
    it('sets uploadToServer true when called with true', () => {
      if (process.platform === 'darwin') {
        crashReporter.start({
          companyName: 'Umbrella Corporation',
          submitURL: 'http://127.0.0.1/crashes',
          uploadToServer: false
        })
        crashReporter.setUploadToServer(true)
        assert.equal(crashReporter.getUploadToServer(), true)
      }
    })
  })

  describe('Parameters', () => {
    it('returns all of the current parameters', () => {
      crashReporter.start({
        companyName: 'Umbrella Corporation',
        submitURL: 'http://127.0.0.1/crashes'
      })

      const parameters = crashReporter.getParameters()
      assert(typeof parameters === 'object')
    })
    it('adds a parameter', () => {
      // only run on MacOS
      if (process.platform !== 'darwin') return

      crashReporter.start({
        companyName: 'Umbrella Corporation',
        submitURL: 'http://127.0.0.1/crashes'
      })

      crashReporter.setExtraParameter('hello', 'world')
      const updatedParams = crashReporter.getParameters()

      assert('hello' in updatedParams)
    })
    it('removes a parameter', () => {
      // only run on MacOS
      if (process.platform !== 'darwin') return

      crashReporter.start({
        companyName: 'Umbrella Corporation',
        submitURL: 'http://127.0.0.1/crashes'
      })

      crashReporter.setExtraParameter('hello', 'world')
      const originalParams = crashReporter.getParameters()
      assert('hello' in originalParams)

      crashReporter.removeExtraParameter('hello')
      const updatedParams = crashReporter.getParameters()
      assert(!('hello' in updatedParams))
    })
  })
})

const waitForCrashReport = () => {
  return new Promise((resolve, reject) => {
    let times = 0
    const checkForReport = () => {
      if (crashReporter.getLastCrashReport() != null) {
        resolve()
      } else if (times >= 10) {
        reject(new Error('No crash report available'))
      } else {
        times++
        setTimeout(checkForReport, 100)
      }
    }
    checkForReport()
  })
}

const startServer = ({callback, processType, done}) => {
  let called = false
  let server = http.createServer((req, res) => {
    const form = new multiparty.Form()
    form.parse(req, (error, fields) => {
      if (error) throw error
      if (called) return
      called = true
      assert.equal(fields.prod, 'Electron')
      assert.equal(fields.ver, process.versions.electron)
      assert.equal(fields.process_type, processType)
      assert.equal(fields.platform, process.platform)
      assert.equal(fields.extra1, 'extra1')
      assert.equal(fields.extra2, 'extra2')
      assert.equal(fields.extra3, undefined)
      assert.equal(fields._productName, 'Zombies')
      assert.equal(fields._companyName, 'Umbrella Corporation')
      assert.equal(fields._version, app.getVersion())

      const reportId = 'abc-123-def-456-abc-789-abc-123-abcd'
      res.end(reportId, () => {
        waitForCrashReport().then(() => {
          assert.equal(crashReporter.getLastCrashReport().id, reportId)
          assert.notEqual(crashReporter.getUploadedReports().length, 0)
          assert.equal(crashReporter.getUploadedReports()[0].id, reportId)
          req.socket.destroy()
          done()
        }, done)
      })
    })
  })

  const activeConnections = new Set()
  server.on('connection', (connection) => {
    activeConnections.add(connection)
    connection.once('close', () => {
      activeConnections.delete(connection)
    })
  })

  let {port} = remote.process
  server.listen(port, '127.0.0.1', () => {
    port = server.address().port
    remote.process.port = port
    if (process.platform === 'darwin') {
      crashReporter.start({
        companyName: 'Umbrella Corporation',
        submitURL: 'http://127.0.0.1:' + port
      })
    }
    callback(port)
  })

  return function stopServer (done) {
    for (const connection of activeConnections) {
      connection.destroy()
    }
    server.close(() => {
      done()
    })
  }
}

const stopCrashService = () => {
  const {crashServicePid} = remote.process
  if (crashServicePid) {
    remote.process.crashServicePid = 0
    try {
      process.kill(crashServicePid)
    } catch (error) {
      if (error.code !== 'ESRCH') {
        throw error
      }
    }
  }
}
<commit_msg>another try at fixing travis test<commit_after>const assert = require('assert')
const childProcess = require('child_process')
const fs = require('fs')
const http = require('http')
const multiparty = require('multiparty')
const path = require('path')
const temp = require('temp').track()
const url = require('url')
const {closeWindow} = require('./window-helpers')

const {remote} = require('electron')
const {app, BrowserWindow, crashReporter} = remote.require('electron')

describe('crashReporter module', () => {
  if (process.mas || process.env.DISABLE_CRASH_REPORTER_TESTS) return

  let originalTempDirectory = null
  let tempDirectory = null

  before(() => {
    tempDirectory = temp.mkdirSync('electronCrashReporterSpec-')
    originalTempDirectory = app.getPath('temp')
    app.setPath('temp', tempDirectory)
  })

  after(() => {
    app.setPath('temp', originalTempDirectory)
  })

  const fixtures = path.resolve(__dirname, 'fixtures')
  const generateSpecs = (description, browserWindowOpts) => {
    describe(description, () => {
      let w = null
      let stopServer = null

      beforeEach(() => {
        stopServer = null
        w = new BrowserWindow(Object.assign({ show: false }, browserWindowOpts))
      })

      afterEach(() => closeWindow(w).then(() => { w = null }))

      afterEach(() => {
        stopCrashService()
      })

      afterEach((done) => {
        if (stopServer != null) {
          stopServer(done)
        } else {
          done()
        }
      })

      it('should send minidump when renderer crashes', function (done) {
        if (process.env.APPVEYOR === 'True') return done()
        if (process.env.TRAVIS === 'true') return done()

        this.timeout(120000)

        stopServer = startServer({
          callback (port) {
            const crashUrl = url.format({
              protocol: 'file',
              pathname: path.join(fixtures, 'api', 'crash.html'),
              search: '?port=' + port
            })
            w.loadURL(crashUrl)
          },
          processType: 'renderer',
          done: done
        })
      })
      it('should send minidump when node processes crash', function (done) {
        if (process.env.APPVEYOR === 'True') return done()
        if (process.env.TRAVIS === 'true') return done()

        this.timeout(120000)

        stopServer = startServer({
          callback (port) {
            const crashesDir = path.join(app.getPath('temp'), `${process.platform === 'win32' ? 'Zombies' : app.getName()} Crashes`)
            const version = app.getVersion()
            const crashPath = path.join(fixtures, 'module', 'crash.js')

            if (process.platform === 'win32') {
              const crashServiceProcess = childProcess.spawn(process.execPath, [
                `--reporter-url=http://127.0.0.1:${port}`,
                '--application-name=Zombies',
                `--crashes-directory=${crashesDir}`
              ], {
                env: {
                  ELECTRON_INTERNAL_CRASH_SERVICE: 1
                },
                detached: true
              })
              remote.process.crashServicePid = crashServiceProcess.pid
            }

            childProcess.fork(crashPath, [port, version, crashesDir], {silent: true})
          },
          processType: 'browser',
          done: done
        })
      })
      it('should not send minidump if uploadToServer is false', function (done) {
        this.timeout(120000)

        let dumpFile
        let crashesDir = crashReporter.getCrashesDirectory()
        const existingDumpFiles = new Set()
        if (process.platform === 'darwin') {
          // crashpad puts the dump files in the "completed" subdirectory
          crashesDir = path.join(crashesDir, 'completed')
          crashReporter.setUploadToServer(false)
        }
        const testDone = (uploaded) => {
          if (uploaded) return done(new Error('Uploaded crash report'))
          if (process.platform === 'darwin') crashReporter.setUploadToServer(true)
          assert(fs.existsSync(dumpFile))
          done()
        }

        let pollInterval
        const pollDumpFile = () => {
          fs.readdir(crashesDir, (err, files) => {
            if (err) return
            const dumps = files.filter((file) => /\.dmp$/.test(file) && !existingDumpFiles.has(file))
            if (!dumps.length) return

            assert.equal(1, dumps.length)
            dumpFile = path.join(crashesDir, dumps[0])
            clearInterval(pollInterval)
            // dump file should not be deleted when not uploading, so we wait
            // 1s and assert it still exists in `testDone`
            setTimeout(testDone, 1000)
          })
        }

        remote.ipcMain.once('list-existing-dumps', (event) => {
          fs.readdir(crashesDir, (err, files) => {
            if (!err) {
              for (const file of files) {
                if (/\.dmp$/.test(file)) {
                  existingDumpFiles.add(file)
                }
              }
            }
            event.returnValue = null  // allow the renderer to crash
            pollInterval = setInterval(pollDumpFile, 100)
          })
        })

        stopServer = startServer({
          callback (port) {
            const crashUrl = url.format({
              protocol: 'file',
              pathname: path.join(fixtures, 'api', 'crash.html'),
              search: `?port=${port}&skipUpload=1`
            })
            w.loadURL(crashUrl)
          },
          processType: 'renderer',
          done: testDone.bind(null, true)
        })
      })
      it('should send minidump with updated extra parameters', function (done) {
        if (process.env.APPVEYOR === 'True') return done()
        if (process.env.TRAVIS === 'true') return done()

        this.timeout(120000)

        stopServer = startServer({
          callback (port) {
            const crashUrl = url.format({
              protocol: 'file',
              pathname: path.join(fixtures, 'api', 'crash-restart.html'),
              search: `?port=${port}`
            })
            w.loadURL(crashUrl)
          },
          processType: 'renderer',
          done: done()
        })
      })
    })
  }

  generateSpecs('without sandbox', {})
  generateSpecs('with sandbox', {
    webPreferences: {
      sandbox: true,
      preload: path.join(fixtures, 'module', 'preload-sandbox.js')
    }
  })

  describe('getProductName', () => {
    it('returns the product name if one is specified', () => {
      const name = crashReporter.getProductName()
      const expectedName = (process.platform === 'darwin') ? 'Electron Test' : 'Zombies'
      assert.equal(name, expectedName)
    })
  })

  describe('getTempDirectory', () => {
    it('returns temp directory for app if one is specified', () => {
      const tempDir = crashReporter.getTempDirectory()
      assert.equal(tempDir, app.getPath('temp'))
    })
  })

  describe('start(options)', () => {
    it('requires that the companyName and submitURL options be specified', () => {
      assert.throws(() => {
        crashReporter.start({companyName: 'Missing submitURL'})
      }, /submitURL is a required option to crashReporter\.start/)
      assert.throws(() => {
        crashReporter.start({submitURL: 'Missing companyName'})
      }, /companyName is a required option to crashReporter\.start/)
    })
    it('can be called multiple times', () => {
      assert.doesNotThrow(() => {
        crashReporter.start({
          companyName: 'Umbrella Corporation',
          submitURL: 'http://127.0.0.1/crashes'
        })

        crashReporter.start({
          companyName: 'Umbrella Corporation 2',
          submitURL: 'http://127.0.0.1/more-crashes'
        })
      })
    })
  })

  describe('getCrashesDirectory', () => {
    it('correctly returns the directory', () => {
      const crashesDir = crashReporter.getCrashesDirectory()
      let dir
      if (process.platform === 'win32') {
        dir = `${app.getPath('temp')}/Zombies Crashes`
      } else {
        dir = `${app.getPath('temp')}/Electron Test Crashes`
      }
      assert.equal(crashesDir, dir)
    })
  })

  describe('getUploadedReports', () => {
    it('returns an array of reports', () => {
      const reports = crashReporter.getUploadedReports()
      assert(typeof reports === 'object')
    })
  })

  describe('getLastCrashReport', () => {
    it('correctly returns the most recent report', () => {
      if (process.env.TRAVIS === 'False') {
        const reports = crashReporter.getUploadedReports()
        const lastReport = reports[0]
        assert(lastReport != null)
      }
    })
  })

  describe('getUploadToServer()', () => {
    it('throws an error when called from the renderer process', () => {
      assert.throws(() => require('electron').crashReporter.getUploadToServer())
    })
    it('returns true when uploadToServer is set to true', () => {
      if (process.platform === 'darwin') {
        crashReporter.start({
          companyName: 'Umbrella Corporation',
          submitURL: 'http://127.0.0.1/crashes',
          uploadToServer: true
        })
        assert.equal(crashReporter.getUploadToServer(), true)
      }
    })
    it('returns false when uploadToServer is set to false', () => {
      if (process.platform === 'darwin') {
        crashReporter.start({
          companyName: 'Umbrella Corporation',
          submitURL: 'http://127.0.0.1/crashes',
          uploadToServer: true
        })
        crashReporter.setUploadToServer(false)
        assert.equal(crashReporter.getUploadToServer(), false)
      }
    })
  })

  describe('setUploadToServer(uploadToServer)', () => {
    it('throws an error when called from the renderer process', () => {
      assert.throws(() => require('electron').crashReporter.setUploadToServer('arg'))
    })
    it('sets uploadToServer false when called with false', () => {
      if (process.platform === 'darwin') {
        crashReporter.start({
          companyName: 'Umbrella Corporation',
          submitURL: 'http://127.0.0.1/crashes',
          uploadToServer: true
        })
        crashReporter.setUploadToServer(false)
        assert.equal(crashReporter.getUploadToServer(), false)
      }
    })
    it('sets uploadToServer true when called with true', () => {
      if (process.platform === 'darwin') {
        crashReporter.start({
          companyName: 'Umbrella Corporation',
          submitURL: 'http://127.0.0.1/crashes',
          uploadToServer: false
        })
        crashReporter.setUploadToServer(true)
        assert.equal(crashReporter.getUploadToServer(), true)
      }
    })
  })

  describe('Parameters', () => {
    it('returns all of the current parameters', () => {
      crashReporter.start({
        companyName: 'Umbrella Corporation',
        submitURL: 'http://127.0.0.1/crashes'
      })

      const parameters = crashReporter.getParameters()
      assert(typeof parameters === 'object')
    })
    it('adds a parameter', () => {
      // only run on MacOS
      if (process.platform !== 'darwin') return

      crashReporter.start({
        companyName: 'Umbrella Corporation',
        submitURL: 'http://127.0.0.1/crashes'
      })

      crashReporter.setExtraParameter('hello', 'world')
      const updatedParams = crashReporter.getParameters()

      assert('hello' in updatedParams)
    })
    it('removes a parameter', () => {
      // only run on MacOS
      if (process.platform !== 'darwin') return

      crashReporter.start({
        companyName: 'Umbrella Corporation',
        submitURL: 'http://127.0.0.1/crashes'
      })

      crashReporter.setExtraParameter('hello', 'world')
      const originalParams = crashReporter.getParameters()
      assert('hello' in originalParams)

      crashReporter.removeExtraParameter('hello')
      const updatedParams = crashReporter.getParameters()
      assert(!('hello' in updatedParams))
    })
  })
})

const waitForCrashReport = () => {
  return new Promise((resolve, reject) => {
    let times = 0
    const checkForReport = () => {
      if (crashReporter.getLastCrashReport() != null) {
        resolve()
      } else if (times >= 10) {
        reject(new Error('No crash report available'))
      } else {
        times++
        setTimeout(checkForReport, 100)
      }
    }
    checkForReport()
  })
}

const startServer = ({callback, processType, done}) => {
  let called = false
  let server = http.createServer((req, res) => {
    const form = new multiparty.Form()
    form.parse(req, (error, fields) => {
      if (error) throw error
      if (called) return
      called = true
      assert.equal(fields.prod, 'Electron')
      assert.equal(fields.ver, process.versions.electron)
      assert.equal(fields.process_type, processType)
      assert.equal(fields.platform, process.platform)
      assert.equal(fields.extra1, 'extra1')
      assert.equal(fields.extra2, 'extra2')
      assert.equal(fields.extra3, undefined)
      assert.equal(fields._productName, 'Zombies')
      assert.equal(fields._companyName, 'Umbrella Corporation')
      assert.equal(fields._version, app.getVersion())

      const reportId = 'abc-123-def-456-abc-789-abc-123-abcd'
      res.end(reportId, () => {
        waitForCrashReport().then(() => {
          assert.equal(crashReporter.getLastCrashReport().id, reportId)
          assert.notEqual(crashReporter.getUploadedReports().length, 0)
          assert.equal(crashReporter.getUploadedReports()[0].id, reportId)
          req.socket.destroy()
          done()
        }, done)
      })
    })
  })

  const activeConnections = new Set()
  server.on('connection', (connection) => {
    activeConnections.add(connection)
    connection.once('close', () => {
      activeConnections.delete(connection)
    })
  })

  let {port} = remote.process
  server.listen(port, '127.0.0.1', () => {
    port = server.address().port
    remote.process.port = port
    if (process.platform === 'darwin') {
      crashReporter.start({
        companyName: 'Umbrella Corporation',
        submitURL: 'http://127.0.0.1:' + port
      })
    }
    callback(port)
  })

  return function stopServer (done) {
    for (const connection of activeConnections) {
      connection.destroy()
    }
    server.close(() => {
      done()
    })
  }
}

const stopCrashService = () => {
  const {crashServicePid} = remote.process
  if (crashServicePid) {
    remote.process.crashServicePid = 0
    try {
      process.kill(crashServicePid)
    } catch (error) {
      if (error.code !== 'ESRCH') {
        throw error
      }
    }
  }
}
<|endoftext|>