<commit_before>/*
 * ----------------------------- JSTORAGE -------------------------------------
 * Simple local storage wrapper to save data on the browser side, supporting
 * all major browsers - IE6+, Firefox2+, Safari4+, Chrome4+ and Opera 10.5+
 *
 * Copyright (c) 2010 - 2012 Andris Reinman, andris.reinman@gmail.com
 * Project homepage: www.jstorage.info
 *
 * Licensed under MIT-style license:
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

(function(){
	var
	/* jStorage version */
		JSTORAGE_VERSION = "0.4.3",

	/* detect a dollar object or create one if not found */
		$ = window.jQuery || window.$ || (window.$ = {}),

	/* check for a JSON handling support */
		JSON = {
			parse:
				window.JSON && (window.JSON.parse || window.JSON.decode) ||
					String.prototype.evalJSON && function(str){return String(str).evalJSON();} ||
					$.parseJSON ||
					$.evalJSON,
			stringify:
				Object.toJSON ||
					window.JSON && (window.JSON.stringify || window.JSON.encode) ||
					$.toJSON
		};

	// Break if no JSON support was found
	if(!JSON.parse || !JSON.stringify){
		throw new Error("No JSON support found, include //cdnjs.cloudflare.com/ajax/libs/json2/20110223/json2.js to page");
	}

	var
	/* This is the object, that holds the cached values */
		_storage = {__jstorage_meta:{CRC32:{}}},

	/* Actual browser storage (localStorage or globalStorage['domain']) */
		_storage_service = {jStorage:"{}"},

	/* DOM element for older IE versions, holds userData behavior */
		_storage_elm = null,

	/* How much space does the storage take */
		_storage_size = 0,

	/* which backend is currently used */
		_backend = false,

	/* onchange observers */
		_observers = {},

	/* timeout to wait after onchange event */
		_observer_timeout = false,

	/* last update time */
		_observer_update = 0,

	/* pubsub observers */
		_pubsub_observers = {},

	/* skip published items older than current timestamp */
		_pubsub_last = +new Date(),

	/* Next check for TTL */
		_ttl_timeout,

		/**
		 * XML encoding and decoding as XML nodes can't be JSON'ized
		 * XML nodes are encoded and decoded if the node is the value to be saved
		 * but not if it's as a property of another object
		 * Eg. -
		 *   $.jStorage.set("key", xmlNode);        // IS OK
		 *   $.jStorage.set("key", {xml: xmlNode}); // NOT OK
		 */
			_XMLService = {

			/**
			 * Validates a XML node to be XML
			 * based on jQuery.isXML function
			 */
			isXML: function(elm){
				var documentElement = (elm ? elm.ownerDocument || elm : 0).documentElement;
				return documentElement ? documentElement.nodeName !== "HTML" : false;
			},

			/**
			 * Encodes a XML node to string
			 * based on http://www.mercurytide.co.uk/news/article/issues-when-working-ajax/
			 */
			encode: function(xmlNode) {
				if(!this.isXML(xmlNode)){
					return false;
				}
				try{ // Mozilla, Webkit, Opera
					return new XMLSerializer().serializeToString(xmlNode);
				}catch(E1) {
					try {  // IE
						return xmlNode.xml;
					}catch(E2){}
				}
				return false;
			},

			/**
			 * Decodes a XML node from string
			 * loosely based on http://outwestmedia.com/jquery-plugins/xmldom/
			 */
			decode: function(xmlString){
				var dom_parser = ("DOMParser" in window && (new DOMParser()).parseFromString) ||
						(window.ActiveXObject && function(_xmlString) {
							var xml_doc = new ActiveXObject('Microsoft.XMLDOM');
							xml_doc.async = 'false';
							xml_doc.loadXML(_xmlString);
							return xml_doc;
						}),
					resultXML;
				if(!dom_parser){
					return false;
				}
				resultXML = dom_parser.call("DOMParser" in window && (new DOMParser()) || window, xmlString, 'text/xml');
				return this.isXML(resultXML)?resultXML:false;
			}
		};

	////////////////////////// PRIVATE METHODS ////////////////////////

	/**
	 * Initialization function. Detects if the browser supports DOM Storage
	 * or userData behavior and behaves accordingly.
	 */
	function _init(){
		/* Check if browser supports localStorage */
		var localStorageReallyWorks = false;
		if("localStorage" in window){
			try {
				window.localStorage.setItem('_tmptest', 'tmpval');
				localStorageReallyWorks = true;
				window.localStorage.removeItem('_tmptest');
			} catch(BogusQuotaExceededErrorOnIos5) {
				// Thanks be to iOS5 Private Browsing mode which throws
				// QUOTA_EXCEEDED_ERRROR DOM Exception 22.
			}
		}

		if(localStorageReallyWorks){
			try {
				if(window.localStorage) {
					_storage_service = window.localStorage;
					_backend = "localStorage";
					_observer_update = _storage_service.jStorage_update;
				}
			} catch(E3) {/* Firefox fails when touching localStorage and cookies are disabled */}
		}
		/* Check if browser supports globalStorage */
		else if("globalStorage" in window){
			try {
				if(window.globalStorage) {
					_storage_service = window.globalStorage[window.location.hostname];
					_backend = "globalStorage";
					_observer_update = _storage_service.jStorage_update;
				}
			} catch(E4) {/* Firefox fails when touching localStorage and cookies are disabled */}
		}
		/* Check if browser supports userData behavior */
		else {
			_storage_elm = document.createElement('link');
			if(_storage_elm.addBehavior){

				/* Use a DOM element to act as userData storage */
				_storage_elm.style.behavior = 'url(#default#userData)';

				/* userData element needs to be inserted into the DOM! */
				document.getElementsByTagName('head')[0].appendChild(_storage_elm);

				try{
					_storage_elm.load("jStorage");
				}catch(E){
					// try to reset cache
					_storage_elm.setAttribute("jStorage", "{}");
					_storage_elm.save("jStorage");
					_storage_elm.load("jStorage");
				}

				var data = "{}";
				try{
					data = _storage_elm.getAttribute("jStorage");
				}catch(E5){}

				try{
					_observer_update = _storage_elm.getAttribute("jStorage_update");
				}catch(E6){}

				_storage_service.jStorage = data;
				_backend = "userDataBehavior";
			}else{
				_storage_elm = null;
				return;
			}
		}

		// Load data from storage
		_load_storage();

		// remove dead keys
		_handleTTL();

		// start listening for changes
		_setupObserver();

		// initialize publish-subscribe service
		_handlePubSub();

		// handle cached navigation
		if("addEventListener" in window){
			window.addEventListener("pageshow", function(event){
				if(event.persisted){
					_storageObserver();
				}
			}, false);
		}
	}

	/**
	 * Reload data from storage when needed
	 */
	function _reloadData(){
		var data = "{}";

		if(_backend == "userDataBehavior"){
			_storage_elm.load("jStorage");

			try{
				data = _storage_elm.getAttribute("jStorage");
			}catch(E5){}

			try{
				_observer_update = _storage_elm.getAttribute("jStorage_update");
			}catch(E6){}

			_storage_service.jStorage = data;
		}

		_load_storage();

		// remove dead keys
		_handleTTL();

		_handlePubSub();
	}

	/**
	 * Sets up a storage change observer
	 */
	function _setupObserver(){
		if(_backend == "localStorage" || _backend == "globalStorage"){
			if("addEventListener" in window){
				window.addEventListener("storage", _storageObserver, false);
			}else{
				document.attachEvent("onstorage", _storageObserver);
			}
		}else if(_backend == "userDataBehavior"){
			setInterval(_storageObserver, 1000);
		}
	}

	/**
	 * Fired on any kind of data change, needs to check if anything has
	 * really been changed
	 */
	function _storageObserver(){
		var updateTime;
		// cumulate change notifications with timeout
		clearTimeout(_observer_timeout);
		_observer_timeout = setTimeout(function(){

			if(_backend == "localStorage" || _backend == "globalStorage"){
				updateTime = _storage_service.jStorage_update;
			}else if(_backend == "userDataBehavior"){
				_storage_elm.load("jStorage");
				try{
					updateTime = _storage_elm.getAttribute("jStorage_update");
				}catch(E5){}
			}

			if(updateTime && updateTime != _observer_update){
				_observer_update = updateTime;
				_checkUpdatedKeys();
			}

		}, 25);
	}

	/**
	 * Reloads the data and checks if any keys are changed
	 */
	function _checkUpdatedKeys(){
		var oldCrc32List = JSON.parse(JSON.stringify(_storage.__jstorage_meta.CRC32)),
			newCrc32List;

		_reloadData();
		newCrc32List = JSON.parse(JSON.stringify(_storage.__jstorage_meta.CRC32));

		var key,
			updated = [],
			removed = [];

		for(key in oldCrc32List){
			if(oldCrc32List.hasOwnProperty(key)){
				if(!newCrc32List[key]){
					removed.push(key);
					continue;
				}
				if(oldCrc32List[key] != newCrc32List[key] && String(oldCrc32List[key]).substr(0,2) == "2."){
					updated.push(key);
				}
			}
		}

		for(key in newCrc32List){
			if(newCrc32List.hasOwnProperty(key)){
				if(!oldCrc32List[key]){
					updated.push(key);
				}
			}
		}

		_fireObservers(updated, "updated");
		_fireObservers(removed, "deleted");
	}

	/**
	 * Fires observers for updated keys
	 *
	 * @param {Array|String} keys Array of key names or a key
	 * @param {String} action What happened with the value (updated, deleted, flushed)
	 */
	function _fireObservers(keys, action){
		keys = [].concat(keys || []);
		if(action == "flushed"){
			keys = [];
			for(var key in _observers){
				if(_observers.hasOwnProperty(key)){
					keys.push(key);
				}
			}
			action = "deleted";
		}
		for(var i=0, len = keys.length; i<len; i++){
			if(_observers[keys[i]]){
				for(var j=0, jlen = _observers[keys[i]].length; j<jlen; j++){
					_observers[keys[i]][j](keys[i], action);
				}
			}
			if(_observers["*"]){
				for(var j=0, jlen = _observers["*"].length; j<jlen; j++){
					_observers["*"][j](keys[i], action);
				}
			}
		}
	}

	/**
	 * Publishes key change to listeners
	 */
	function _publishChange(){
		var updateTime = (+new Date()).toString();

		if(_backend == "localStorage" || _backend == "globalStorage"){
			_storage_service.jStorage_update = updateTime;
		}else if(_backend == "userDataBehavior"){
			_storage_elm.setAttribute("jStorage_update", updateTime);
			_storage_elm.save("jStorage");
		}

		_storageObserver();
	}

	/**
	 * Loads the data from the storage based on the supported mechanism
	 */
	function _load_storage(){
		/* if jStorage string is retrieved, then decode it */
		if(_storage_service.jStorage){
			try{
				_storage = JSON.parse(String(_storage_service.jStorage));
			}catch(E6){_storage_service.jStorage = "{}";}
		}else{
			_storage_service.jStorage = "{}";
		}
		_storage_size = _storage_service.jStorage?String(_storage_service.jStorage).length:0;

		if(!_storage.__jstorage_meta){
			_storage.__jstorage_meta = {};
		}
		if(!_storage.__jstorage_meta.CRC32){
			_storage.__jstorage_meta.CRC32 = {};
		}
	}

	/**
	 * This functions provides the "save" mechanism to store the jStorage object
	 */
	function _save(){
		_dropOldEvents(); // remove expired events
		try{
			_storage_service.jStorage = JSON.stringify(_storage);
			// If userData is used as the storage engine, additional
			if(_storage_elm) {
				_storage_elm.setAttribute("jStorage",_storage_service.jStorage);
				_storage_elm.save("jStorage");
			}
			_storage_size = _storage_service.jStorage?String(_storage_service.jStorage).length:0;
		}catch(E7){/* probably cache is full, nothing is saved this way*/}
	}

	/**
	 * Function checks if a key is set and is string or numberic
	 *
	 * @param {String} key Key name
	 */
	function _checkKey(key){
		if(!key || (typeof key != "string" && typeof key != "number")){
			throw new TypeError('Key name must be string or numeric');
		}
		if(key == "__jstorage_meta"){
			throw new TypeError('Reserved key name');
		}
		return true;
	}

	/**
	 * Removes expired keys
	 */
	function _handleTTL(){
		var curtime, i, TTL, CRC32, nextExpire = Infinity, changed = false, deleted = [];

		clearTimeout(_ttl_timeout);

		if(!_storage.__jstorage_meta || typeof _storage.__jstorage_meta.TTL != "object"){
			// nothing to do here
			return;
		}

		curtime = +new Date();
		TTL = _storage.__jstorage_meta.TTL;

		CRC32 = _storage.__jstorage_meta.CRC32;
		for(i in TTL){
			if(TTL.hasOwnProperty(i)){
				if(TTL[i] <= curtime){
					delete TTL[i];
					delete CRC32[i];
					delete _storage[i];
					changed = true;
					deleted.push(i);
				}else if(TTL[i] < nextExpire){
					nextExpire = TTL[i];
				}
			}
		}

		// set next check
		if(nextExpire != Infinity){
			_ttl_timeout = setTimeout(_handleTTL, nextExpire - curtime);
		}

		// save changes
		if(changed){
			_save();
			_publishChange();
			_fireObservers(deleted, "deleted");
		}
	}

	/**
	 * Checks if there's any events on hold to be fired to listeners
	 */
	function _handlePubSub(){
		var i, len;
		if(!_storage.__jstorage_meta.PubSub){
			return;
		}
		var pubelm,
			_pubsubCurrent = _pubsub_last;

		for(i=len=_storage.__jstorage_meta.PubSub.length-1; i>=0; i--){
			pubelm = _storage.__jstorage_meta.PubSub[i];
			if(pubelm[0] > _pubsub_last){
				_pubsubCurrent = pubelm[0];
				_fireSubscribers(pubelm[1], pubelm[2]);
			}
		}

		_pubsub_last = _pubsubCurrent;
	}

	/**
	 * Fires all subscriber listeners for a pubsub channel
	 *
	 * @param {String} channel Channel name
	 * @param {Mixed} payload Payload data to deliver
	 */
	function _fireSubscribers(channel, payload){
		if(_pubsub_observers[channel]){
			for(var i=0, len = _pubsub_observers[channel].length; i<len; i++){
				// send immutable data that can't be modified by listeners
				_pubsub_observers[channel][i](channel, JSON.parse(JSON.stringify(payload)));
			}
		}
	}

	/**
	 * Remove old events from the publish stream (at least 2sec old)
	 */
	function _dropOldEvents(){
		if(!_storage.__jstorage_meta.PubSub){
			return;
		}

		var retire = +new Date() - 2000;

		for(var i=0, len = _storage.__jstorage_meta.PubSub.length; i<len; i++){
			if(_storage.__jstorage_meta.PubSub[i][0] <= retire){
				// deleteCount is needed for IE6
				_storage.__jstorage_meta.PubSub.splice(i, _storage.__jstorage_meta.PubSub.length - i);
				break;
			}
		}

		if(!_storage.__jstorage_meta.PubSub.length){
			delete _storage.__jstorage_meta.PubSub;
		}

	}

	/**
	 * Publish payload to a channel
	 *
	 * @param {String} channel Channel name
	 * @param {Mixed} payload Payload to send to the subscribers
	 */
	function _publish(channel, payload){
		if(!_storage.__jstorage_meta){
			_storage.__jstorage_meta = {};
		}
		if(!_storage.__jstorage_meta.PubSub){
			_storage.__jstorage_meta.PubSub = [];
		}

		_storage.__jstorage_meta.PubSub.unshift([+new Date, channel, payload]);

		_save();
		_publishChange();
	}

	/**
	 * JS Implementation of MurmurHash2
	 *
	 *  SOURCE: https://github.com/garycourt/murmurhash-js (MIT licensed)
	 *
	 * @author Gary Court
	 * @see http://github.com/garycourt/murmurhash-js
	 * @author Austin Appleby
	 * @see http://sites.google.com/site/murmurhash/
	 *
	 * @param {string} str ASCII only
	 * @param {number} seed Positive integer only
	 * @return {number} 32-bit positive integer hash
	 */

	function murmurhash2_32_gc(str, seed) {
		var
			l = str.length,
			h = seed ^ l,
			i = 0,
			k;

		while (l >= 4) {
			k =
				((str.charCodeAt(i) & 0xff)) |
					((str.charCodeAt(++i) & 0xff) << 8) |
					((str.charCodeAt(++i) & 0xff) << 16) |
					((str.charCodeAt(++i) & 0xff) << 24);

			k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16));
			k ^= k >>> 24;
			k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16));

			h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)) ^ k;

			l -= 4;
			++i;
		}

		switch (l) {
			case 3: h ^= (str.charCodeAt(i + 2) & 0xff) << 16;
			case 2: h ^= (str.charCodeAt(i + 1) & 0xff) << 8;
			case 1: h ^= (str.charCodeAt(i) & 0xff);
				h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16));
		}

		h ^= h >>> 13;
		h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16));
		h ^= h >>> 15;

		return h >>> 0;
	}

	////////////////////////// PUBLIC INTERFACE /////////////////////////

	$.jStorage = {
		/* Version number */
		version: JSTORAGE_VERSION,

		/**
		 * Sets a key's value.
		 *
		 * @param {String} key Key to set. If this value is not set or not
		 *              a string an exception is raised.
		 * @param {Mixed} value Value to set. This can be any value that is JSON
		 *              compatible (Numbers, Strings, Objects etc.).
		 * @param {Object} [options] - possible options to use
		 * @param {Number} [options.TTL] - optional TTL value
		 * @return {Mixed} the used value
		 */
		set: function(key, value, options){
			_checkKey(key);

			options = options || {};

			// undefined values are deleted automatically
			if(typeof value == "undefined"){
				this.deleteKey(key);
				return value;
			}

			if(_XMLService.isXML(value)){
				value = {_is_xml:true,xml:_XMLService.encode(value)};
			}else if(typeof value == "function"){
				return undefined; // functions can't be saved!
			}else if(value && typeof value == "object"){
				// clone the object before saving to _storage tree
				value = JSON.parse(JSON.stringify(value));
			}

			_storage[key] = value;

			_storage.__jstorage_meta.CRC32[key] = "2." + murmurhash2_32_gc(JSON.stringify(value), 0x9747b28c);

			this.setTTL(key, options.TTL || 0); // also handles saving and _publishChange

			_fireObservers(key, "updated");
			return value;
		},

		/**
		 * Looks up a key in cache
		 *
		 * @param {String} key - Key to look up.
		 * @param {mixed} def - Default value to return, if key didn't exist.
		 * @return {Mixed} the key value, default value or null
		 */
		get: function(key, def){
			_checkKey(key);
			if(key in _storage){
				if(_storage[key] && typeof _storage[key] == "object" && _storage[key]._is_xml) {
					return _XMLService.decode(_storage[key].xml);
				}else{
					return _storage[key];
				}
			}
			return typeof(def) == 'undefined' ? null : def;
		},

		/**
		 * Deletes a key from cache.
		 *
		 * @param {String} key - Key to delete.
		 * @return {Boolean} true if key existed or false if it didn't
		 */
		deleteKey: function(key){
			_checkKey(key);
			if(key in _storage){
				delete _storage[key];
				// remove from TTL list
				if(typeof _storage.__jstorage_meta.TTL == "object" &&
					key in _storage.__jstorage_meta.TTL){
					delete _storage.__jstorage_meta.TTL[key];
				}

				delete _storage.__jstorage_meta.CRC32[key];

				_save();
				_publishChange();
				_fireObservers(key, "deleted");
				return true;
			}
			return false;
		},

		/**
		 * Sets a TTL for a key, or remove it if ttl value is 0 or below
		 *
		 * @param {String} key - key to set the TTL for
		 * @param {Number} ttl - TTL timeout in milliseconds
		 * @return {Boolean} true if key existed or false if it didn't
		 */
		setTTL: function(key, ttl){
			var curtime = +new Date();
			_checkKey(key);
			ttl = Number(ttl) || 0;
			if(key in _storage){

				if(!_storage.__jstorage_meta.TTL){
					_storage.__jstorage_meta.TTL = {};
				}

				// Set TTL value for the key
				if(ttl>0){
					_storage.__jstorage_meta.TTL[key] = curtime + ttl;
				}else{
					delete _storage.__jstorage_meta.TTL[key];
				}

				_save();

				_handleTTL();

				_publishChange();
				return true;
			}
			return false;
		},

		/**
		 * Gets remaining TTL (in milliseconds) for a key or 0 when no TTL has been set
		 *
		 * @param {String} key Key to check
		 * @return {Number} Remaining TTL in milliseconds
		 */
		getTTL: function(key){
			var curtime = +new Date(), ttl;
			_checkKey(key);
			if(key in _storage && _storage.__jstorage_meta.TTL && _storage.__jstorage_meta.TTL[key]){
				ttl = _storage.__jstorage_meta.TTL[key] - curtime;
				return ttl || 0;
			}
			return 0;
		},

		/**
		 * Deletes everything in cache.
		 *
		 * @return {Boolean} Always true
		 */
		flush: function(){
			_storage = {__jstorage_meta:{CRC32:{}}};
			_save();
			_publishChange();
			_fireObservers(null, "flushed");
			return true;
		},

		/**
		 * Returns a read-only copy of _storage
		 *
		 * @return {Object} Read-only copy of _storage
		 */
		storageObj: function(){
			function F() {}
			F.prototype = _storage;
			return new F();
		},

		/**
		 * Returns an index of all used keys as an array
		 * ['key1', 'key2',..'keyN']
		 *
		 * @return {Array} Used keys
		 */
		index: function(){
			var index = [], i;
			for(i in _storage){
				if(_storage.hasOwnProperty(i) && i != "__jstorage_meta"){
					index.push(i);
				}
			}
			return index;
		},

		/**
		 * How much space in bytes does the storage take?
		 *
		 * @return {Number} Storage size in chars (not the same as in bytes,
		 *                  since some chars may take several bytes)
		 */
		storageSize: function(){
			return _storage_size;
		},

		/**
		 * Which backend is currently in use?
		 *
		 * @return {String} Backend name
		 */
		currentBackend: function(){
			return _backend;
		},

		/**
		 * Test if storage is available
		 *
		 * @return {Boolean} True if storage can be used
		 */
		storageAvailable: function(){
			return !!_backend;
		},

		/**
		 * Register change listeners
		 *
		 * @param {String} key Key name
		 * @param {Function} callback Function to run when the key changes
		 */
		listenKeyChange: function(key, callback){
			_checkKey(key);
			if(!_observers[key]){
				_observers[key] = [];
			}
			_observers[key].push(callback);
		},

		/**
		 * Remove change listeners
		 *
		 * @param {String} key Key name to unregister listeners against
		 * @param {Function} [callback] If set, unregister the callback, if not - unregister all
		 */
		stopListening: function(key, callback){
			_checkKey(key);

			if(!_observers[key]){
				return;
			}

			if(!callback){
				delete _observers[key];
				return;
			}

			for(var i = _observers[key].length - 1; i>=0; i--){
				if(_observers[key][i] == callback){
					_observers[key].splice(i,1);
				}
			}
		},

		/**
		 * Subscribe to a Publish/Subscribe event stream
		 *
		 * @param {String} channel Channel name
		 * @param {Function} callback Function to run when the something is published to the channel
		 */
		subscribe: function(channel, callback){
			channel = (channel || "").toString();
			if(!channel){
				throw new TypeError('Channel not defined');
			}
			if(!_pubsub_observers[channel]){
				_pubsub_observers[channel] = [];
			}
			_pubsub_observers[channel].push(callback);
		},

		/**
		 * Publish data to an event stream
		 *
		 * @param {String} channel Channel name
		 * @param {Mixed} payload Payload to deliver
		 */
		publish: function(channel, payload){
			channel = (channel || "").toString();
			if(!channel){
				throw new TypeError('Channel not defined');
			}

			_publish(channel, payload);
		},

		/**
		 * Reloads the data from browser storage
		 */
		reInit: function(){
			_reloadData();
		}
	};

	// Initialize jStorage
	_init();

})();
<commit_msg>Removed jStorage plugin<commit_after><|endoftext|>


<commit_before>define([
    'bluebird',
    'kb/common/router',
    'kb/common/lang'
], function (Promise, Router, lang) {
    function factory(config) {
        var runtime = config.runtime,
            router = Router.make(config);

        function doRoute() {
            var handler;
            try {
                handler = router.findCurrentRoute();
            } catch (ex) {
                console.error(ex);
                if (ex instanceof Router.NotFoundException) {
                    runtime.send('app', 'route-not-found', ex);
                    return;
                } else {
                    throw ex;
                }
            }
            if (handler.route.authorization) {
                if (!runtime.getService('session').isLoggedIn()) {
                    var loginParams = {};
                    if (handler.request.path) {
                        loginParams.nextrequest = JSON.stringify(handler.request);
                    }
                    runtime.send('app', 'navigate', {
                        path: 'login',
                        // TODO: path needs to be the path + params
                        params: loginParams
                    });
                    return;
                }
            }
            var route = {
                routeHandler: handler
            };
            if (handler.route.redirect) {
                runtime.send('app', 'route-redirect', route);
            } else if (handler.route.widget) {
                runtime.send('app', 'route-widget', route);
            } else if (handler.route.handler) {
                runtime.send('app', 'route-handler', route);
            }
            //runtime.send('app', 'new-route', {
            //    routeHandler: handler
            //});
        }
        function installRoute(route) {
            return Promise.try(function () {
                if (route.widget) {
                    // ADD ROUTE WIDGET HERE...
                    router.addRoute(route);
                    return true;
//                    router.addRoute({
//                        path: route.path,
//                        queryParams: route.queryParams,
//                        config: {
//                        },
//                        widget: route.widget
//                    });
                } else if (route.redirectHandler) {
                    router.addRoute(route);
                    return true;
                } else {
                    throw new lang.UIError({
                        type: 'ConfiguratonError',
                        name: 'RouterConfigurationError',
                        source: 'installRoute',
                        message: 'invalid route',
                        suggestion: 'Fix the plugin which specified this route.',
                        data: route
                    });
                }
            });
        }
        function installRoutes(routes) {
            if (!routes) {
                return;
            }
            return routes.map(function (route) {
                return installRoute(route);
            });
        }
        function pluginHandler(pluginConfig) {
            return installRoutes(pluginConfig);
        }

        runtime.recv('app', 'do-route', function () {
            doRoute();
        });

        runtime.recv('app', 'new-route', function (data) {
            if (data.routeHandler.route.redirect) {
                send('app', 'route-redirect', data);
            } else if (data.routeHandler.route.widget) {
                send('app', 'route-widget', data);
            } else if (data.routeHandler.route.handler) {
                send('app', 'route-handler', data);
            }
        });

        runtime.recv('app', 'route-redirect', function (data) {
            runtime.send('app', 'navigate', {
                path: data.routeHandler.route.redirect.path,
                params: data.routeHandler.route.redirect.params
            });
        });

        //runtime.recv('app', 'route-handler', function (data) {
        //
        //})

        runtime.recv('app', 'navigate', function (data) {
            router.navigateTo(data);
        });

        runtime.recv('app', 'redirect', function (data) {
            router.redirectTo(data.url, data.new_window);
        });

        window.addEventListener('hashchange', function (e) {
            // $(window).on('hashchange', function () {
            // NB this is called AFTER it has changed. The browser will do nothing by
            // default
            doRoute();
        });

        return {
            pluginHandler: pluginHandler
        };
    }
    return {
        make: function (config) {
            return factory(config);
        }
    };
});<commit_msg>add start/stop methods to router to avoid warning, and move event  registration and unregistration into them.<commit_after>define([
    'bluebird',
    'kb/common/router',
    'kb/common/lang'
], function (Promise, Router, lang) {
    function factory(config) {
        var runtime = config.runtime,
            router = Router.make(config),
            receivers = [],
            eventListeners = [];

        function doRoute() {
            var handler;
            try {
                handler = router.findCurrentRoute();
            } catch (ex) {
                console.error(ex);
                if (ex instanceof Router.NotFoundException) {
                    runtime.send('app', 'route-not-found', ex);
                    return;
                } else {
                    throw ex;
                }
            }
            if (handler.route.authorization) {
                if (!runtime.getService('session').isLoggedIn()) {
                    var loginParams = {};
                    if (handler.request.path) {
                        loginParams.nextrequest = JSON.stringify(handler.request);
                    }
                    runtime.send('app', 'navigate', {
                        path: 'login',
                        // TODO: path needs to be the path + params
                        params: loginParams
                    });
                    return;
                }
            }
            var route = {
                routeHandler: handler
            };
            if (handler.route.redirect) {
                runtime.send('app', 'route-redirect', route);
            } else if (handler.route.widget) {
                runtime.send('app', 'route-widget', route);
            } else if (handler.route.handler) {
                runtime.send('app', 'route-handler', route);
            }
            //runtime.send('app', 'new-route', {
            //    routeHandler: handler
            //});
        }
        function installRoute(route) {
            return Promise.try(function () {
                if (route.widget) {
                    // ADD ROUTE WIDGET HERE...
                    router.addRoute(route);
                    return true;
//                    router.addRoute({
//                        path: route.path,
//                        queryParams: route.queryParams,
//                        config: {
//                        },
//                        widget: route.widget
//                    });
                } else if (route.redirectHandler) {
                    router.addRoute(route);
                    return true;
                } else {
                    throw new lang.UIError({
                        type: 'ConfiguratonError',
                        name: 'RouterConfigurationError',
                        source: 'installRoute',
                        message: 'invalid route',
                        suggestion: 'Fix the plugin which specified this route.',
                        data: route
                    });
                }
            });
        }
        function installRoutes(routes) {
            if (!routes) {
                return;
            }
            return routes.map(function (route) {
                return installRoute(route);
            });
        }
        function pluginHandler(pluginConfig) {
            return installRoutes(pluginConfig);
        }

        function start() {
            runtime.recv('app', 'do-route', function () {
                doRoute();
            });

            runtime.recv('app', 'new-route', function (data) {
                if (data.routeHandler.route.redirect) {
                    send('app', 'route-redirect', data);
                } else if (data.routeHandler.route.widget) {
                    send('app', 'route-widget', data);
                } else if (data.routeHandler.route.handler) {
                    send('app', 'route-handler', data);
                }
            });

            runtime.recv('app', 'route-redirect', function (data) {
                runtime.send('app', 'navigate', {
                    path: data.routeHandler.route.redirect.path,
                    params: data.routeHandler.route.redirect.params
                });
            });

            runtime.recv('app', 'navigate', function (data) {
                router.navigateTo(data);
            });

            runtime.recv('app', 'redirect', function (data) {
                router.redirectTo(data.url, data.new_window);
            });

            eventListeners.push({
                target: window,
                type: 'hashchange',
                listener: function (e) {
                    // $(window).on('hashchange', function () {
                    // NB this is called AFTER it has changed. The browser will do nothing by
                    // default
                    doRoute();
                }
            });
            eventListeners.forEach(function(listener) {
                listener.target.addEventListener(listener.type, listener.listener);
            });
        }

        function stop() {
            receivers.forEach(function (receiver) {
                if (receiver) {
                    runtime.drop(receiver);
                }
            });
            eventListeners.forEach(function (listener) {
                listener.target.removeEventListener(listener.type, listener.listener);
            });
        }

        return {
            pluginHandler: pluginHandler,
            start: start,
            stop: stop
        };
    }
    return {
        make: function (config) {
            return factory(config);
        }
    };
});<|endoftext|>


<commit_before>'use strict';

// Require all components to ensure javascript load ordering
require('./app');
require('./image');
require('./collection');
require('./dbxref');
require('./errors');
require('./footer');
require('./globals');
require('./home');
require('./item');
require('./page');
require('./mixins');
require('./statuslabel');
require('./search');
require('./publication');
require('./curator');
require('./curation_central');
require('./variant_central');
require('./create_gene_disease');
require('./dashboard');
require('./gdm');
require('./group_curation');
require('./group_submit');
require('./family_curation');
require('./family_submit');
require('./individual_curation');
require('./individual_submit');
require('./case_control_curation');
require('./case_control_submit');
require('./experimental_curation');
require('./experimental_submit');
require('./variant_curation');
require('./testing');
require('./edit');
require('./inputs');
require('./provisional_curation');
require('./add_curator');
require('./select_variant');

module.exports = require('./app');
<commit_msg>Added 'add-disease' page<commit_after>'use strict';

// Require all components to ensure javascript load ordering
require('./app');
require('./image');
require('./collection');
require('./dbxref');
require('./errors');
require('./footer');
require('./globals');
require('./home');
require('./item');
require('./page');
require('./mixins');
require('./statuslabel');
require('./search');
require('./publication');
require('./curator');
require('./curation_central');
require('./variant_central');
require('./create_gene_disease');
require('./dashboard');
require('./gdm');
require('./group_curation');
require('./group_submit');
require('./family_curation');
require('./family_submit');
require('./individual_curation');
require('./individual_submit');
require('./case_control_curation');
require('./case_control_submit');
require('./experimental_curation');
require('./experimental_submit');
require('./variant_curation');
require('./testing');
require('./edit');
require('./inputs');
require('./provisional_curation');
require('./add_curator');
require('./select_variant');
require('./add_disease');

module.exports = require('./app');
<|endoftext|>


<commit_before>(function() {

function $(selector) {
    return document.querySelector(selector);
}

function new_elem(type) {
    return document.createElement(type);
}

function httpGetAsync(theUrl, callback, callback_data, page)
{
    var xmlHttp = new XMLHttpRequest();
    xmlHttp.onreadystatechange = function() {
        if (xmlHttp.readyState == 4 && xmlHttp.status == 200)
            callback(xmlHttp.responseText, callback_data);
    }
    xmlHttp.open("GET", theUrl, true); // true for asynchronous
    xmlHttp.send(null);
}

function features_json_received(responseText) {
    const response = JSON.parse(responseText);
    let num_cols = 2;
    const table_head_vardata = $('#feature-support-table > thead > #ua_names');
    const table_head_row = $('#feature-support-table > thead > tr');
    const headers = new Set([...response.user_agents, ...response.feedback]);

    table_head_row.innerHTML += `User Agents
        Other reviewers	`;

    for (ua of headers) {
        const th = new_elem('th');
        th.className = num_cols >= response.user_agents.length + 2 ? 'other' : 'UA';
        th.innerHTML = ua;
        table_head_vardata.appendChild(th);
        num_cols ++;
    }

    for (let category of response.svg2_categories) {
        const tbody = new_elem('tbody');
        const tr = new_elem('tr');

        tr.className = 'category';
        tr.innerHTML = `<td colspan=${num_cols}>${category.category}</td>`;

        tbody.appendChild(tr);
        $('#feature-support-table').appendChild(tbody);

        for (let feature of category.features) {
            const tr = new_elem('tr');
            tr.innerHTML = `<td class='feature'><p>${feature.name}</p></td><td></td>`;
            tbody.appendChild(tr);

            if (feature.at_risk && feature.at_risk.toLowerCase() == "true") {
                tr.className = 'at_risk';
            }

            for (let ua of response.user_agents) {
                const td = new_elem('td');
                td.id = ua;
                td.className = 'UA none';
                tr.appendChild(td);
            }

            if (feature.support) {
                for (let ua_support of feature.support) {
                    if (!ua_support.level)
                        console.log(feature);

                    const ua_td = tr.querySelector(`#${ua_support.name}`);
                    switch (ua_support.level.toLowerCase()) {
                        case 'full':
                        case 'partial':
                            ua_td.innerHTML = ua_support.level;
                            ua_td.className = ua_support.level;
                            break;
                        case 'n/a':
                            ua_td.innerHTML = ua_support.level;
                            ua_td.className = 'not_applicable';
                            break;
                    }

                    if (ua_support.tracker) {
                        function tracker_links(tracker) {
                            let resolved = '';
                            for (t of Array.isArray(tracker) ? tracker : [tracker]) {
                                resolved += ` [<a href='${t}'>track</a>]`;
                            }
                            return resolved;
                        }
                        ua_td.innerHTML = ua_td.innerHTML + tracker_links(ua_support.tracker);
                    }
                }
            }

            if (feature.feedback) {
                for (let feedback of feature.feedback) {
                    const ua_td = tr.querySelector(`#${feedback.name}`);
                    if (!ua_td) continue;

                    ua_td.className = ua_td.className + ` priority_${feedback.priority}`;
                }
            }
        }
    }
}

window.onload = function() {
    httpGetAsync(
        'svg2-new-features-processed.json',
        features_json_received
    );
}

})();
<commit_msg>Add table cell for non UA feedback.<commit_after>(function() {

function $(selector) {
    return document.querySelector(selector);
}

function new_elem(type) {
    return document.createElement(type);
}

function httpGetAsync(theUrl, callback, callback_data, page)
{
    var xmlHttp = new XMLHttpRequest();
    xmlHttp.onreadystatechange = function() {
        if (xmlHttp.readyState == 4 && xmlHttp.status == 200)
            callback(xmlHttp.responseText, callback_data);
    }
    xmlHttp.open("GET", theUrl, true); // true for asynchronous
    xmlHttp.send(null);
}

function features_json_received(responseText) {
    const response = JSON.parse(responseText);
    let num_cols = 2;
    const table_head_vardata = $('#feature-support-table > thead > #ua_names');
    const table_head_row = $('#feature-support-table > thead > tr');
    const headers = new Set([...response.user_agents, ...response.feedback]);

    table_head_row.innerHTML += `User Agents
        Other reviewers	`;

    for (ua of headers) {
        const th = new_elem('th');
        th.className = num_cols >= response.user_agents.length + 2 ? 'other' : 'UA';
        th.innerHTML = ua;
        table_head_vardata.appendChild(th);
        num_cols ++;
    }

    for (let category of response.svg2_categories) {
        const tbody = new_elem('tbody');
        const tr = new_elem('tr');

        tr.className = 'category';
        tr.innerHTML = `<td colspan=${num_cols}>${category.category}</td>`;

        tbody.appendChild(tr);
        $('#feature-support-table').appendChild(tbody);

        for (let feature of category.features) {
            const tr = new_elem('tr');
            tr.innerHTML = `<td class='feature'><p>${feature.name}</p></td><td></td>`;
            tbody.appendChild(tr);

            if (feature.at_risk && feature.at_risk.toLowerCase() == "true") {
                tr.className = 'at_risk';
            }

            for (let ua of response.user_agents) {
                const td = new_elem('td');
                td.id = ua;
                td.className = 'UA none';
                tr.appendChild(td);
            }

            if (feature.support) {
                for (let ua_support of feature.support) {
                    if (!ua_support.level)
                        console.log(feature);

                    const ua_td = tr.querySelector(`#${ua_support.name}`);
                    switch (ua_support.level.toLowerCase()) {
                        case 'full':
                        case 'partial':
                            ua_td.innerHTML = ua_support.level;
                            ua_td.className = ua_support.level;
                            break;
                        case 'n/a':
                            ua_td.innerHTML = ua_support.level;
                            ua_td.className = 'not_applicable';
                            break;
                    }

                    if (ua_support.tracker) {
                        function tracker_links(tracker) {
                            let resolved = '';
                            for (t of Array.isArray(tracker) ? tracker : [tracker]) {
                                resolved += ` [<a href='${t}'>track</a>]`;
                            }
                            return resolved;
                        }
                        ua_td.innerHTML = ua_td.innerHTML + tracker_links(ua_support.tracker);
                    }
                }
            }

            if (feature.feedback) {
                for (let feedback of feature.feedback) {
                    let ua_td = tr.querySelector(`#${feedback.name}`);
                    if (!ua_td) {
                        ua_td = new_elem('td');
                        ua_td.id = feedback;
                        tr.appendChild(ua_td);
                    }
                    ua_td.className = ua_td.className + ` priority_${feedback.priority}`;
                }
            }
        }
    }
}

window.onload = function() {
    httpGetAsync(
        'svg2-new-features-processed.json',
        features_json_received
    );
}

})();
<|endoftext|>


<commit_before>/*
Copyright 2018 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.

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.
*/

const assert = require('assert');

module.exports = async function signup(session, username, password, homeserver) {
    session.log.step("signs up");
    await session.goto(session.url('/#/register'));
    // change the homeserver by clicking the advanced section
    if (homeserver) {
        const advancedButton = await session.query('.mx_ServerTypeSelector_type_Advanced');
        await advancedButton.click();

        // depending on what HS is configured as the default, the advanced registration
        // goes the HS/IS entry directly (for matrix.org) or takes you to the user/pass entry (not matrix.org).
        // To work with both, we look for the "Change" link in the user/pass entry but don't fail when we can't find it
        // As this link should be visible immediately, and to not slow down the case where it isn't present,
        // pick a lower timeout of 5000ms
        try {
            const changeHsField = await session.query('.mx_AuthBody_editServerDetails', 5000);
            if (changeHsField) {
                await changeHsField.click();
            }
        } catch (err) {}

        const hsInputField = await session.query('#mx_ServerConfig_hsUrl');
        await session.replaceInputText(hsInputField, homeserver);
        const nextButton = await session.query('.mx_Login_submit');
        // accept homeserver
        await nextButton.click();
    }
    //fill out form
    const usernameField = await session.query("#mx_RegistrationForm_username");
    const passwordField = await session.query("#mx_RegistrationForm_password");
    const passwordRepeatField = await session.query("#mx_RegistrationForm_passwordConfirm");
    await session.replaceInputText(usernameField, username);
    await session.replaceInputText(passwordField, password);
    await session.replaceInputText(passwordRepeatField, password);
    //wait 300ms because Registration/ServerConfig have a 250ms
    //delay to internally set the homeserver url
    //see Registration::render and ServerConfig::props::delayTimeMs
    await session.delay(300);
    /// focus on the button to make sure error validation
    /// has happened before checking the form is good to go
    const registerButton = await session.query('.mx_Login_submit');
    await registerButton.focus();
    // Password validation is async, wait for it to complete before submit
    await session.query(".mx_Field_valid #mx_RegistrationForm_password");
    //check no errors
    const errorText = await session.tryGetInnertext('.mx_Login_error');
    assert.strictEqual(errorText, null);
    //submit form
    //await page.screenshot({path: "beforesubmit.png", fullPage: true});
    await registerButton.click();

    //confirm dialog saying you cant log back in without e-mail
    const continueButton = await session.query('.mx_QuestionDialog button.mx_Dialog_primary');
    await continueButton.click();

    //find the privacy policy checkbox and check it
    const policyCheckbox = await session.query('.mx_InteractiveAuthEntryComponents_termsPolicy input');
    await policyCheckbox.click();

    //now click the 'Accept' button to agree to the privacy policy
    const acceptButton = await session.query('.mx_InteractiveAuthEntryComponents_termsSubmit');
    await acceptButton.click();

    //plow through cross-signing setup by entering arbitrary details
    //TODO: It's probably important for the tests to know the passphrase
    const xsigningPassphrase = 'a7eaXcjpa9!Yl7#V^h$B^%dovHUVX'; // https://xkcd.com/221/
    let passphraseField = await session.query('.mx_CreateSecretStorageDialog_passPhraseField input');
    await session.replaceInputText(passphraseField, xsigningPassphrase);
    await session.delay(1000); // give it a second to analyze our passphrase for security
    let xsignContButton = await session.query('.mx_CreateSecretStorageDialog .mx_Dialog_buttons .mx_Dialog_primary');
    await xsignContButton.click();

    //repeat passphrase entry
    passphraseField = await session.query('.mx_CreateSecretStorageDialog_passPhraseField input');
    await session.replaceInputText(passphraseField, xsigningPassphrase);
    await session.delay(1000); // give it a second to analyze our passphrase for security
    xsignContButton = await session.query('.mx_CreateSecretStorageDialog .mx_Dialog_buttons .mx_Dialog_primary');
    await xsignContButton.click();

    //ignore the recovery key
    //TODO: It's probably important for the tests to know the recovery key
    const copyButton = await session.query('.mx_CreateSecretStorageDialog_recoveryKeyButtons_copyBtn');
    await copyButton.click();

    //acknowledge that we copied the recovery key to a safe place
    const copyContinueButton = await session.query('.mx_CreateSecretStorageDialog .mx_Dialog_primary');
    await copyContinueButton.click();

    //acknowledge that we're done cross-signing setup and our keys are safe
    const doneOkButton = await session.query('.mx_CreateSecretStorageDialog .mx_Dialog_primary');
    await doneOkButton.click();

    //wait for registration to finish so the hash gets set
    //onhashchange better?

    const foundHomeUrl = await session.poll(async () => {
        const url = session.page.url();
        return url === session.url('/#/home');
    });
    assert(foundHomeUrl);
    session.log.done();
};
<commit_msg>Fix end to end tests for new UI<commit_after>/*
Copyright 2018 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.

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.
*/

const assert = require('assert');

module.exports = async function signup(session, username, password, homeserver) {
    session.log.step("signs up");
    await session.goto(session.url('/#/register'));
    // change the homeserver by clicking the advanced section
    if (homeserver) {
        const advancedButton = await session.query('.mx_ServerTypeSelector_type_Advanced');
        await advancedButton.click();

        // depending on what HS is configured as the default, the advanced registration
        // goes the HS/IS entry directly (for matrix.org) or takes you to the user/pass entry (not matrix.org).
        // To work with both, we look for the "Change" link in the user/pass entry but don't fail when we can't find it
        // As this link should be visible immediately, and to not slow down the case where it isn't present,
        // pick a lower timeout of 5000ms
        try {
            const changeHsField = await session.query('.mx_AuthBody_editServerDetails', 5000);
            if (changeHsField) {
                await changeHsField.click();
            }
        } catch (err) {}

        const hsInputField = await session.query('#mx_ServerConfig_hsUrl');
        await session.replaceInputText(hsInputField, homeserver);
        const nextButton = await session.query('.mx_Login_submit');
        // accept homeserver
        await nextButton.click();
    }
    //fill out form
    const usernameField = await session.query("#mx_RegistrationForm_username");
    const passwordField = await session.query("#mx_RegistrationForm_password");
    const passwordRepeatField = await session.query("#mx_RegistrationForm_passwordConfirm");
    await session.replaceInputText(usernameField, username);
    await session.replaceInputText(passwordField, password);
    await session.replaceInputText(passwordRepeatField, password);
    //wait 300ms because Registration/ServerConfig have a 250ms
    //delay to internally set the homeserver url
    //see Registration::render and ServerConfig::props::delayTimeMs
    await session.delay(300);
    /// focus on the button to make sure error validation
    /// has happened before checking the form is good to go
    const registerButton = await session.query('.mx_Login_submit');
    await registerButton.focus();
    // Password validation is async, wait for it to complete before submit
    await session.query(".mx_Field_valid #mx_RegistrationForm_password");
    //check no errors
    const errorText = await session.tryGetInnertext('.mx_Login_error');
    assert.strictEqual(errorText, null);
    //submit form
    //await page.screenshot({path: "beforesubmit.png", fullPage: true});
    await registerButton.click();

    //confirm dialog saying you cant log back in without e-mail
    const continueButton = await session.query('.mx_QuestionDialog button.mx_Dialog_primary');
    await continueButton.click();

    //find the privacy policy checkbox and check it
    const policyCheckbox = await session.query('.mx_InteractiveAuthEntryComponents_termsPolicy input');
    await policyCheckbox.click();

    //now click the 'Accept' button to agree to the privacy policy
    const acceptButton = await session.query('.mx_InteractiveAuthEntryComponents_termsSubmit');
    await acceptButton.click();

    let xsignContButton = await session.query('.mx_CreateSecretStorageDialog .mx_Dialog_buttons .mx_Dialog_primary');
    await xsignContButton.click();

    //ignore the recovery key
    //TODO: It's probably important for the tests to know the recovery key
    const copyButton = await session.query('.mx_CreateSecretStorageDialog_recoveryKeyButtons_copyBtn');
    await copyButton.click();

    //acknowledge that we copied the recovery key to a safe place
    const copyContinueButton = await session.query('.mx_CreateSecretStorageDialog .mx_Dialog_buttons .mx_Dialog_primary');
    await copyContinueButton.click();

    //wait for registration to finish so the hash gets set
    //onhashchange better?

    const foundHomeUrl = await session.poll(async () => {
        const url = session.page.url();
        return url === session.url('/#/home');
    });
    assert(foundHomeUrl);
    session.log.done();
};
<|endoftext|>


<commit_before>describe('HEAD requests', function () {
    var _ = require('lodash'),
        testrun;

    before(function (done) {
        this.run({
            collection: {
                item: [{
                    request: {
                        url: 'http://google.com',
                        method: 'HEAD',
                        body: {
                            mode: 'formdata',
                            formdata: []
                        }
                    }
                }, {
                    request: {
                        url: 'http://github.com',
                        method: 'HEAD',
                        body: {
                            mode: 'formdata',
                            formdata: []
                        }
                    }
                }]
            }
        }, function (err, results) {
            testrun = results;
            done(err);
        });
    });

    it('must have completed the HEAD requests successfully', function () {
        expect(testrun).be.ok();
        expect(testrun.request.calledTwice).be.ok();

        expect(testrun.request.getCall(0).args[0]).to.be(null);
        expect(testrun.request.getCall(0).args[2].code).to.be(200);

        expect(testrun.request.getCall(1).args[0]).to.be(null);
        expect(testrun.request.getCall(1).args[2].code).to.be(200);
    });

    it('must have completed the run', function () {
        expect(testrun).be.ok();
        expect(testrun.done.calledOnce).be.ok();
        expect(testrun.done.getCall(0).args[0]).to.be(null);
        expect(testrun.start.calledOnce).be.ok();
    });
});
<commit_msg>removed var declaration<commit_after>describe('HEAD requests', function () {
    var testrun;

    before(function (done) {
        this.run({
            collection: {
                item: [{
                    request: {
                        url: 'http://google.com',
                        method: 'HEAD',
                        body: {
                            mode: 'formdata',
                            formdata: []
                        }
                    }
                }, {
                    request: {
                        url: 'http://github.com',
                        method: 'HEAD',
                        body: {
                            mode: 'formdata',
                            formdata: []
                        }
                    }
                }]
            }
        }, function (err, results) {
            testrun = results;
            done(err);
        });
    });

    it('must have completed the HEAD requests successfully', function () {
        expect(testrun).be.ok();
        expect(testrun.request.calledTwice).be.ok();

        expect(testrun.request.getCall(0).args[0]).to.be(null);
        expect(testrun.request.getCall(0).args[2].code).to.be(200);

        expect(testrun.request.getCall(1).args[0]).to.be(null);
        expect(testrun.request.getCall(1).args[2].code).to.be(200);
    });

    it('must have completed the run', function () {
        expect(testrun).be.ok();
        expect(testrun.done.calledOnce).be.ok();
        expect(testrun.done.getCall(0).args[0]).to.be(null);
        expect(testrun.start.calledOnce).be.ok();
    });
});
<|endoftext|>


<commit_before>var supertest = require("supertest");
var should = require("should");

var server = supertest.agent("http://localhost:3000");

describe("Test of Crisis",function(){

  it("Deve retornar erro de falta de parametros informados",function(done){

    // calling home page api
    server
    .post("/rest/crisis")
    .send({'nome': 'João', 'email': 'teste@teste.com'})
    .expect("Content-categoria",/json/)
    .set('Accept', 'application/json')
    .end(function(err,res){

      res.should.be.json;
        res.status.should.equal(400);
      // Error key should havebe false.
      res.body.should.have.property('success');
      res.body.success.should.equal(false);
      res.body.should.have.property('message');
      res.body.message.should.equal('Invalid value data fields.');

      done();
    });
  });

  it("Deve retornar erro ao passar email inválido",function(done){

    var crisis_param = {
      descricao: 'Teste descricao',
      categoria: 4,
      nome: 'Teste',
      email: 'testegmail.com',
      telefone: 111111,
      latitude: 40.0,
      longitude: 50.0
    };

    // calling home page api
    server
    .post("/rest/crisis")
    .send(crisis_param)
    .expect("Content-categoria",/json/)
    .set('Accept', 'application/json')
    .end(function(err,res){

      res.should.be.json;
        res.status.should.equal(400);
      // Error key should havebe false.
      res.body.should.have.property('success');
      res.body.success.should.equal(false);
      res.body.should.have.property('message');
      res.body.message.should.equal('Invalid value data fields.');

      done();
    });
  });

  it("Deve retornar erro ao passar tipo de crise fora do intervalo de 1 à 8",function(done){

    var crisis_param = {
      descricao: 'Teste de descricao',
      nome: 'Teste',
      email: 'teste@gmail.com',
      telefone: 111111,
      longitude: 50.0,
      categoria: 9,
      latitude: 40.0
    };

    // calling home page api
    server
    .post("/rest/crisis")
    .send(crisis_param)
    .expect("Content-categoria",/json/)
    .set('Accept', 'application/json')
    .end(function(err,res){

      res.should.be.json;
      res.status.should.equal(400);
      // Error key should havebe false.
      res.body.should.have.property('success');
      res.body.success.should.equal(false);
      res.body.should.have.property('message');
      res.body.message.should.equal('Invalid value data fields.');

      done();
    });
  });

  it("Deve retornar ok",function(done){

    var crisis_param = {
      descricao:'teste descricao',
      nome: 'Teste',
      email: 'teste@gmail.com',
      telefone: 111111,
      latitude: 40.0,
      categoria: 3,
      longitude: 50.0,
      fotografia : '77+9UE5HDQoaCgAAAA1JSERSAAAADAAAABAIBgAAACJh77+9BwAAAARnQU1BAADvv73vv70L77+9YQUAAAAgY0hSTQAAeiYAAO+/ve+/vQAA77+9AAAA77+977+9AAB1MAAA77+9YAAAOu+/vQAAF3Dvv73vv71RPAAAAAlwSFlzAAAXEgAAFxIBZ++/ve+/vVIAAAFZaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA1LjQuMCI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIj4KICAgICAgICAgPHRpZmY6T3JpZW50YXRpb24+MTwvdGlmZjpPcmllbnRhdGlvbj4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+Ckzvv70nWQAAAk5JREFUKBVVUk1PE1EUPe+/ve+/ve+/vTDvv73vv71K77+9UEPvv71R77+90JTvv73vv70qau+/vQ0bE1bvv70FfwF/77+977+9QNyx77+9aEx077+9RkNY77+977+9BRpFTTDvv700FAUtae+/vX7vv71H77+9M++/vTMlanzvv71577+977+977+9e8+977+9au+/ve+/vVfvv70577+977+9aSLvv71KYVZo77+9z6ZlYe+/ve+/ve+/ve+/vV4XB++/ve+/ve+/ve+/ve+/ve+/vSgpFO+/vSfvv71OJDB377+9xIfvv70tVCPvv73vv73vv70yRu+/vWYR77+977+9Je+/vQbvv71B77+977+9SgU/X2/vv73vv73vv73vv71f77+977+977+9RzDvv71dRe+/vTjvv707DRpkS0rvv70e77+9MO+/vXAD77+9du+/ve+/ve+/ve+/vX3vv71yWVw6b++/vX1C77+977+9bDrvv73vv71ZEu+/ve+/ve+/vUYTRwTvv70/X++/ve+/ve+/vQLvv70uMGoa77+977+9Pj7vv70DEu+/vTEE77+9EO+/ve+/vS8w77+977+9Ru+/vXQF77+977+977+977+977+9dT3vv73vv70577+9BnBJEGDvv71cIt6C77+9JO+/vSbvv73vv73vv73ktIPvv71NzZrvv73vv73vv70EbhoD77+9YO+/vWYMZCIJIe+/vX3vv70WVnQHIgxQ77+977+9EEbvv70w3rjvv70jFe+/vRkM77+977+977+977+9DQZ4Mj3vv73vv73vv71777+9M++/ve+/vULvv71YJgLvv71F77+977+9Z++/ve+/vRAmJe+/ve+/vSJNEwHvv70LTO+/vXJiGk/vv70S77+9H2BAae+/vUXvv73vv70ifu+/vRbvv71E77+977+977+9Ue+/vSfvv73vv71F77+9Mu+/ve+/vRRL77+9IGnvv73vv71F77+977+977+9xLZIIEfvv73vv70x77+9HGlw77+977+977+9Imnvv73vv73vv71xbFzvv73vv73vv70477+977+9KxU6BO+/ve+/vXfvv70jdknvv73Qp3nvv716HTPvv73vv73vv718CXXvv73vv73vv73vv70GbO+/ve+/ve+/ve+/vRHvv70exqckGicgORrvv73vv73vv70o77+9L++/ve+/ve+/vU7vv70SRu+/vVfvv70R77+9AdG5NBTvv73vv73vv73vv70G77+9Lnd777+9f++/vUfvv73vv73VqlUYDO+/vWwk77+9Le+/vTTvv71zWu+/ve+/ve+/vVPvv73vv70MDu+/ve+/ve+/ve+/vT0+77+977+977+9Q++/ve+/vT4K77+9RSAzDu+/ve+/vXHvv73GtVgnzYt+A0fvv73vv70T77+9AO+/vWUAAAAASUVORO+/vUJg77+9'
    };

    // calling home page api
    server
    .post("/rest/crisis")
    .send(crisis_param)
    .expect("Content-categoria",/json/)
    .set('Accept', 'application/json')
    .end(function(err,res){

      res.should.be.json;
      res.status.should.equal(201);
      res.body.should.have.property('message');

      done();
    });
  });

});
<commit_msg>Adicionado caso de teste US17 - SP02<commit_after>var supertest = require("supertest");
var should = require("should");

var server = supertest.agent("http://localhost:3000");

describe("Test of Crisis",function(){

  it("Deve retornar erro de falta de parametros informados",function(done){

    // calling home page api
    server
    .post("/rest/crisis")
    .send({'nome': 'João', 'email': 'teste@teste.com'})
    .expect("Content-categoria",/json/)
    .set('Accept', 'application/json')
    .end(function(err,res){

      res.should.be.json;
        res.status.should.equal(400);
      // Error key should havebe false.
      res.body.should.have.property('success');
      res.body.success.should.equal(false);
      res.body.should.have.property('message');
      res.body.message.should.equal('Invalid value data fields.');

      done();
    });
  });

  it("Deve retornar erro ao passar email inválido",function(done){

    var crisis_param = {
      descricao: 'Teste descricao',
      categoria: 4,
      nome: 'Teste',
      email: 'testegmail.com',
      telefone: 111111,
      latitude: 40.0,
      longitude: 50.0
    };

    // calling home page api
    server
    .post("/rest/crisis")
    .send(crisis_param)
    .expect("Content-categoria",/json/)
    .set('Accept', 'application/json')
    .end(function(err,res){

      res.should.be.json;
        res.status.should.equal(400);
      // Error key should havebe false.
      res.body.should.have.property('success');
      res.body.success.should.equal(false);
      res.body.should.have.property('message');
      res.body.message.should.equal('Invalid value data fields.');

      done();
    });
  });

  it("Deve retornar erro ao passar tipo de crise fora do intervalo de 1 à 8",function(done){

    var crisis_param = {
      descricao: 'Teste de descricao',
      nome: 'Teste',
      email: 'teste@gmail.com',
      telefone: 111111,
      longitude: 50.0,
      categoria: 9,
      latitude: 40.0
    };

    // calling home page api
    server
    .post("/rest/crisis")
    .send(crisis_param)
    .expect("Content-categoria",/json/)
    .set('Accept', 'application/json')
    .end(function(err,res){

      res.should.be.json;
      res.status.should.equal(400);
      // Error key should havebe false.
      res.body.should.have.property('success');
      res.body.success.should.equal(false);
      res.body.should.have.property('message');
      res.body.message.should.equal('Invalid value data fields.');

      done();
    });
  });

  it("Deve retornar ok",function(done){

    var crisis_param = {
      descricao:'teste descricao',
      nome: 'Teste',
      email: 'teste@gmail.com',
      telefone: 111111,
      latitude: 40.0,
      categoria: 3,
      longitude: 50.0,
      fotografia : '77+9UE5HDQoaCgAAAA1JSERSAAAADAAAABAIBgAAACJh77+9BwAAAARnQU1BAADvv73vv70L77+9YQUAAAAgY0hSTQAAeiYAAO+/ve+/vQAA77+9AAAA77+977+9AAB1MAAA77+9YAAAOu+/vQAAF3Dvv73vv71RPAAAAAlwSFlzAAAXEgAAFxIBZ++/ve+/vVIAAAFZaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA1LjQuMCI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIj4KICAgICAgICAgPHRpZmY6T3JpZW50YXRpb24+MTwvdGlmZjpPcmllbnRhdGlvbj4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+Ckzvv70nWQAAAk5JREFUKBVVUk1PE1EUPe+/ve+/ve+/vTDvv73vv71K77+9UEPvv71R77+90JTvv73vv70qau+/vQ0bE1bvv70FfwF/77+977+9QNyx77+9aEx077+9RkNY77+977+9BRpFTTDvv700FAUtae+/vX7vv71H77+9M++/vTMlanzvv71577+977+977+9e8+977+9au+/ve+/vVfvv70577+977+9aSLvv71KYVZo77+9z6ZlYe+/ve+/ve+/ve+/vV4XB++/ve+/ve+/ve+/ve+/ve+/vSgpFO+/vSfvv71OJDB377+9xIfvv70tVCPvv73vv73vv70yRu+/vWYR77+977+9Je+/vQbvv71B77+977+9SgU/X2/vv73vv73vv73vv71f77+977+977+9RzDvv71dRe+/vTjvv707DRpkS0rvv70e77+9MO+/vXAD77+9du+/ve+/ve+/ve+/vX3vv71yWVw6b++/vX1C77+977+9bDrvv73vv71ZEu+/ve+/ve+/vUYTRwTvv70/X++/ve+/ve+/vQLvv70uMGoa77+977+9Pj7vv70DEu+/vTEE77+9EO+/ve+/vS8w77+977+9Ru+/vXQF77+977+977+977+977+9dT3vv73vv70577+9BnBJEGDvv71cIt6C77+9JO+/vSbvv73vv73vv73ktIPvv71NzZrvv73vv73vv70EbhoD77+9YO+/vWYMZCIJIe+/vX3vv70WVnQHIgxQ77+977+9EEbvv70w3rjvv70jFe+/vRkM77+977+977+977+9DQZ4Mj3vv73vv73vv71777+9M++/ve+/vULvv71YJgLvv71F77+977+9Z++/ve+/vRAmJe+/ve+/vSJNEwHvv70LTO+/vXJiGk/vv70S77+9H2BAae+/vUXvv73vv70ifu+/vRbvv71E77+977+977+9Ue+/vSfvv73vv71F77+9Mu+/ve+/vRRL77+9IGnvv73vv71F77+977+977+9xLZIIEfvv73vv70x77+9HGlw77+977+977+9Imnvv73vv73vv71xbFzvv73vv73vv70477+977+9KxU6BO+/ve+/vXfvv70jdknvv73Qp3nvv716HTPvv73vv73vv718CXXvv73vv73vv73vv70GbO+/ve+/ve+/ve+/vRHvv70exqckGicgORrvv73vv73vv70o77+9L++/ve+/ve+/vU7vv70SRu+/vVfvv70R77+9AdG5NBTvv73vv73vv73vv70G77+9Lnd777+9f++/vUfvv73vv73VqlUYDO+/vWwk77+9Le+/vTTvv71zWu+/ve+/ve+/vVPvv73vv70MDu+/ve+/ve+/ve+/vT0+77+977+977+9Q++/ve+/vT4K77+9RSAzDu+/ve+/vXHvv73GtVgnzYt+A0fvv73vv70T77+9AO+/vWUAAAAASUVORO+/vUJg77+9'
    };

    // calling home page api
    server
    .post("/rest/crisis")
    .send(crisis_param)
    .expect("Content-categoria",/json/)
    .set('Accept', 'application/json')
    .end(function(err,res){

      res.should.be.json;
      res.status.should.equal(201);
      res.body.should.have.property('message');

      done();
    });
  });

  it("Deve retornar crises por latitude/longitude e raio",function(done){

  var crisis_param = {
      descricao:'teste descricao',
      nome: 'Teste',
      email: 'teste@gmail.com',
      telefone: 111111,
      latitude: 40.0,
      categoria: 3,
      longitude: 50.0,
      fotografia : '77+9UE5HDQoaCgAAAA1JSERSAAAADAAAABAIBgAAACJh77+9BwAAAARnQU1BAADvv73vv70L77+9YQUAAAAgY0hSTQAAeiYAAO+/ve+/vQAA77+9AAAA77+977+9AAB1MAAA77+9YAAAOu+/vQAAF3Dvv73vv71RPAAAAAlwSFlzAAAXEgAAFxIBZ++/ve+/vVIAAAFZaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA1LjQuMCI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIj4KICAgICAgICAgPHRpZmY6T3JpZW50YXRpb24+MTwvdGlmZjpPcmllbnRhdGlvbj4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+Ckzvv70nWQAAAk5JREFUKBVVUk1PE1EUPe+/ve+/ve+/vTDvv73vv71K77+9UEPvv71R77+90JTvv73vv70qau+/vQ0bE1bvv70FfwF/77+977+9QNyx77+9aEx077+9RkNY77+977+9BRpFTTDvv700FAUtae+/vX7vv71H77+9M++/vTMlanzvv71577+977+977+9e8+977+9au+/ve+/vVfvv70577+977+9aSLvv71KYVZo77+9z6ZlYe+/ve+/ve+/ve+/vV4XB++/ve+/ve+/ve+/ve+/ve+/vSgpFO+/vSfvv71OJDB377+9xIfvv70tVCPvv73vv73vv70yRu+/vWYR77+977+9Je+/vQbvv71B77+977+9SgU/X2/vv73vv73vv73vv71f77+977+977+9RzDvv71dRe+/vTjvv707DRpkS0rvv70e77+9MO+/vXAD77+9du+/ve+/ve+/ve+/vX3vv71yWVw6b++/vX1C77+977+9bDrvv73vv71ZEu+/ve+/ve+/vUYTRwTvv70/X++/ve+/ve+/vQLvv70uMGoa77+977+9Pj7vv70DEu+/vTEE77+9EO+/ve+/vS8w77+977+9Ru+/vXQF77+977+977+977+977+9dT3vv73vv70577+9BnBJEGDvv71cIt6C77+9JO+/vSbvv73vv73vv73ktIPvv71NzZrvv73vv73vv70EbhoD77+9YO+/vWYMZCIJIe+/vX3vv70WVnQHIgxQ77+977+9EEbvv70w3rjvv70jFe+/vRkM77+977+977+977+9DQZ4Mj3vv73vv73vv71777+9M++/ve+/vULvv71YJgLvv71F77+977+9Z++/ve+/vRAmJe+/ve+/vSJNEwHvv70LTO+/vXJiGk/vv70S77+9H2BAae+/vUXvv73vv70ifu+/vRbvv71E77+977+977+9Ue+/vSfvv73vv71F77+9Mu+/ve+/vRRL77+9IGnvv73vv71F77+977+977+9xLZIIEfvv73vv70x77+9HGlw77+977+977+9Imnvv73vv73vv71xbFzvv73vv73vv70477+977+9KxU6BO+/ve+/vXfvv70jdknvv73Qp3nvv716HTPvv73vv73vv718CXXvv73vv73vv73vv70GbO+/ve+/ve+/ve+/vRHvv70exqckGicgORrvv73vv73vv70o77+9L++/ve+/ve+/vU7vv70SRu+/vVfvv70R77+9AdG5NBTvv73vv73vv73vv70G77+9Lnd777+9f++/vUfvv73vv73VqlUYDO+/vWwk77+9Le+/vTTvv71zWu+/ve+/ve+/vVPvv73vv70MDu+/ve+/ve+/ve+/vT0+77+977+977+9Q++/ve+/vT4K77+9RSAzDu+/ve+/vXHvv73GtVgnzYt+A0fvv73vv70T77+9AO+/vWUAAAAASUVORO+/vUJg77+9'
    };

    // calling home page api
    server
    .get("/rest/crisis/nearbycrisis?latitude=40.0&longitude=50.0&raio=1.0")
    .expect("Content-type",/json/)
    .end(function(err,res){

     res.should.be.json;

     done();
	});
  });
});<|endoftext|>


<commit_before>import {load, getHash} from '../../common';

describe.only('Scale - nearest neighbor', function () {
    let original;
    before(function () {
        return load('resize/5x5a_5.png').then(function (img) {
            original = img;
        });
    });

    const tests = [1, 2, 3, 4, 5, 10, 13, 18];
    tests.forEach(function (test) {
        it(`5x5 -> ${test}x${test}`, function () {
            return load('resize/5x5a_' + test + '.png').then(function (expected) {
                getHash(original.scale({
                    height: test,
                    width: test
                })).should.equal(getHash(expected));
            });
        });
    });
});
<commit_msg>remove describe.only<commit_after>import {load, getHash} from '../../common';

describe('Scale - nearest neighbor', function () {
    let original;
    before(function () {
        return load('resize/5x5a_5.png').then(function (img) {
            original = img;
        });
    });

    const tests = [1, 2, 3, 4, 5, 10, 13, 18];
    tests.forEach(function (test) {
        it(`5x5 -> ${test}x${test}`, function () {
            return load('resize/5x5a_' + test + '.png').then(function (expected) {
                getHash(original.scale({
                    height: test,
                    width: test
                })).should.equal(getHash(expected));
            });
        });
    });
});
<|endoftext|>


<commit_before>define(["Tone/core/Tone", "Tone/type/TimeBase"], function (Tone) {

	/**
	 *  @class Tone.Time is a primitive type for encoding Time values.
	 *         Tone.Time can be constructed with or without the `new` keyword. Tone.Time can be passed
	 *         into the parameter of any method which takes time as an argument.
	 *  @constructor
	 *  @extends {Tone.TimeBase}
	 *  @param  {String|Number}  val    The time value.
	 *  @param  {String=}  units  The units of the value.
	 *  @example
	 * var t = Tone.Time("4n");//a quarter note
	 */
	Tone.Time = function(val, units){
		if (this instanceof Tone.Time){

			Tone.TimeBase.call(this, val, units);

		} else {
			return new Tone.Time(val, units);
		}
	};

	Tone.extend(Tone.Time, Tone.TimeBase);

	/**
	 * Extend the base expressions
	 */
	Tone.Time.prototype._expressions = Object.assign({}, Tone.TimeBase.prototype._expressions, {
		"quantize" : {
			regexp : /^@(.+)/,
			method : function(capture){
				if (Tone.Transport){
					var quantTo = new Tone.Time(capture);
					return Tone.Transport.nextSubdivision(quantTo);
				} else {
					return 0;
				}
			}
		},
		"now" : {
			regexp : /^\+(.+)/,
			method : function(capture){
				return this._now() + (new Tone.Time(capture));
			}
		}
	});

	/**
	 *  Quantize the time by the given subdivision. Optionally add a
	 *  percentage which will move the time value towards the ideal
	 *  quantized value by that percentage.
	 *  @param  {Number|Time}  val    The subdivision to quantize to
	 *  @param  {NormalRange}  [percent=1]  Move the time value
	 *                                   towards the quantized value by
	 *                                   a percentage.
	 *  @return  {Number}  this
	 *  @example
	 * Tone.Time(21).quantize(2) //returns 22
	 * Tone.Time(0.6).quantize("4n", 0.5) //returns 0.55
	 */
	Tone.Time.prototype.quantize = function(subdiv, percent){
		percent = Tone.defaultArg(percent, 1);
		var subdivision = new this.constructor(subdiv);
		var value = this.valueOf();
		var multiple = Math.round(value / subdivision);
		var ideal = multiple * subdivision;
		var diff = ideal - value;
		return value + diff * percent;
	};

	///////////////////////////////////////////////////////////////////////////
	// CONVERSIONS
	///////////////////////////////////////////////////////////////////////////

	/**
	 *  Convert a Time to Notation. Values will be thresholded to the nearest 128th note.
	 *  @return {Notation}
	 *  @example
	 * //if the Transport is at 120bpm:
	 * Tone.Time(2).toNotation();//returns "1m"
	 */
	Tone.Time.prototype.toNotation = function(){
		var time = this.toSeconds();
		var testNotations = ["1m", "2n", "4n", "8n", "16n", "32n", "64n", "128n"];
		var retNotation = this._toNotationHelper(time, testNotations);
		//try the same thing but with tripelets
		var testTripletNotations = ["1m", "2n", "2t", "4n", "4t", "8n", "8t", "16n", "16t", "32n", "32t", "64n", "64t", "128n"];
		var retTripletNotation = this._toNotationHelper(time, testTripletNotations);
		//choose the simpler expression of the two
		if (retTripletNotation.split("+").length < retNotation.split("+").length){
			return retTripletNotation;
		} else {
			return retNotation;
		}
	};

	/**
	 *  Helper method for Tone.toNotation
	 *  @param {Number} units
	 *  @param {Array} testNotations
	 *  @return {String}
	 *  @private
	 */
	Tone.Time.prototype._toNotationHelper = function(units, testNotations){
		//the threshold is the last value in the array
		var threshold = this._notationToUnits(testNotations[testNotations.length - 1]);
		var retNotation = "";
		for (var i = 0; i < testNotations.length; i++){
			var notationTime = this._notationToUnits(testNotations[i]);
			//account for floating point errors (i.e. round up if the value is 0.999999)
			var multiple = units / notationTime;
			var floatingPointError = 0.000001;
			if (1 - multiple % 1 < floatingPointError){
				multiple += floatingPointError;
			}
			multiple = Math.floor(multiple);
			if (multiple > 0){
				if (multiple === 1){
					retNotation += testNotations[i];
				} else {
					retNotation += multiple.toString() + "*" + testNotations[i];
				}
				units -= multiple * notationTime;
				if (units < threshold){
					break;
				} else {
					retNotation += " + ";
				}
			}
		}
		if (retNotation === ""){
			retNotation = "0";
		}
		return retNotation;
	};

	/**
	 *  Convert a notation value to the current units
	 *  @param  {Notation}  notation
	 *  @return  {Number}
	 *  @private
	 */
	Tone.Time.prototype._notationToUnits = function(notation){
		var primaryExprs = this._expressions;
		var notationExprs = [primaryExprs.n, primaryExprs.t, primaryExprs.m];
		for (var i = 0; i < notationExprs.length; i++){
			var expr = notationExprs[i];
			var match = notation.match(expr.regexp);
			if (match){
				return expr.method.call(this, match[1]);
			}
		}
	};

	/**
	 *  Return the time encoded as Bars:Beats:Sixteenths.
	 *  @return  {BarsBeatsSixteenths}
	 */
	Tone.Time.prototype.toBarsBeatsSixteenths = function(){
		var quarterTime = this._beatsToUnits(1);
		var quarters = this.toSeconds() / quarterTime;
		var measures = Math.floor(quarters / this._getTimeSignature());
		var sixteenths = (quarters % 1) * 4;
		quarters = Math.floor(quarters) % this._getTimeSignature();
		sixteenths = sixteenths.toString();
		if (sixteenths.length > 3){
			// the additional parseFloat removes insignificant trailing zeroes
			sixteenths = parseFloat(parseFloat(sixteenths).toFixed(3));
		}
		var progress = [measures, quarters, sixteenths];
		return progress.join(":");
	};

	/**
	 *  Return the time in ticks.
	 *  @return  {Ticks}
	 */
	Tone.Time.prototype.toTicks = function(){
		var quarterTime = this._beatsToUnits(1);
		var quarters = this.toSeconds() / quarterTime;
		return Math.round(quarters * this._getPPQ());
	};

	/**
	 *  Return the time in samples
	 *  @return  {Samples}
	 */
	Tone.Time.prototype.toSamples = function(){
		return this.toSeconds() * this.context.sampleRate;
	};

	/**
	 *  Return the time as a frequency value
	 *  @return  {Frequency}
	 *  @example
	 * Tone.Time(2).toFrequency(); //0.5
	 */
	Tone.Time.prototype.toFrequency = function(){
		return 1/this.toSeconds();
	};

	/**
	 *  Return the time in seconds.
	 *  @return  {Seconds}
	 */
	Tone.Time.prototype.toSeconds = function(){
		return this.valueOf();
	};

	/**
	 *  Return the time in milliseconds.
	 *  @return  {Milliseconds}
	 */
	Tone.Time.prototype.toMilliseconds = function(){
		return this.toSeconds() * 1000;
	};

	return Tone.Time;
});
<commit_msg>toNotation() returns closest notation representation<commit_after>define(["Tone/core/Tone", "Tone/type/TimeBase"], function (Tone) {

	/**
	 *  @class Tone.Time is a primitive type for encoding Time values.
	 *         Tone.Time can be constructed with or without the `new` keyword. Tone.Time can be passed
	 *         into the parameter of any method which takes time as an argument.
	 *  @constructor
	 *  @extends {Tone.TimeBase}
	 *  @param  {String|Number}  val    The time value.
	 *  @param  {String=}  units  The units of the value.
	 *  @example
	 * var t = Tone.Time("4n");//a quarter note
	 */
	Tone.Time = function(val, units){
		if (this instanceof Tone.Time){

			Tone.TimeBase.call(this, val, units);

		} else {
			return new Tone.Time(val, units);
		}
	};

	Tone.extend(Tone.Time, Tone.TimeBase);

	/**
	 * Extend the base expressions
	 */
	Tone.Time.prototype._expressions = Object.assign({}, Tone.TimeBase.prototype._expressions, {
		"quantize" : {
			regexp : /^@(.+)/,
			method : function(capture){
				if (Tone.Transport){
					var quantTo = new this.constructor(capture);
					return Tone.Transport.nextSubdivision(quantTo);
				} else {
					return 0;
				}
			}
		},
		"now" : {
			regexp : /^\+(.+)/,
			method : function(capture){
				return this._now() + (new this.constructor(capture));
			}
		}
	});

	/**
	 *  Quantize the time by the given subdivision. Optionally add a
	 *  percentage which will move the time value towards the ideal
	 *  quantized value by that percentage.
	 *  @param  {Number|Time}  val    The subdivision to quantize to
	 *  @param  {NormalRange}  [percent=1]  Move the time value
	 *                                   towards the quantized value by
	 *                                   a percentage.
	 *  @return  {Number}  this
	 *  @example
	 * Tone.Time(21).quantize(2) //returns 22
	 * Tone.Time(0.6).quantize("4n", 0.5) //returns 0.55
	 */
	Tone.Time.prototype.quantize = function(subdiv, percent){
		percent = Tone.defaultArg(percent, 1);
		var subdivision = new this.constructor(subdiv);
		var value = this.valueOf();
		var multiple = Math.round(value / subdivision);
		var ideal = multiple * subdivision;
		var diff = ideal - value;
		return value + diff * percent;
	};

	///////////////////////////////////////////////////////////////////////////
	// CONVERSIONS
	///////////////////////////////////////////////////////////////////////////

	/**
	 *  Convert a Time to Notation. The notation values are will be the
	 *  closest representation between 1m to 128th note.
	 *  @return {Notation}
	 *  @example
	 * //if the Transport is at 120bpm:
	 * Tone.Time(2).toNotation();//returns "1m"
	 */
	Tone.Time.prototype.toNotation = function(){
		var time = this.toSeconds();
		var testNotations = ["1m"];
		for (var power = 1; power < 8; power++){
			var subdiv = Math.pow(2, power);
			testNotations.push(subdiv + "n.");
			testNotations.push(subdiv + "n");
			testNotations.push(subdiv + "t");
		}
		testNotations.push("0");
		//find the closets notation representation
		var closest = testNotations[0];
		var closestSeconds = Tone.Time(testNotations[0]).toSeconds();
		testNotations.forEach(function(notation){
			var notationSeconds = Tone.Time(notation).toSeconds();
			if (Math.abs(notationSeconds - time) < Math.abs(closestSeconds - time)){
				closest = notation;
				closestSeconds = notationSeconds;
			}
		});
		return closest;
	};

	/**
	 *  Return the time encoded as Bars:Beats:Sixteenths.
	 *  @return  {BarsBeatsSixteenths}
	 */
	Tone.Time.prototype.toBarsBeatsSixteenths = function(){
		var quarterTime = this._beatsToUnits(1);
		var quarters = this.valueOf() / quarterTime;
		var measures = Math.floor(quarters / this._getTimeSignature());
		var sixteenths = (quarters % 1) * 4;
		quarters = Math.floor(quarters) % this._getTimeSignature();
		sixteenths = sixteenths.toString();
		if (sixteenths.length > 3){
			// the additional parseFloat removes insignificant trailing zeroes
			sixteenths = parseFloat(parseFloat(sixteenths).toFixed(3));
		}
		var progress = [measures, quarters, sixteenths];
		return progress.join(":");
	};

	/**
	 *  Return the time in ticks.
	 *  @return  {Ticks}
	 */
	Tone.Time.prototype.toTicks = function(){
		var quarterTime = this._beatsToUnits(1);
		var quarters = this.valueOf() / quarterTime;
		return Math.round(quarters * this._getPPQ());
	};

	/**
	 *  Return the time in samples
	 *  @return  {Samples}
	 */
	Tone.Time.prototype.toSamples = function(){
		return this.toSeconds() * this.context.sampleRate;
	};

	/**
	 *  Return the time as a frequency value
	 *  @return  {Frequency}
	 *  @example
	 * Tone.Time(2).toFrequency(); //0.5
	 */
	Tone.Time.prototype.toFrequency = function(){
		return 1/this.toSeconds();
	};

	/**
	 *  Return the time in seconds.
	 *  @return  {Seconds}
	 */
	Tone.Time.prototype.toSeconds = function(){
		return this.valueOf();
	};

	/**
	 *  Return the time in milliseconds.
	 *  @return  {Milliseconds}
	 */
	Tone.Time.prototype.toMilliseconds = function(){
		return this.toSeconds() * 1000;
	};

	return Tone.Time;
});
<|endoftext|>


<commit_before>var parentClass = new View({
  		frame : {top: 10, left: 10, height: 30, width: 100},
  		styles: {
        	backgroundColor : "#000",
        	color: "white",
      	},
  		oncreate: function(){
          	alert('All you singe ladies!');
  		},
  		onclick: function(x, y){
		}
});

var testViewController = new ViewController();
testViewController.viewDidLoad = function(self){
      var newView = new View();

      newView.inheritFrom(parentClass);

      newView.text = "Hello world";

      self.view.addSubview(newView);
};
<commit_msg>Better spelling<commit_after>var parentClass = new View({
  		frame : {top: 10, left: 10, height: 30, width: 100},
  		styles: {
        	backgroundColor : "#000",
        	color: "white",
      	},
  		oncreate: function(){
          	alert('All you single ladies!');
  		},
  		onclick: function(x, y){
		}
});

var testViewController = new ViewController();
testViewController.viewDidLoad = function(self){
      var newView = new View();

      newView.inheritFrom(parentClass);

      newView.text = "Hello world";

      self.view.addSubview(newView);
};
<|endoftext|>


<commit_before>module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),

    harp: {
      server: {
        server: true
      },
      compile: {
        dest: 'www'
      }
    },

    clean: {
      vendor: {
        src: [
          'www/vendor/'
        ]
      },
      root: {
        src: [
          '../*',
          '!../_app/**',
          '!../.gitignore',
          '!../.git/**'
        ],
        options: {
          force: true
        }
      },
      www: {
        src: 'www/'
      }
    },

    replace: {
      vendor: {
        src: ['www/**/*.html', 'www/**/*.css'],
        overwrite: true,
        replacements: [
          {
            from: '/vendor/jquery/dist/jquery.min.js',
            to: '//cdn.jsdelivr.net/jquery/2.1.1/jquery.min.js'
          }, {
            from: '/vendor/skeljs/dist/skel.min.js',
            to: '//cdn.jsdelivr.net/skeljs/0.4.6/skel.min.js'
          }, {
            from: '/vendor/skeljs/dist/skel-panels.min.js',
            to: '//cdn.jsdelivr.net/skeljs/0.4.6/skel-panels.min.js'
          }, {
            from: '/vendor/skeljs/dist/skel-noscript.css',
            to: '//cdn.jsdelivr.net/skeljs/0.4.6/skel-noscript.css'
          }, {
            from: '/vendor/html5shiv/dist/html5shiv.min.js',
            to: '//cdn.jsdelivr.net/html5shiv/3.7.2/html5shiv.js'
          }, {
            from: '../vendor/font-awesome-bower/fonts/fontawesome-webfont',
            to: '//cdn.jsdelivr.net/fontawesome/4.1.0/fonts/fontawesome-webfont'
          }
        ]
      }
    },

    uglify: {
      minify: {
        files: [{
          expand: true,
          cwd: 'www/js',
          src: '**/*.js',
          dest: 'www/js'
        }]
      }
    },

    cssmin: {
      minify: {
        expand: true,
        cwd: 'www/css/',
        src: ['**/*.css'],
        dest: 'www/css/',
        ext: '.css'
      }
    },

    copy: {
      www: {
        files: [{
          expand: true,
          cwd: 'www',
          src: '**',
          dest: '../'
        }]
      }
    },

    connect: {
      server: {
        options: {
          base: '../',
          keepalive: true
        }
      }
    }
  });

  // Load all task plugins.
  grunt.loadNpmTasks('grunt-harp');
  grunt.loadNpmTasks('grunt-text-replace');
  grunt.loadNpmTasks('grunt-contrib-clean');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-cssmin');
  grunt.loadNpmTasks('grunt-contrib-copy');
  grunt.loadNpmTasks('grunt-contrib-connect');

  // Declare all Grunt task(s).
  grunt.registerTask('default', ['harp:compile']);
  grunt.registerTask('serve', 'harp:server');
  grunt.registerTask('test', 'connect');
};
<commit_msg>add default task of compiling the entire website<commit_after>module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),

    harp: {
      server: {
        server: true
      },
      compile: {
        dest: 'www'
      }
    },

    clean: {
      vendor: {
        src: [
          'www/vendor/'
        ]
      },
      root: {
        src: [
          '../*',
          '!../_app/**',
          '!../.gitignore',
          '!../.git/**'
        ],
        options: {
          force: true
        }
      },
      www: {
        src: 'www/'
      }
    },

    replace: {
      vendor: {
        src: ['www/**/*.html', 'www/**/*.css'],
        overwrite: true,
        replacements: [
          {
            from: '/vendor/jquery/dist/jquery.min.js',
            to: '//cdn.jsdelivr.net/jquery/2.1.1/jquery.min.js'
          }, {
            from: '/vendor/skeljs/dist/skel.min.js',
            to: '//cdn.jsdelivr.net/skeljs/0.4.6/skel.min.js'
          }, {
            from: '/vendor/skeljs/dist/skel-panels.min.js',
            to: '//cdn.jsdelivr.net/skeljs/0.4.6/skel-panels.min.js'
          }, {
            from: '/vendor/skeljs/dist/skel-noscript.css',
            to: '//cdn.jsdelivr.net/skeljs/0.4.6/skel-noscript.css'
          }, {
            from: '/vendor/html5shiv/dist/html5shiv.min.js',
            to: '//cdn.jsdelivr.net/html5shiv/3.7.2/html5shiv.js'
          }, {
            from: '../vendor/font-awesome-bower/fonts/fontawesome-webfont',
            to: '//cdn.jsdelivr.net/fontawesome/4.1.0/fonts/fontawesome-webfont'
          }
        ]
      }
    },

    uglify: {
      minify: {
        files: [{
          expand: true,
          cwd: 'www/js',
          src: '**/*.js',
          dest: 'www/js'
        }]
      }
    },

    cssmin: {
      minify: {
        expand: true,
        cwd: 'www/css/',
        src: ['**/*.css'],
        dest: 'www/css/',
        ext: '.css'
      }
    },

    copy: {
      www: {
        files: [{
          expand: true,
          cwd: 'www',
          src: '**',
          dest: '../'
        }]
      }
    },

    connect: {
      server: {
        options: {
          base: '../',
          keepalive: true
        }
      }
    }
  });

  // Load all task plugins.
  grunt.loadNpmTasks('grunt-harp');
  grunt.loadNpmTasks('grunt-text-replace');
  grunt.loadNpmTasks('grunt-contrib-clean');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-cssmin');
  grunt.loadNpmTasks('grunt-contrib-copy');
  grunt.loadNpmTasks('grunt-contrib-connect');

  // Declare all Grunt task(s).
  grunt.registerTask('default', ['harp:compile', 'clean:vendor', 'replace', 'uglify', 'cssmin', 'clean:root', 'copy', 'clean:www']);
  grunt.registerTask('serve', 'harp:server');
  grunt.registerTask('test', 'connect');
};
<|endoftext|>


<commit_before>describe("Wizard: Unit Tests Free Navigation", function() {

    var steps = {
            theme     : 0,
            demo      : 1,
            doc       : 0,
            community : 0
    };

    var w;

    beforeEach(module('myApp'));
    beforeEach(inject(function(wizard) {
        w = wizard;
        callback = jasmine.createSpy('cb');
        w.init(steps, callback, true);
    }));

    it('should contain the wizard service', function() {
        expect(w).not.toEqual(null);
    });

    it('should init on step 2 (DEMO)', function() {
        expect(w.index).toEqual(1);
    });

    it('should have previous step', function() {
        expect(w.hasPrev()).toBe(true);
    });

    it('should have next step', function() {
        expect(w.hasNext()).toBe(true);
    });

    it('should have one completed step', function() {
        expect(Object.keys(w.completed).length).toEqual(1);
        expect(w.completed.theme).toEqual(1);
    });

    it('should go to prev step (THEME), not marking current step complete, and then callback', function() {
        w.back();
        expect(Object.keys(w.completed).length).toEqual(1);
        expect(callback).toHaveBeenCalled();
    });

    it('should not have a prev step', function() {
        expect(w.hasPrev()).toBe(false);
    });

    it('should go to next step (DEMO), the current step marked completed already (keep), and callback', function() {
        w.next();
        expect(Object.keys(w.completed).length).toEqual(1);
        expect(w.index).toEqual(1);
        expect(callback).toHaveBeenCalled();
    });

    it('should BE ABLE TO GO TO the last step (restricted navigation). Still callback.', function() {
        w.goto('community');
        expect(Object.keys(w.completed).length).toEqual(1);
        expect(w.index).toEqual(3);
        expect(callback).toHaveBeenCalled();
    });

    it('should not have a next available', function() {
        expect(w.index).toEqual(3);
        expect(w.hasNext()).toBe(false);
    });

    it('should go to the first step, without marking last one complete, and callback', function() {
        w.goto('theme');
        expect(Object.keys(w.completed).length).toEqual(3);
        expect(callback).toHaveBeenCalled();
    });

    it('should go to the last step', function() {
        w.goto('community');
        expect(w.index).toEqual(3);
        expect(callback).toHaveBeenCalled();
    });

    it('should allow manually marking the current step as completed', function() {
        w.markCompleted();
        expect(Object.keys(w.completed).length).toEqual(4);
    });

    it('should allow manually marking the current step as incomple', function() {
        w.markIncomplete();
        expect(Object.keys(w.completed).length).toEqual(3);
    });

    it('should finish by marking the current step as complete and returning true', function() {
        var done = w.finish();
        expect(Object.keys(w.completed).length).toEqual(4);
        expect(done).toBe(true);
    });
});

<commit_msg>fixed tests<commit_after>describe("Wizard: Unit Tests Free Navigation", function() {

    var steps = {
            theme     : 0,
            demo      : 1,
            doc       : 0,
            community : 0
    };

    var w;

    beforeEach(module('myApp'));
    beforeEach(inject(function(wizard) {
        w = wizard;
        callback = jasmine.createSpy('cb');
        w.init(steps, callback, true);
    }));

    it('should contain the wizard service', function() {
        expect(w).not.toEqual(null);
    });

    it('should init on step 2 (DEMO)', function() {
        expect(w.index).toEqual(1);
    });

    it('should have previous step', function() {
        expect(w.hasPrev()).toBe(true);
    });

    it('should have next step', function() {
        expect(w.hasNext()).toBe(true);
    });

    it('should have one completed step', function() {
        expect(Object.keys(w.completed).length).toEqual(1);
        expect(w.completed.theme).toEqual(1);
    });

    it('should go to prev step (THEME), not marking current step complete, and then callback', function() {
        w.back();
        expect(Object.keys(w.completed).length).toEqual(1);
        expect(callback).toHaveBeenCalled();
    });

    it('should not have a prev step', function() {
        expect(w.hasPrev()).toBe(false);
    });

    it('should go to next step (DEMO), the current step marked completed already (keep), and callback', function() {
        w.next();
        expect(Object.keys(w.completed).length).toEqual(1);
        expect(w.index).toEqual(1);
        expect(callback).toHaveBeenCalled();
    });

    it('should BE ABLE TO GO TO the last step (restricted navigation). Still callback.', function() {
        w.goto('community');
        expect(Object.keys(w.completed).length).toEqual(1);
        expect(w.index).toEqual(3);
        expect(callback).toHaveBeenCalled();
    });

    it('should not have a next available', function() {
        expect(w.index).toEqual(3);
        expect(w.hasNext()).toBe(false);
    });

    it('should go to the first step, without marking last one complete, and callback', function() {
        w.goto('theme');
        expect(Object.keys(w.completed).length).toEqual(3);
        expect(callback).toHaveBeenCalled();
    });

    it('should go to the last step', function() {
        w.goto('community');
        expect(w.index).toEqual(3);
        expect(callback).toHaveBeenCalled();
    });

    it('should allow manually marking the current step as completed', function() {
        w.markCompleted();
        expect(Object.keys(w.completed).length).toEqual(4);
    });

    it('should allow manually marking the current step as incomple', function() {
        w.markIncomplete();
        expect(Object.keys(w.completed).length).toEqual(3);
    });

    it('should finish by marking the current step as complete and returning true', function() {
        w.finish();
        var done = w.isComplete()
        expect(Object.keys(w.completed).length).toEqual(4);
        expect(callback).toHaveBeenCalled();
        expect(done).toBe(true);
    });
});

<|endoftext|>