<commit_before>/*
 * The javascript for friendicas hovercard. Bootstraps popover is needed.
 *
 * Much parts of the code are from Hannes Mannerheims <h@nnesmannerhe.im>
 * qvitter code (https://github.com/hannesmannerheim/qvitter)
 *
 * It is licensed under the GNU Affero General Public License <http://www.gnu.org/licenses/>
 *
 */
$(document).ready(function(){
	// Elements with the class "userinfo" will get a hover-card.
	// Note that this elements does need a href attribute which links to
	// a valid profile url
	$("body").on("mouseover", ".userinfo, .wall-item-responses a, .wall-item-bottom .mention a", function(e) {
			var timeNow = new Date().getTime();
			removeAllhoverCards(e,timeNow);
			var hoverCardData = false;
			var hrefAttr = false;
			var targetElement = $(this);

			// get href-attribute
			if(targetElement.is('[href]')) {
				hrefAttr = targetElement.attr('href');
			} else {
				return true;
			}

			// no hover card if the element has the no-hover-card class
			if(targetElement.hasClass('no-hover-card')) {
				return true;
			}

			// no hovercard for anchor links
			if(hrefAttr.substring(0,1) == '#') {
				return true;
			}

			targetElement.attr('data-awaiting-hover-card',timeNow);

			// Take link href attribute as link to the profile
			var profileurl = hrefAttr;
			// the url to get the contact and template data
			var url = baseurl + "/hovercard";

			// store the title in an other data attribute beause bootstrap
			// popover destroys the title.attribute. We can restore it later
			var title = targetElement.attr("title");
			targetElement.attr({"data-orig-title": title, title: ""});

			// if the device is a mobile open the hover card by click and not by hover
			if(typeof is_mobile != "undefined") {
					targetElement[0].removeAttribute("href");
					var hctrigger = 'click';
				} else {
					var hctrigger = 'manual';
			};

			// Timeoute until the hover-card does appear
			setTimeout(function(){
				if(targetElement.is(":hover") && parseInt(targetElement.attr('data-awaiting-hover-card'),10) == timeNow) {
					if($('.hovercard').length == 0) {	// no card if there already is one open
						// get an additional data atribute if the card is active
						targetElement.attr('data-hover-card-active',timeNow);
						// get the whole html content of the hover card and
						// push it to the bootstrap popover
						getHoverCardContent(profileurl, url, function(data){
							if(data) {
								targetElement.popover({
									html: true,
									placement: function () {
										// Calculate the placement of the the hovercard (if top or bottom)
										// The placement depence on the distance between window top and the element
										// which triggers the hover-card
										var get_position = $(targetElement).offset().top - $(window).scrollTop();
										if (get_position < 270 ){
											return "bottom";
										}
										return "top";
									},
									trigger: hctrigger,
									template: '

',
									content: data
								}).popover('show');
							}
						});
					}
				}
			}, 500);
	}).on("mouseleave", ".userinfo", function(e) { // action when mouse leaves the hover-card
		var timeNow = new Date().getTime();
		// copy the original title to the title atribute
		var title = $(this).attr("data-orig-title");
		$(this).attr({"data-orig-title": "", title: title});
		removeAllhoverCards(e,timeNow);
	});

	// hover cards should be removed very easily, e.g. when any of these events happen
	$('body').on("mouseleave touchstart scroll click dblclick mousedown mouseup submit keydown keypress keyup", function(e){
		// remove hover card only for desktiop user, since on mobile we openen the hovercards
		// by click event insteadof hover
		if(typeof is_mobile == "undefined") {
			var timeNow = new Date().getTime();
			removeAllhoverCards(e,timeNow);
		};
	});

	// if we're hovering a hover card, give it a class, so we don't remove it
	$('body').on('mouseover','.hovercard', function(e) {
		$(this).addClass('dont-remove-card');
	});
	$('body').on('mouseleave','.hovercard', function(e) {
		$(this).removeClass('dont-remove-card');
		$(this).popover("hide");
	});

}); // End of $(document).ready

// removes all hover cards
function removeAllhoverCards(event,priorTo) {
	// don't remove hovercards until after 100ms, so user have time to move the cursor to it (which gives it the dont-remove-card class)
	setTimeout(function(){
		$.each($('.hovercard'),function(){
			var title = $(this).attr("data-orig-title");
			// don't remove card if it was created after removeAllhoverCards() was called
			if($(this).data('card-created') < priorTo) {
				// don't remove it if we're hovering it right now!
				if(!$(this).hasClass('dont-remove-card')) {
					$('[data-hover-card-active="' + $(this).data('card-created') + '"]').removeAttr('data-hover-card-active');
					$(this).popover("hide");
				}
			}
		});
	},100);
}

// Ajax request to get json contact data
function getContactData(purl, url, actionOnSuccess) {
	var postdata = {
		mode		: 'none',
		profileurl	: purl,
		datatype	: 'json',
	};

	// Normalize and clean the profile so we can use a standardized url
	// as key for the cache
	var nurl = cleanContactUrl(purl).normalizeLink();

	// If the contact is allready in the cache use the cached result instead
	// of doing a new ajax request
	if(nurl in getContactData.cache) {
		setTimeout(function() { actionOnSuccess(getContactData.cache[nurl]); } , 1);
		return;
	}

	$.ajax({
		url: url,
		data: postdata,
		dataType: "json",
		success: function(data, textStatus, request){
			// Check if the nurl (normalized profile url) is present and store it to the cache
			// The nurl will be the identifier in the object
			if(data.nurl.length > 0) {
				// Test if the contact is allready connected with the user (if url containing
				// the expression ("redir/") We will store different cache keys
				if((data.url.search("redir/")) >= 0 ) {
					var key = data.url;
				} else {
					var key = data.nurl;
				}
				getContactData.cache[key] = data;
			}
			actionOnSuccess(data, url, request);
		},
		error: function(data) {
			actionOnSuccess(false, data, url);
		}
	});
}
getContactData.cache = {};

// Get hover-card template data and the contact-data and transform it with
// the help of jSmart. At the end we have full html content of the hovercard
function getHoverCardContent(purl, url, callback) {
	// fetch the raw content of the template
	getHoverCardTemplate(url, function(stpl) {
		var template = unescape(stpl);

		// get the contact data
		getContactData (purl, url, function(data) {
			if(typeof template != 'undefined') {
				// get the hover-card variables
				var variables = getHoverCardVariables(data);
				var tpl;

				// use friendicas template delimiters instead of
				// the original one
				jSmart.prototype.left_delimiter = '{{';
				jSmart.prototype.right_delimiter = '}}';

				// create a new jSmart instant with the raw content
				// of the template
				var tpl = new jSmart (template);
				// insert the variables content into the template content
				var HoverCardContent = tpl.fetch(variables);

				callback(HoverCardContent);
			}
		});
	});

// This is interisting. this pice of code ajax request are done asynchron.
// To make it work getHOverCardTemplate() and getHOverCardData have to return it's
// data (no succes handler for each of this). I leave it here, because it could be useful.
// https://lostechies.com/joshuaflanagan/2011/10/20/coordinating-multiple-ajax-requests-with-jquery-when/
//	$.when(
//		getHoverCardTemplate(url),
//		getContactData (term, url )
//
//	).done(function(template, profile){
//		if(typeof template != 'undefined') {
//			var variables = getHoverCardVariables(profile);
//
//			jSmart.prototype.left_delimiter = '{{';
//			jSmart.prototype.right_delimiter = '}}';
//			var tpl = new jSmart (template);
//			var html = tpl.fetch(variables);
//
//			return html;
//		}
//	});
}

// Ajax request to get the raw template content
function getHoverCardTemplate (url, callback) {
	var postdata = {
		mode: 'none',
		datatype: 'tpl'
	};

	// Look if we have the template already in the cace, so we don't have
	// request it again
	if('hovercard' in getHoverCardTemplate.cache) {
		setTimeout(function() { callback(getHoverCardTemplate.cache['hovercard']); } , 1);
		return;
	}

	$.ajax({
		url: url,
		data: postdata,
		success: function(data, textStatus) {
			// write the data in the cache
			getHoverCardTemplate.cache['hovercard'] = data;
			callback(data);
		}
	}).fail(function () {callback([]); });
}
getHoverCardTemplate.cache = {};

// The Variables used for the template
function getHoverCardVariables(object) {
	var profile = {
			name:		object.name,
			nick:		object.nick,
			addr:		object.addr,
			thumb:		object.thumb,
			url:		object.url,
			nurl:		object.nurl,
			location:	object.location,
			gender:		object.gender,
			about:		object.about,
			network:	object.network,
			tags:		object.tags,
			bd:		object.bd,
			account_type:	object.account_type,
			actions:	object.actions
	};

	var variables = { profile:  profile};

	return variables;
}

// This is the html template for the hover-card
// Since we grab the original hovercard.tpl we don't
// need it anymore
function hovercard_template() {
	var tempate = '\

\

\

\

\
					\
						\
					\

\

\

\

{{$profile.name}}
{{if $profile.account_type}}{{$profile.account_type}}{{/if}}\

\

\
						{{$profile.addr}}\
						{{if $profile.network}} ({{$profile.network}}){{/if}}\

\
					{{*{{if $profile.about}}
{{$profile.about}}
{{/if}}*}}\
\

\

\
					{{* here are the differnt actions like privat message, poke, delete and so on *}}\
					{{* @todo we have two different photo menus one for contacts and one for items at the network stream. We currently use the contact photo menu, so the items options are missing We need to move them *}}\

\
						{{if $profile.actions.pm}}{{/if}}\
						{{if $profile.actions.poke}}{{/if}}\

\

\
						{{if $profile.actions.edit}}{{/if}}\
						{{if $profile.actions.drop}}{{/if}}\
						{{if $profile.actions.follow}}{{/if}}\

\

\

\
\

\
\

\

\
	{{if $profile.tags}}
{{$profile.tags}}
{{/if}}';
}
<commit_msg>frio: fix close hovercard on mousleave<commit_after>/*
 * The javascript for friendicas hovercard. Bootstraps popover is needed.
 *
 * Much parts of the code are from Hannes Mannerheims <h@nnesmannerhe.im>
 * qvitter code (https://github.com/hannesmannerheim/qvitter)
 *
 * It is licensed under the GNU Affero General Public License <http://www.gnu.org/licenses/>
 *
 */
$(document).ready(function(){
	// Elements with the class "userinfo" will get a hover-card.
	// Note that this elements does need a href attribute which links to
	// a valid profile url
	$("body").on("mouseover", ".userinfo, .wall-item-responses a, .wall-item-bottom .mention a", function(e) {
			var timeNow = new Date().getTime();
			removeAllhoverCards(e,timeNow);
			var hoverCardData = false;
			var hrefAttr = false;
			var targetElement = $(this);

			// get href-attribute
			if(targetElement.is('[href]')) {
				hrefAttr = targetElement.attr('href');
			} else {
				return true;
			}

			// no hover card if the element has the no-hover-card class
			if(targetElement.hasClass('no-hover-card')) {
				return true;
			}

			// no hovercard for anchor links
			if(hrefAttr.substring(0,1) == '#') {
				return true;
			}

			targetElement.attr('data-awaiting-hover-card',timeNow);

			// Take link href attribute as link to the profile
			var profileurl = hrefAttr;
			// the url to get the contact and template data
			var url = baseurl + "/hovercard";

			// store the title in an other data attribute beause bootstrap
			// popover destroys the title.attribute. We can restore it later
			var title = targetElement.attr("title");
			targetElement.attr({"data-orig-title": title, title: ""});

			// if the device is a mobile open the hover card by click and not by hover
			if(typeof is_mobile != "undefined") {
					targetElement[0].removeAttribute("href");
					var hctrigger = 'click';
				} else {
					var hctrigger = 'manual';
			};

			// Timeoute until the hover-card does appear
			setTimeout(function(){
				if(targetElement.is(":hover") && parseInt(targetElement.attr('data-awaiting-hover-card'),10) == timeNow) {
					if($('.hovercard').length == 0) {	// no card if there already is one open
						// get an additional data atribute if the card is active
						targetElement.attr('data-hover-card-active',timeNow);
						// get the whole html content of the hover card and
						// push it to the bootstrap popover
						getHoverCardContent(profileurl, url, function(data){
							if(data) {
								targetElement.popover({
									html: true,
									placement: function () {
										// Calculate the placement of the the hovercard (if top or bottom)
										// The placement depence on the distance between window top and the element
										// which triggers the hover-card
										var get_position = $(targetElement).offset().top - $(window).scrollTop();
										if (get_position < 270 ){
											return "bottom";
										}
										return "top";
									},
									trigger: hctrigger,
									template: '

',
									content: data
								}).popover('show');
							}
						});
					}
				}
			}, 500);
	}).on("mouseleave", ".userinfo, .wall-item-responses a, .wall-item-bottom .mention a", function(e) { // action when mouse leaves the hover-card
		var timeNow = new Date().getTime();
		// copy the original title to the title atribute
		var title = $(this).attr("data-orig-title");
		$(this).attr({"data-orig-title": "", title: title});
		removeAllhoverCards(e,timeNow);
	});

	// hover cards should be removed very easily, e.g. when any of these events happen
	$('body').on("mouseleave touchstart scroll click dblclick mousedown mouseup submit keydown keypress keyup", function(e){
		// remove hover card only for desktiop user, since on mobile we openen the hovercards
		// by click event insteadof hover
		if(typeof is_mobile == "undefined") {
			var timeNow = new Date().getTime();
			removeAllhoverCards(e,timeNow);
		};
	});

	// if we're hovering a hover card, give it a class, so we don't remove it
	$('body').on('mouseover','.hovercard', function(e) {
		$(this).addClass('dont-remove-card');
	});
	$('body').on('mouseleave','.hovercard', function(e) {
		$(this).removeClass('dont-remove-card');
		$(this).popover("hide");
	});

}); // End of $(document).ready

// removes all hover cards
function removeAllhoverCards(event,priorTo) {
	// don't remove hovercards until after 100ms, so user have time to move the cursor to it (which gives it the dont-remove-card class)
	setTimeout(function(){
		$.each($('.hovercard'),function(){
			var title = $(this).attr("data-orig-title");
			// don't remove card if it was created after removeAllhoverCards() was called
			if($(this).data('card-created') < priorTo) {
				// don't remove it if we're hovering it right now!
				if(!$(this).hasClass('dont-remove-card')) {
					$('[data-hover-card-active="' + $(this).data('card-created') + '"]').removeAttr('data-hover-card-active');
					$(this).popover("hide");
				}
			}
		});
	},100);
}

// Ajax request to get json contact data
function getContactData(purl, url, actionOnSuccess) {
	var postdata = {
		mode		: 'none',
		profileurl	: purl,
		datatype	: 'json',
	};

	// Normalize and clean the profile so we can use a standardized url
	// as key for the cache
	var nurl = cleanContactUrl(purl).normalizeLink();

	// If the contact is allready in the cache use the cached result instead
	// of doing a new ajax request
	if(nurl in getContactData.cache) {
		setTimeout(function() { actionOnSuccess(getContactData.cache[nurl]); } , 1);
		return;
	}

	$.ajax({
		url: url,
		data: postdata,
		dataType: "json",
		success: function(data, textStatus, request){
			// Check if the nurl (normalized profile url) is present and store it to the cache
			// The nurl will be the identifier in the object
			if(data.nurl.length > 0) {
				// Test if the contact is allready connected with the user (if url containing
				// the expression ("redir/") We will store different cache keys
				if((data.url.search("redir/")) >= 0 ) {
					var key = data.url;
				} else {
					var key = data.nurl;
				}
				getContactData.cache[key] = data;
			}
			actionOnSuccess(data, url, request);
		},
		error: function(data) {
			actionOnSuccess(false, data, url);
		}
	});
}
getContactData.cache = {};

// Get hover-card template data and the contact-data and transform it with
// the help of jSmart. At the end we have full html content of the hovercard
function getHoverCardContent(purl, url, callback) {
	// fetch the raw content of the template
	getHoverCardTemplate(url, function(stpl) {
		var template = unescape(stpl);

		// get the contact data
		getContactData (purl, url, function(data) {
			if(typeof template != 'undefined') {
				// get the hover-card variables
				var variables = getHoverCardVariables(data);
				var tpl;

				// use friendicas template delimiters instead of
				// the original one
				jSmart.prototype.left_delimiter = '{{';
				jSmart.prototype.right_delimiter = '}}';

				// create a new jSmart instant with the raw content
				// of the template
				var tpl = new jSmart (template);
				// insert the variables content into the template content
				var HoverCardContent = tpl.fetch(variables);

				callback(HoverCardContent);
			}
		});
	});

// This is interisting. this pice of code ajax request are done asynchron.
// To make it work getHOverCardTemplate() and getHOverCardData have to return it's
// data (no succes handler for each of this). I leave it here, because it could be useful.
// https://lostechies.com/joshuaflanagan/2011/10/20/coordinating-multiple-ajax-requests-with-jquery-when/
//	$.when(
//		getHoverCardTemplate(url),
//		getContactData (term, url )
//
//	).done(function(template, profile){
//		if(typeof template != 'undefined') {
//			var variables = getHoverCardVariables(profile);
//
//			jSmart.prototype.left_delimiter = '{{';
//			jSmart.prototype.right_delimiter = '}}';
//			var tpl = new jSmart (template);
//			var html = tpl.fetch(variables);
//
//			return html;
//		}
//	});
}

// Ajax request to get the raw template content
function getHoverCardTemplate (url, callback) {
	var postdata = {
		mode: 'none',
		datatype: 'tpl'
	};

	// Look if we have the template already in the cace, so we don't have
	// request it again
	if('hovercard' in getHoverCardTemplate.cache) {
		setTimeout(function() { callback(getHoverCardTemplate.cache['hovercard']); } , 1);
		return;
	}

	$.ajax({
		url: url,
		data: postdata,
		success: function(data, textStatus) {
			// write the data in the cache
			getHoverCardTemplate.cache['hovercard'] = data;
			callback(data);
		}
	}).fail(function () {callback([]); });
}
getHoverCardTemplate.cache = {};

// The Variables used for the template
function getHoverCardVariables(object) {
	var profile = {
			name:		object.name,
			nick:		object.nick,
			addr:		object.addr,
			thumb:		object.thumb,
			url:		object.url,
			nurl:		object.nurl,
			location:	object.location,
			gender:		object.gender,
			about:		object.about,
			network:	object.network,
			tags:		object.tags,
			bd:		object.bd,
			account_type:	object.account_type,
			actions:	object.actions
	};

	var variables = { profile:  profile};

	return variables;
}

// This is the html template for the hover-card
// Since we grab the original hovercard.tpl we don't
// need it anymore
function hovercard_template() {
	var tempate = '\

\

\

\

\
					\
						\
					\

\

\

\

{{$profile.name}}
{{if $profile.account_type}}{{$profile.account_type}}{{/if}}\

\

\
						{{$profile.addr}}\
						{{if $profile.network}} ({{$profile.network}}){{/if}}\

\
					{{*{{if $profile.about}}
{{$profile.about}}
{{/if}}*}}\
\

\

\
					{{* here are the differnt actions like privat message, poke, delete and so on *}}\
					{{* @todo we have two different photo menus one for contacts and one for items at the network stream. We currently use the contact photo menu, so the items options are missing We need to move them *}}\

\
						{{if $profile.actions.pm}}{{/if}}\
						{{if $profile.actions.poke}}{{/if}}\

\

\
						{{if $profile.actions.edit}}{{/if}}\
						{{if $profile.actions.drop}}{{/if}}\
						{{if $profile.actions.follow}}{{/if}}\

\

\

\
\

\
\

\

\
	{{if $profile.tags}}
{{$profile.tags}}
{{/if}}';
}
<|endoftext|>


<commit_before>import React from 'react';

import Analyzer, { SELECTED_PLAYER_PET } from 'parser/core/Analyzer';
import Events from 'parser/core/Events';

import SPELLS from 'common/SPELLS';
import SpellIcon from 'common/SpellIcon';
import SpellLink from 'common/SpellLink';
import { formatThousands } from 'common/format';

import StatisticBox from 'interface/others/StatisticBox';

class LegionStrike extends Analyzer {
  casts = 0;
  damage = 0;

  constructor(...args) {
    super(...args);
    this.addEventListener(Events.cast.by(SELECTED_PLAYER_PET).spell(SPELLS.FELGUARD_LEGION_STRIKE), this.legionStrikeCast);
    this.addEventListener(Events.damage.by(SELECTED_PLAYER_PET).spell(SPELLS.FELGUARD_LEGION_STRIKE), this.legionStrikeDamage);
  }

  legionStrikeCast() {
    this.casts += 1;
  }

  legionStrikeDamage(event) {
    this.damage += event.amount + (event.absorbed || 0);
  }

  get suggestionThresholds() {
    return {
      actual: this.casts,
      isLessThan: {
        minor: 1,
        average: 0,
        major: 0,
      },
      style: 'number',
    };
  }

  suggestions(when) {
    when(this.suggestionThresholds)
      .addSuggestion((suggest, actual, recommended) => {
        return suggest(<>Your Felguard didn't cast <SpellLink id={SPELLS.FELGUARD_LEGION_STRIKE.id} /> at all. Remember to turn on the auto-cast for this ability as it's a great portion of your total damage.</>)
          .icon(SPELLS.FELGUARD_LEGION_STRIKE.icon)
          .actual(`${actual} Legion Strike casts`)
          .recommended(`> ${recommended} casts are recommended`);
      });
  }

  statistic() {
    return (
      <StatisticBox
        icon={<SpellIcon id={SPELLS.FELGUARD_LEGION_STRIKE.id} />}
        value={this.owner.formatItemDamageDone(this.damage)}
        label="Legion Strike damage"
        tooltip={`${formatThousands(this.damage)} damage`}
      />
    );
  }
}

export default LegionStrike;
<commit_msg>Only count Legion Strikes from the permanent pet<commit_after>import React from 'react';

import Analyzer, { SELECTED_PLAYER_PET } from 'parser/core/Analyzer';
import Events from 'parser/core/Events';

import SPELLS from 'common/SPELLS';
import SpellIcon from 'common/SpellIcon';
import SpellLink from 'common/SpellLink';
import { formatThousands } from 'common/format';

import StatisticBox from 'interface/others/StatisticBox';

import { isPermanentPet } from '../pets/helpers';

class LegionStrike extends Analyzer {
  casts = 0;
  damage = 0;

  constructor(...args) {
    super(...args);
    this.addEventListener(Events.cast.by(SELECTED_PLAYER_PET).spell(SPELLS.FELGUARD_LEGION_STRIKE), this.legionStrikeCast);
    this.addEventListener(Events.damage.by(SELECTED_PLAYER_PET).spell(SPELLS.FELGUARD_LEGION_STRIKE), this.legionStrikeDamage);
  }

  legionStrikeCast(event) {
    // Grimoire: Felguard casts Legion Strike with the same spell ID, only count LS casts from the permanent pet
    if (this._isPermanentPet(event.sourceID)) {
      this.casts += 1;
    }
  }

  legionStrikeDamage(event) {
    if (this._isPermanentPet(event.sourceID)) {
      this.damage += event.amount + (event.absorbed || 0);
    }
  }

  _getPetGuid(id) {
    return this.owner.playerPets.find(pet => pet.id === id).guid;
  }

  _isPermanentPet(id) {
    const guid = this._getPetGuid(id);
    return isPermanentPet(guid);
  }

  get suggestionThresholds() {
    return {
      actual: this.casts,
      isLessThan: {
        minor: 1,
        average: 0,
        major: 0,
      },
      style: 'number',
    };
  }

  suggestions(when) {
    when(this.suggestionThresholds)
      .addSuggestion((suggest, actual, recommended) => {
        return suggest(<>Your Felguard didn't cast <SpellLink id={SPELLS.FELGUARD_LEGION_STRIKE.id} /> at all. Remember to turn on the auto-cast for this ability as it's a great portion of your total damage.</>)
          .icon(SPELLS.FELGUARD_LEGION_STRIKE.icon)
          .actual(`${actual} Legion Strike casts`)
          .recommended(`> ${recommended} casts are recommended`);
      });
  }

  statistic() {
    return (
      <StatisticBox
        icon={<SpellIcon id={SPELLS.FELGUARD_LEGION_STRIKE.id} />}
        value={this.owner.formatItemDamageDone(this.damage)}
        label="Legion Strike damage"
        tooltip={`${formatThousands(this.damage)} damage`}
      />
    );
  }
}

export default LegionStrike;
<|endoftext|>


<commit_before>import React from 'react'
import { FormattedMessage } from 'react-intl'

export const WebConnectivityDetails = ({ measurement }) => {
  return (

      {/* Normal measurement */}
      {measurement.test_keys.accessible !== false && measurement.test_keys.blocking === false &&

          <FormattedMessage
            id='nettests.webConnectivity.noCensorshipDetected'
            defaultMessage='No censorship detected'
          />

          <FormattedMessage
            id='nettests.webConnectivity.websiteAccessibleUncensored'
            defaultMessage='The website is accessible and uncensored from this network.'
          />

      }
      {(measurement.test_keys.accessible === true || measurement.test_keys.accessible === null) &&
      measurement.test_keys.blocking === null &&

          <FormattedMessage
            id='nettests.webConnectivity.errorInMeasurement'
            defaultMessage='Error in measurement' />

        {measurement.test_keys.accessible === true &&

          <FormattedMessage
            id='nettests.webConnectivity.websiteUp'
            defaultMessage={`The website is up, however there was a problem in assessing if whether or not
 it is being censored.`}
          />

        }
        {measurement.test_keys.accessible === null &&

          <FormattedMessage
            id='nettests.webConnectivity.thereWasAnError'
            defaultMessage='There was an error in understanding if this site is up and/or censored.'
          />

        }

      }

      {/* Site inaccessible */}
      {measurement.test_keys.accessible === false && (
          measurement.test_keys.blocking === false || measurement.test_keys.blocking === null
        ) &&

            <FormattedMessage
              id='nettests.webConnectivity.websiteUnavailable'
              defaultMessage='Website unavailable'
            />

            <FormattedMessage
              id='nettests.webConnectivity.websiteUnavailableDesc'
              defaultMessage={`The website appears to not be available at the moment.
 Requests from the control vantage point are also failing.`}
          />

      }

      {/* Evidence of censorship */}
      {measurement.test_keys.blocking !== null && measurement.test_keys.blocking !== false &&

          <FormattedMessage
            id='nettests.webConnectivity.evidenceOfCensorship'
            defaultMessage='Evidence of possible censorship'
          />

      }

      {measurement.test_keys.blocking === 'dns' &&

        <FormattedMessage
          id='nettests.webConnectivity.censorshipDNSBased'
          defaultMessage='The site appears to be blocked due to {reason}.'
          values={{
            dnsCensorship: <FormattedMessage
              id='nettest.webConnectivity.censorshipDNSBased.reason'
              defaultMessage='DNS based censorship'
            />
          }}
        />

}

      {measurement.test_keys.blocking === 'http-diff' &&

        <FormattedMessage
          id='nettests.webConnectivity.httpDiff'
          defaultMessage='The site appears to be blocked because it presents a {reason}.'
          values={{
            reason:
              <FormattedMessage
                id='nettests.webConnectivity.httpDiff.reason'
                defaultMessage='different HTTP response'
              />

          }}
        />

      }

      {measurement.test_keys.blocking === 'http-failure' &&

        <FormattedMessage
          id='nettests.webConnectivity.httpFailure'
          defaultMessage='The site appears to be blocked because the {what} with {errorMessage}.'
          values={{
            what: <FormattedMessage
              id='nettests.webConnectivity.httpFailure.what'
              defaultMessage='HTTP request failed'
            />,
            errorMessage: {measurement.test_keys.http_experiment_failure}
          }}
        />

      }

      {measurement.test_keys.blocking === 'tcp_ip' &&

        <FormattedMessage
          id='nettests.webConnectivity.tcpIP'
          defaultMessage='The site appears to be blocked by means of {reason}'
          values={{
            reason: <FormattedMessage
              id='nettests.webConnectivity.tcpIP.reason'
              defaultMessage='TCP/IP based blocking'
            />
          }}
        />

      }

      {measurement.test_keys.blocking !== false &&
      measurement.test_keys.blocking != null &&

 Circumvention strategies

You can try to circumvent the blocking of the site
          {measurement.input} through the
          following:

        {(measurement.test_keys.blocking === 'http-diff' ||
          measurement.test_keys.blocking === 'http-failure') &&
        measurement.input.substr(0, 5) !== 'https' &&

Use Secure HTTP (HTTPS)

Try visiting the HTTPS version of the website in
            question by accessing it via
            <a href={`https${measurement.input.substr(4)}`}>https{measurement.input.substr(4)}

        }
        {measurement.test_keys.blocking === 'dns' &&

Change your DNS

Try changing your DNS resolver to one that does not
            implement blocking, such as the Google DNS resolver:
            8.8.8.8.

            <FormattedMessage
              id='nettests.webConnectivity.circumvention.googleDNSInstructions'
              defaultMessage={`You can find instructions on how to change your DNS resolver to the
 Google DNS resolver through the following URL: {instructionsLink}`}
              values={{
                instructionsLink:
                  https://developers.google.com/speed/public-dns/docs/using

              }}
            />

        }

Use Tor

In most cases you should be able to circumvent censorship by
            using Tor.
            To download Tor visit:
              https://www.torproject.org/download/download.html.en

            If the torproject.org website is blocked in your country you can download Tor from a mirror of it
            hosted on github:
              https://github.com/TheTorProject/gettorbrowser

          Note: The above techniques might not always work.

      }

  )
}

WebConnectivityDetails.propTypes = {
  measurement: React.PropTypes.object
}

export default WebConnectivityDetails
<commit_msg>Finish web-connectivity internationalization<commit_after>import React from 'react'
import { FormattedMessage } from 'react-intl'

export const WebConnectivityDetails = ({ measurement }) => {
  return (

      {/* Normal measurement */}
      {measurement.test_keys.accessible !== false && measurement.test_keys.blocking === false &&

          <FormattedMessage
            id='nettests.webConnectivity.noCensorshipDetected'
            defaultMessage='No censorship detected'
          />

          <FormattedMessage
            id='nettests.webConnectivity.websiteAccessibleUncensored'
            defaultMessage='The website is accessible and uncensored from this network.'
          />

      }
      {(measurement.test_keys.accessible === true || measurement.test_keys.accessible === null) &&
      measurement.test_keys.blocking === null &&

          <FormattedMessage
            id='nettests.webConnectivity.errorInMeasurement'
            defaultMessage='Error in measurement' />

        {measurement.test_keys.accessible === true &&

          <FormattedMessage
            id='nettests.webConnectivity.websiteUp'
            defaultMessage={`The website is up, however there was a problem in assessing if whether or not
 it is being censored.`}
          />

        }
        {measurement.test_keys.accessible === null &&

          <FormattedMessage
            id='nettests.webConnectivity.thereWasAnError'
            defaultMessage='There was an error in understanding if this site is up and/or censored.'
          />

        }

      }

      {/* Site inaccessible */}
      {measurement.test_keys.accessible === false && (
          measurement.test_keys.blocking === false || measurement.test_keys.blocking === null
        ) &&

            <FormattedMessage
              id='nettests.webConnectivity.websiteUnavailable'
              defaultMessage='Website unavailable'
            />

            <FormattedMessage
              id='nettests.webConnectivity.websiteUnavailableDesc'
              defaultMessage={`The website appears to not be available at the moment.
 Requests from the control vantage point are also failing.`}
          />

      }

      {/* Evidence of censorship */}
      {measurement.test_keys.blocking !== null && measurement.test_keys.blocking !== false &&

          <FormattedMessage
            id='nettests.webConnectivity.evidenceOfCensorship'
            defaultMessage='Evidence of possible censorship'
          />

      }

      {measurement.test_keys.blocking === 'dns' &&

        <FormattedMessage
          id='nettests.webConnectivity.censorshipDNSBased'
          defaultMessage='The site appears to be blocked due to {reason}.'
          values={{
            dnsCensorship: <FormattedMessage
              id='nettest.webConnectivity.censorshipDNSBased.reason'
              defaultMessage='DNS based censorship'
            />
          }}
        />

}

      {measurement.test_keys.blocking === 'http-diff' &&

        <FormattedMessage
          id='nettests.webConnectivity.httpDiff'
          defaultMessage='The site appears to be blocked because it presents a {reason}.'
          values={{
            reason:
              <FormattedMessage
                id='nettests.webConnectivity.httpDiff.reason'
                defaultMessage='different HTTP response'
              />

          }}
        />

      }

      {measurement.test_keys.blocking === 'http-failure' &&

        <FormattedMessage
          id='nettests.webConnectivity.httpFailure'
          defaultMessage='The site appears to be blocked because the {what} with {errorMessage}.'
          values={{
            what: <FormattedMessage
              id='nettests.webConnectivity.httpFailure.what'
              defaultMessage='HTTP request failed'
            />,
            errorMessage: {measurement.test_keys.http_experiment_failure}
          }}
        />

      }

      {measurement.test_keys.blocking === 'tcp_ip' &&

        <FormattedMessage
          id='nettests.webConnectivity.tcpIP'
          defaultMessage='The site appears to be blocked by means of {reason}'
          values={{
            reason: <FormattedMessage
              id='nettests.webConnectivity.tcpIP.reason'
              defaultMessage='TCP/IP based blocking'
            />
          }}
        />

      }

      {measurement.test_keys.blocking !== false &&
      measurement.test_keys.blocking != null &&

          <FormattedMessage
            id='nettests.webConnectivity.circumventionStrategies.title'
            defaultMessage='Circumvention strategies'
          />

          <FormattedMessage
            id='nettests.webConnectivity.circumventionStrategies.text1'
            defaultMessage='You can try to circumvent the blocking of the site {site} throught the following:'
            values={{
              site: {measurement.input}
            }}
          />

        {(measurement.test_keys.blocking === 'http-diff' ||
          measurement.test_keys.blocking === 'http-failure') &&
        measurement.input.substr(0, 5) !== 'https' &&

            <FormattedMessage
              id='nettests.circumventionStrategies.useHTTPS.title'
              defaultMessage='Use Secure HTTP (HTTPS)'
            />

            <FormattedMessage
              id='nettests.circumventionStrategies.useHTTPS.text'
              defaultMessage='Try visiting the {https} version of the website in question by accessing it via {url}'
              values={{
                https: HTTPS,
                url: <a target='_blank'
                  href={`https${measurement.input.substr(4)}`}>https{measurement.input.substr(4)}
              }}
            />

        }
        {measurement.test_keys.blocking === 'dns' &&

            <FormattedMessage
              id='nettests.webConnectivity.circumvention.changeDNS.title'
              defaultMessage='Change your DNS'
            />

            <FormattedMessage
              id='nettests.webConnectivity.circumvention.changeDNS.text'
              defaultMessage='Try {changeDNS} to one that does not
            implement blocking, such as the OpenDNS ({openDNS}) or Google DNS: {googleDNS}.'
              values={{
                changeDNS: <FormattedMessage
                  id='nettests.webConnectivity.circumvention.changeDNS.text.changeDNS'
                  defaultMessage='changing your DNS resolver'
                />,
                googleDNS: 8.8.8.8,
                openDNS: 208.67.222.222
              }}
            />

            <FormattedMessage
              id='nettests.webConnectivity.circumvention.changeDNS.instructions'
              defaultMessage={`You can find instructions on how to change your DNS resolver to the through the
 following URL: {instructionsLink}`}
              values={{
                instructionsLink:
                  https://www.opendns.com/setupguide/

              }}
            />

        }

<FormattedMessage
            id='nettests.webConnectivity.circumvention.useTor.title'
            defaultMessage='Use Tor'
          />

            <FormattedMessage
              id='nettests.webConnectivity.circumvention.useTor.text'
              defaultMessage='In most cases you should be able to circumvent censorship by using {Tor}.'
              values={{
                Tor: Tor
              }} />
            <FormattedMessage
              id='nettests.webConnectivity.circumvention.useTor.downloadTor'
              defaultMessage={`On a computer you can use {torBrowserLink}, on Android {orbotLink} and on
 iOS {onionBrowserLink}`}
              values={{
                torBrowserLink:

                    Tor Browser
                  ,
                orbotLink:

                    OrBot
                  ,
                onionBrowser:

                    Onion Browser

              }}
            />

            <FormattedMessage
              id='nettests.webConnectivity.circumvention.useTor.torBlocked'
              defaultMessage={`If the torproject.org website is blocked in your country you can download Tor
 from a mirror of it hosted on github: {gettorLink}`}
              values={{
                gettorLink:
                              https://github.com/TheTorProject/gettorbrowser

              }}
            />

          <FormattedMessage
            id='nettests.webConnectivity.circumvention.note'
            defaultMessage='Note: The above techniques might not always work.'
          />

      }

  )
}

WebConnectivityDetails.propTypes = {
  measurement: React.PropTypes.object
}

export default WebConnectivityDetails
<|endoftext|>


<commit_before>var positive = require('stay-positive');
var cost = require('overall-loan-cost');
var amortize = require('amortize');
var humanizeLoanType = require('../humanize-loan-type');

var TAX_RATE = 0.01;
var INSURANCE_RATE = 0.005;

var mortgage = {};

mortgage['loan-amount'] = function (loan) {
    return loan['price'] - loan['downpayment'] || 0;
};

mortgage['discount'] = function (loan) {
    return (loan['points'] / 100) * loan['loan-amount'];
};

mortgage['downpayment-percent'] = function (loan) {
    var dp = +loan['downpayment'];
    var price = +loan['price'];
    return (dp && price) ? Math.round(dp / price * 100) : 0;
};

mortgage['downpayment'] = function (loan) {
    return Math.round((+loan['downpayment-percent'] / 100) * +loan['price']);
};

mortgage['processing'] = function (loan) {
    return loan['loan-amount'] / 100;
};

mortgage['lender-fees'] = function (loan) {
    return loan['processing'] + loan['discount'] || 0;
};

mortgage['third-party-services'] = function (loan) {
    return 3000;
};

mortgage['insurance'] = function (loan) {
    var upfront = (loan['mtg-ins-data'] || {}).upfront;
    if (upfront) {
        return Math.round((upfront / 100) * loan['loan-amount']);
    }
    return 0;
};

mortgage['third-party-fees'] = function (loan) {
    return loan['third-party-services'];
}

mortgage['taxes-gov-fees'] = function (loan) {
    return 1000;
};

mortgage['prepaid-expenses'] = function (loan) {
    var prepaidInterest = loan['loan-amount'] * (loan['interest-rate'] / 100) / 365 * 15,
        prepaidInsurance = INSURANCE_RATE * loan['price'] / 12 * 6;
    return Math.round(prepaidInterest + prepaidInsurance);
};

mortgage['initial-escrow'] = function (loan) {
    var initialTaxes = TAX_RATE * loan['price'] / 12 * 2,
        initialInsurance = INSURANCE_RATE * loan['price'] / 12 * 2;
    return Math.round(initialTaxes + initialInsurance);
};

mortgage['monthly-taxes-insurance'] = function (loan) {
    var propertyTaxes = (loan['price'] / 100) / 12,
        homeInsurance = (INSURANCE_RATE * loan['price']) / 12;
    return propertyTaxes + homeInsurance;
};

mortgage['monthly-hoa-dues'] = function (loan) {
    return 0;
};

mortgage['monthly-principal-interest'] = function (loan) {
    return Math.round(amortize({
                  amount: positive(loan['loan-amount']),
                  rate: loan['interest-rate'],
                  totalTerm: loan['loan-term'] * 12,
                  // since we are starting a new loan,
                  // amortizeTerm is 0, since we haven't make
                  // any payment yet
                  amortizeTerm: 0
                }).payment);
};

mortgage['monthly-mortgage-insurance'] = function (loan) {
    var monthly = (loan['mtg-ins-data'] || {}).monthly;
    if (monthly) {
        return Math.round((monthly / 100) * loan['loan-amount'] / 12);
    }
    return 0;
};

mortgage['monthly-payment'] = function (loan) {
    return loan['monthly-taxes-insurance']
        + loan['monthly-mortgage-insurance']
        + loan['monthly-hoa-dues']
        + loan['monthly-principal-interest'];
};

mortgage['closing-costs'] = function (loan) {
    return loan['downpayment']
            + loan['discount']
            + loan['processing']
            + loan['third-party-services']
            + loan['insurance']
            + loan['taxes-gov-fees']
            + loan['prepaid-expenses']
            + loan['initial-escrow'];
};

mortgage['get-cost'] = function (loan) {
    return cost({
        amountBorrowed: positive(loan['loan-amount']),
        rate: loan['interest-rate'],
        totalTerm: loan['loan-term'] * 12,
        downPayment: loan['downpayment'],
        closingCosts: loan['closing-costs']
    });
};

mortgage['principal-paid'] = function (loan) {
    return mortgage['get-cost'](loan).totalEquity;
};

mortgage['interest-fees-paid'] = function (loan) {
    return mortgage['get-cost'](loan).totalCost;
};

mortgage['overall-costs'] = function (loan) {
    return mortgage['get-cost'](loan).overallCost;
}

mortgage['loan-summary'] = function (loan) {
    if (loan['rate-structure'] === 'arm') {
        return loan['arm-type'].split('-').join('/') + ' ARM';
    } else {
        return loan['loan-term'] + '-year ' + loan['rate-structure'] + ' ' + humanizeLoanType(loan['loan-type']);
    }
};

module.exports = mortgage;<commit_msg>add third party fees accurate calculation<commit_after>var positive = require('stay-positive');
var cost = require('overall-loan-cost');
var amortize = require('amortize');
var humanizeLoanType = require('../humanize-loan-type');

var TAX_RATE = 0.01;
var INSURANCE_RATE = 0.005;

var mortgage = {};

mortgage['loan-amount'] = function (loan) {
    return loan['price'] - loan['downpayment'] || 0;
};

mortgage['discount'] = function (loan) {
    return (loan['points'] / 100) * loan['loan-amount'];
};

mortgage['downpayment-percent'] = function (loan) {
    var dp = +loan['downpayment'];
    var price = +loan['price'];
    return (dp && price) ? Math.round(dp / price * 100) : 0;
};

mortgage['downpayment'] = function (loan) {
    return Math.round((+loan['downpayment-percent'] / 100) * +loan['price']);
};

mortgage['processing'] = function (loan) {
    return loan['loan-amount'] / 100;
};

mortgage['lender-fees'] = function (loan) {
    return loan['processing'] + loan['discount'] || 0;
};

mortgage['third-party-services'] = function (loan) {
    return 3000;
};

mortgage['insurance'] = function (loan) {
    var upfront = (loan['mtg-ins-data'] || {}).upfront;
    if (upfront) {
        return Math.round((upfront / 100) * loan['loan-amount']);
    }
    return 0;
};

mortgage['third-party-fees'] = function (loan) {
    return loan['third-party-services'] + loan['insurance'];
}

mortgage['taxes-gov-fees'] = function (loan) {
    return 1000;
};

mortgage['prepaid-expenses'] = function (loan) {
    var prepaidInterest = loan['loan-amount'] * (loan['interest-rate'] / 100) / 365 * 15,
        prepaidInsurance = INSURANCE_RATE * loan['price'] / 12 * 6;
    return Math.round(prepaidInterest + prepaidInsurance);
};

mortgage['initial-escrow'] = function (loan) {
    var initialTaxes = TAX_RATE * loan['price'] / 12 * 2,
        initialInsurance = INSURANCE_RATE * loan['price'] / 12 * 2;
    return Math.round(initialTaxes + initialInsurance);
};

mortgage['monthly-taxes-insurance'] = function (loan) {
    var propertyTaxes = (loan['price'] / 100) / 12,
        homeInsurance = (INSURANCE_RATE * loan['price']) / 12;
    return propertyTaxes + homeInsurance;
};

mortgage['monthly-hoa-dues'] = function (loan) {
    return 0;
};

mortgage['monthly-principal-interest'] = function (loan) {
    return Math.round(amortize({
                  amount: positive(loan['loan-amount']),
                  rate: loan['interest-rate'],
                  totalTerm: loan['loan-term'] * 12,
                  // since we are starting a new loan,
                  // amortizeTerm is 0, since we haven't make
                  // any payment yet
                  amortizeTerm: 0
                }).payment);
};

mortgage['monthly-mortgage-insurance'] = function (loan) {
    var monthly = (loan['mtg-ins-data'] || {}).monthly;
    if (monthly) {
        return Math.round((monthly / 100) * loan['loan-amount'] / 12);
    }
    return 0;
};

mortgage['monthly-payment'] = function (loan) {
    return loan['monthly-taxes-insurance']
        + loan['monthly-mortgage-insurance']
        + loan['monthly-hoa-dues']
        + loan['monthly-principal-interest'];
};

mortgage['closing-costs'] = function (loan) {
    return loan['downpayment']
            + loan['discount']
            + loan['processing']
            + loan['third-party-services']
            + loan['insurance']
            + loan['taxes-gov-fees']
            + loan['prepaid-expenses']
            + loan['initial-escrow'];
};

mortgage['get-cost'] = function (loan) {
    return cost({
        amountBorrowed: positive(loan['loan-amount']),
        rate: loan['interest-rate'],
        totalTerm: loan['loan-term'] * 12,
        downPayment: loan['downpayment'],
        closingCosts: loan['closing-costs']
    });
};

mortgage['principal-paid'] = function (loan) {
    return mortgage['get-cost'](loan).totalEquity;
};

mortgage['interest-fees-paid'] = function (loan) {
    return mortgage['get-cost'](loan).totalCost;
};

mortgage['overall-costs'] = function (loan) {
    return mortgage['get-cost'](loan).overallCost;
}

mortgage['loan-summary'] = function (loan) {
    if (loan['rate-structure'] === 'arm') {
        return loan['arm-type'].split('-').join('/') + ' ARM';
    } else {
        return loan['loan-term'] + '-year ' + loan['rate-structure'] + ' ' + humanizeLoanType(loan['loan-type']);
    }
};

module.exports = mortgage;<|endoftext|>


<commit_before>module.exports = {
  require: [
    'spec/support/mocha-path-mappings.js',
    'dist/spec/helpers/polyfills.js',
    'dist/spec/helpers/testScheduler-ui.js'
  ],
  ui: [
    'dist/spec/helpers/testScheduler-ui.js'
  ],
  reporter: 'dot',
  timeout: 5000,
  recursive: true,
};<commit_msg>chore: ensure source maps are used in test results<commit_after>module.exports = {
  require: [
    'spec/support/mocha-path-mappings.js',
    'dist/spec/helpers/polyfills.js',
    'dist/spec/helpers/testScheduler-ui.js'
  ],
  ui: [
    'dist/spec/helpers/testScheduler-ui.js'
  ],
  reporter: 'dot',
  timeout: 5000,
  recursive: true,
  'enable-source-maps': true
};<|endoftext|>


<commit_before>"use strict";
const Intent = require("../..").Intent;
const log = require("../log");

describe("Intent", function() {
    let intent, client, botClient;
    const userId = "@alice:bar";
    const botUserId = "@bot:user";
    const roomId = "!foo:bar";
    const alreadyRegistered = {
        registered: true
    };

    beforeEach(
    /** @this */
    function() {
        log.beforeEach(this);
        const clientFields = [
            "credentials", "joinRoom", "invite", "leave", "ban", "unban",
            "kick", "getStateEvent", "setPowerLevel", "sendTyping", "sendEvent",
            "sendStateEvent", "setDisplayName", "setAvatarUrl",
        ];
        client = jasmine.createSpyObj("client", clientFields);
        client.credentials.userId = userId;
        botClient = jasmine.createSpyObj("botClient", clientFields);
        botClient.credentials.userId = botUserId;
        intent = new Intent(client, botClient, alreadyRegistered);
    });

    describe("join", function() {

        it("should /join/$ROOMID if it doesn't know it is already joined",
        function() {
            client.joinRoom.and.callFake(() => Promise.resolve({}));
            return intent.join(roomId).then(function() {
                expect(client.joinRoom).toHaveBeenCalledWith(
                    roomId, { syncRoom: false }
                );
            });
        });

        it("should no-op if it knows it is already joined", function() {
            intent.onEvent({
                event_id: "test",
                type: "m.room.member",
                state_key: userId,
                room_id: roomId,
                content: {
                    membership: "join"
                }
            });
            return intent.join(roomId).then(function() {
                expect(client.joinRoom).not.toHaveBeenCalled();
            });
        });

        it("should fail if the join returned an error other than forbidden",
        function() {
            client.joinRoom.and.callFake(() => Promise.reject({
                errcode: "M_YOU_ARE_A_FISH",
                error: "you're a fish"
            }));
            return intent.join(roomId).catch(function() {
                expect(client.joinRoom).toHaveBeenCalled();
            });
        });

        describe("client join failed", function() {

            it("should make the bot invite then the client join", function() {
                client.joinRoom.and.callFake(function() {
                    if (botClient.invite.calls.count() === 0) {
                        return Promise.reject({
                            errcode: "M_FORBIDDEN",
                            error: "Join first"
                        });
                    }
                    return Promise.resolve({});
                });
                botClient.invite.and.callFake(() => Promise.resolve({}));

                return intent.join(roomId).then(function() {
                    expect(client.joinRoom).toHaveBeenCalledWith(
                        roomId, { syncRoom: false }
                    );
                    expect(botClient.invite).toHaveBeenCalledWith(roomId, userId);
                });
            });

            describe("bot invite failed", function() {
                it("should make the bot join then invite then the client join",
                function() {
                    client.joinRoom.and.callFake(function() {
                        if (botClient.invite.calls.count() === 0) {
                            return Promise.reject({
                                errcode: "M_FORBIDDEN",
                                error: "Join first"
                            });
                        }
                        return Promise.resolve({});
                    });
                    botClient.invite.and.callFake(function() {
                        if (botClient.joinRoom.calls.count() === 0) {
                            return Promise.reject({
                                errcode: "M_FORBIDDEN",
                                error: "Join first"
                            });
                        }
                        return Promise.resolve({});
                    });
                    botClient.joinRoom.and.callFake(() => Promise.resolve({}));

                    return intent.join(roomId).then(function() {
                        expect(client.joinRoom).toHaveBeenCalledWith(
                            roomId, { syncRoom: false }
                        );
                        expect(botClient.invite).toHaveBeenCalledWith(roomId, userId);
                        expect(botClient.joinRoom).toHaveBeenCalledWith(
                            roomId, { syncRoom: false }
                        );
                    });
                });

                it("should give up if the bot cannot join the room", function() {
                    client.joinRoom.and.callFake(() => Promise.reject({
                        errcode: "M_FORBIDDEN",
                        error: "Join first"
                    }));
                    botClient.invite.and.callFake(() => Promise.reject({
                        errcode: "M_FORBIDDEN",
                        error: "No invites kthx"
                    }));
                    botClient.joinRoom.and.callFake(() => Promise.reject({
                        errcode: "M_FORBIDDEN",
                        error: "No bots allowed!"
                    }));

                    return intent.join(roomId).catch(function() {
                        expect(client.joinRoom).toHaveBeenCalledWith(
                            roomId, { syncRoom: false }
                        );
                        expect(botClient.invite).toHaveBeenCalledWith(roomId, userId);
                        expect(botClient.joinRoom).toHaveBeenCalledWith(
                            roomId, { syncRoom: false }
                        );
                    });
                });
            });
        });
    });

    describe("sending state events", function() {
        let validPowerLevels, invalidPowerLevels;

        beforeEach(() => {
            // not interested in joins, so no-op them.
            intent.onEvent({
                event_id: "test",
                type: "m.room.member",
                state_key: userId,
                room_id: roomId,
                content: {
                    membership: "join"
                }
            });

            const basePowerLevelEvent = {
                content: {
                    "ban": 50,
                    "events": {
                        "m.room.name": 100,
                        "m.room.power_levels": 100
                    },
                    "events_default": 0,
                    "kick": 50,
                    "redact": 50,
                    "state_default": 50,
                    "users": {
                        "@example:localhost": 100
                    },
                    "users_default": 0
                },
                state_key: "",
                room_id: roomId,
                sender: "@example:localhost",
                type: "m.room.power_levels",
                event_id: "test2"
            };
            validPowerLevels = JSON.parse(JSON.stringify(basePowerLevelEvent));
            validPowerLevels.content.users[userId] = 100;
            invalidPowerLevels = basePowerLevelEvent;
        });

        it("should directly send the event if it thinks power levels are ok",
        function() {
            client.sendStateEvent.and.returnValue(Promise.resolve({}));

            intent.onEvent(validPowerLevels);
            return intent.setRoomTopic(roomId, "Hello world").then(function() {
                expect(client.sendStateEvent).toHaveBeenCalledWith(
                    roomId, "m.room.topic", {topic: "Hello world"}, ""
                );
            })
        });

        it("should get the power levels before sending if it doesn't know them",
        function() {
            client.sendStateEvent.and.returnValue(Promise.resolve({}));
            client.getStateEvent.and.returnValue(
                Promise.resolve(validPowerLevels.content)
            );

            return intent.setRoomTopic(roomId, "Hello world").then(function() {
                expect(client.getStateEvent).toHaveBeenCalledWith(
                    roomId, "m.room.power_levels", ""
                );
                expect(client.sendStateEvent).toHaveBeenCalledWith(
                    roomId, "m.room.topic", {topic: "Hello world"}, ""
                );
            })
        });

        it("should modify power levels before sending if client is too low",
        function() {
            client.sendStateEvent.and.callFake(function() {
                if (botClient.setPowerLevel.calls.count() > 0) {
                    return Promise.resolve({});
                }
                return Promise.reject({
                    errcode: "M_FORBIDDEN",
                    error: "Not enough powaaaaaa"
                });
            });
            botClient.setPowerLevel.and.returnValue(Promise.resolve({}));
            // give the power to the bot
            invalidPowerLevels.content.users[botUserId] = 100;
            intent.onEvent(invalidPowerLevels);

            return intent.setRoomTopic(roomId, "Hello world").then(function() {
                expect(client.sendStateEvent).toHaveBeenCalledWith(
                    roomId, "m.room.topic", {topic: "Hello world"}, ""
                );
                expect(botClient.setPowerLevel).toHaveBeenCalledWith(
                    roomId, userId, 50, jasmine.any(Object)
                );
            })
        });

        it("should fail if the bot cannot modify power levels and the client is too low",
        function() {
            // bot has NO power
            intent.onEvent(invalidPowerLevels);

            return intent.setRoomTopic(roomId, "Hello world").catch(function() {
                expect(client.sendStateEvent).not.toHaveBeenCalled();
                expect(botClient.setPowerLevel).not.toHaveBeenCalled();
            })
        });
    });

    describe("sending message events", function() {
        const content = {
            body: "hello world",
            msgtype: "m.text",
        };

        beforeEach(function() {
            intent = new Intent(client, botClient, {
                ...alreadyRegistered,
                dontCheckPowerLevel: true,
            });
            // not interested in joins, so no-op them.
            intent.onEvent({
                event_id: "test",
                type: "m.room.member",
                state_key: userId,
                room_id: roomId,
                content: {
                    membership: "join"
                }
            });
        });

        it("should immediately try to send the event if joined/have pl", function() {
            client.sendEvent.and.returnValue(Promise.resolve({
                event_id: "$abra:kadabra"
            }));
            return intent.sendMessage(roomId, content).then(function() {
                expect(client.sendEvent).toHaveBeenCalledWith(
                    roomId, "m.room.message", content
                );
                expect(client.joinRoom).not.toHaveBeenCalled();
            });
        });

        it("should fail if get an error that isn't M_FORBIDDEN", function() {
            client.sendEvent.and.callFake(() => Promise.reject({
                error: "Oh no",
                errcode: "M_UNKNOWN"
            }));
            return intent.sendMessage(roomId, content).catch(function() {
                expect(client.sendEvent).toHaveBeenCalledWith(
                    roomId, "m.room.message", content
                );
                expect(client.joinRoom).not.toHaveBeenCalled();
            });
        });

        it("should try to join the room on M_FORBIDDEN then resend", function() {
            let isJoined = false;
            client.sendEvent.and.callFake(function() {
                if (isJoined) {
                    return Promise.resolve({
                        event_id: "$12345:6789"
                    });
                }
                return Promise.reject({
                    error: "You are not joined",
                    errcode: "M_FORBIDDEN"
                });
            });
            client.joinRoom.and.callFake(function(joinRoomId) {
                isJoined = true;
                return Promise.resolve({
                    room_id: joinRoomId,
                });
            });
            return intent.sendMessage(roomId, content).then(function() {
                expect(client.sendEvent).toHaveBeenCalledWith(
                    roomId, "m.room.message", content
                );
                expect(client.joinRoom).toHaveBeenCalledWith(roomId, { syncRoom: false });
            });
        });

        it("should fail if the join on M_FORBIDDEN fails", function() {
            client.sendEvent.and.callFake(function() {
                return Promise.reject({
                    error: "You are not joined",
                    errcode: "M_FORBIDDEN"
                });
            });
            client.joinRoom.and.callFake(() => Promise.reject({
                error: "Never!",
                errcode: "M_YOU_ARE_A_FISH"
            }));
            return intent.sendMessage(roomId, content).catch(function() {
                expect(client.sendEvent).toHaveBeenCalledWith(
                    roomId, "m.room.message", content
                );
                expect(client.joinRoom).toHaveBeenCalledWith(roomId, { syncRoom: false });
            });
        });

        it("should fail if the resend after M_FORBIDDEN fails", function() {
            let isJoined = false;
            client.sendEvent.and.callFake(function() {
                if (isJoined) {
                    return Promise.reject({
                        error: "Internal Server Error",
                        errcode: "M_WHOOPSIE",
                    });
                }
                return Promise.reject({
                    error: "You are not joined",
                    errcode: "M_FORBIDDEN",
                });
            });
            client.joinRoom.and.callFake(function(joinRoomId) {
                isJoined = true;
                return Promise.resolve({
                    room_id: joinRoomId,
                });
            });
            return intent.sendMessage(roomId, content).catch(function() {
                expect(client.sendEvent).toHaveBeenCalledWith(
                    roomId, "m.room.message", content
                );
                expect(client.joinRoom).toHaveBeenCalledWith(roomId, { syncRoom: false });
            });
        });
    });

    describe("signaling bridge error", function() {
        const reason = "m.event_not_handled"
        let affectedUsers, eventId, bridge;

        beforeEach(function() {
            intent = new Intent(client, botClient, {
                ...alreadyRegistered,
                dontCheckPowerLevel: true,
            });
            // not interested in joins, so no-op them.
            intent.onEvent({
                event_id: "test",
                type: "m.room.member",
                state_key: userId,
                room_id: roomId,
                content: {
                    membership: "join"
                }
            });
            eventId = "$random:event.id";
            bridge = "International Pidgeon Post";
            affectedUsers = ["@_pidgeonpost_.*:home.server"];
        });

        it("should send an event", function() {
            client.sendEvent.and.returnValue(Promise.resolve({
                event_id: "$abra:kadabra"
            }));
            return intent
            .unstableSignalBridgeError(roomId, eventId, bridge, reason, affectedUsers)
            .then(() => {
                expect(client.sendEvent).toHaveBeenCalledWith(
                    roomId,
                    "de.nasnotfound.bridge_error",
                    {
                        "network_name": bridge,
                        "reason": reason,
                        "affected_users": affectedUsers,
                        "m.relates_to": {
                            "rel_type": "m.reference",
                            "event_id": eventId
                        }
                    }
                );
                expect(client.joinRoom).not.toHaveBeenCalled();
            });
        });
    });
});
<commit_msg>Add supporting tests for intent joins<commit_after>const { Intent } = require("../..");
const log = require("../log");

describe("Intent", function() {
    let intent, client, botClient;
    const userId = "@alice:bar";
    const botUserId = "@bot:user";
    const roomId = "!foo:bar";
    const alreadyRegistered = {
        registered: true
    };

    beforeEach(
    /** @this */
    function() {
        log.beforeEach(this);
        const clientFields = [
            "credentials", "joinRoom", "invite", "leave", "ban", "unban",
            "kick", "getStateEvent", "setPowerLevel", "sendTyping", "sendEvent",
            "sendStateEvent", "setDisplayName", "setAvatarUrl",
        ];
        client = jasmine.createSpyObj("client", clientFields);
        client.credentials.userId = userId;
        botClient = jasmine.createSpyObj("botClient", clientFields);
        botClient.credentials.userId = botUserId;
        intent = new Intent(client, botClient, alreadyRegistered);
    });

    describe("join", function() {

        it("should /join/$ROOMID if it doesn't know it is already joined",
        function() {
            client.joinRoom.and.callFake(() => Promise.resolve({room_id: roomId}));
            return intent.join(roomId).then(function(resultRoomId) {
                expect(client.joinRoom).toHaveBeenCalledWith(
                    roomId, { syncRoom: false }
                );
                expect(resultRoomId).toBe(roomId);
            });
        });

        it("should no-op if it knows it is already joined", function() {
            intent.onEvent({
                event_id: "test",
                type: "m.room.member",
                state_key: userId,
                room_id: roomId,
                content: {
                    membership: "join"
                }
            });
            return intent.join(roomId).then(function(resultRoomId) {
                expect(resultRoomId).toBe(roomId);
                expect(client.joinRoom).not.toHaveBeenCalled();
            });
        });

        it("should fail if the join returned an error other than forbidden",
        function() {
            client.joinRoom.and.callFake(() => Promise.reject({
                errcode: "M_YOU_ARE_A_FISH",
                error: "you're a fish"
            }));
            return intent.join(roomId).catch(function() {
                expect(client.joinRoom).toHaveBeenCalled();
            });
        });

        describe("client join failed", function() {

            it("should make the bot invite then the client join", function() {
                client.joinRoom.and.callFake(function() {
                    if (botClient.invite.calls.count() === 0) {
                        return Promise.reject({
                            errcode: "M_FORBIDDEN",
                            error: "Join first"
                        });
                    }
                    return Promise.resolve({room_id: roomId});
                });
                botClient.invite.and.callFake(() => Promise.resolve({}));

                return intent.join(roomId).then(function(resultRoomId) {
                    expect(client.joinRoom).toHaveBeenCalledWith(
                        roomId, { syncRoom: false }
                    );
                    expect(botClient.invite).toHaveBeenCalledWith(roomId, userId);
                    expect(resultRoomId).toBe(roomId);
                });
            });

            describe("bot invite failed", function() {
                it("should make the bot join then invite then the client join",
                function() {
                    client.joinRoom.and.callFake(function() {
                        if (botClient.invite.calls.count() === 0) {
                            return Promise.reject({
                                errcode: "M_FORBIDDEN",
                                error: "Join first"
                            });
                        }
                        return Promise.resolve({});
                    });
                    botClient.invite.and.callFake(function() {
                        if (botClient.joinRoom.calls.count() === 0) {
                            return Promise.reject({
                                errcode: "M_FORBIDDEN",
                                error: "Join first"
                            });
                        }
                        return Promise.resolve({});
                    });
                    botClient.joinRoom.and.callFake(() => Promise.resolve({room_id: roomId}));

                    return intent.join(roomId).then(function(resultRoomId) {
                        expect(client.joinRoom).toHaveBeenCalledWith(
                            roomId, { syncRoom: false }
                        );
                        expect(botClient.invite).toHaveBeenCalledWith(roomId, userId);
                        expect(botClient.joinRoom).toHaveBeenCalledWith(
                            roomId, { syncRoom: false }
                        );
                        expect(resultRoomId).toBe(roomId);

                    });
                });

                it("should give up if the bot cannot join the room", function() {
                    client.joinRoom.and.callFake(() => Promise.reject({
                        errcode: "M_FORBIDDEN",
                        error: "Join first"
                    }));
                    botClient.invite.and.callFake(() => Promise.reject({
                        errcode: "M_FORBIDDEN",
                        error: "No invites kthx"
                    }));
                    botClient.joinRoom.and.callFake(() => Promise.reject({
                        errcode: "M_FORBIDDEN",
                        error: "No bots allowed!"
                    }));

                    return intent.join(roomId).catch(function() {
                        expect(client.joinRoom).toHaveBeenCalledWith(
                            roomId, { syncRoom: false }
                        );
                        expect(botClient.invite).toHaveBeenCalledWith(roomId, userId);
                        expect(botClient.joinRoom).toHaveBeenCalledWith(
                            roomId, { syncRoom: false }
                        );
                    });
                });
            });
        });
    });

    describe("sending state events", function() {
        let validPowerLevels, invalidPowerLevels;

        beforeEach(() => {
            // not interested in joins, so no-op them.
            intent.onEvent({
                event_id: "test",
                type: "m.room.member",
                state_key: userId,
                room_id: roomId,
                content: {
                    membership: "join"
                }
            });

            const basePowerLevelEvent = {
                content: {
                    "ban": 50,
                    "events": {
                        "m.room.name": 100,
                        "m.room.power_levels": 100
                    },
                    "events_default": 0,
                    "kick": 50,
                    "redact": 50,
                    "state_default": 50,
                    "users": {
                        "@example:localhost": 100
                    },
                    "users_default": 0
                },
                state_key: "",
                room_id: roomId,
                sender: "@example:localhost",
                type: "m.room.power_levels",
                event_id: "test2"
            };
            validPowerLevels = JSON.parse(JSON.stringify(basePowerLevelEvent));
            validPowerLevels.content.users[userId] = 100;
            invalidPowerLevels = basePowerLevelEvent;
        });

        it("should directly send the event if it thinks power levels are ok",
        function() {
            client.sendStateEvent.and.returnValue(Promise.resolve({}));

            intent.onEvent(validPowerLevels);
            return intent.setRoomTopic(roomId, "Hello world").then(function() {
                expect(client.sendStateEvent).toHaveBeenCalledWith(
                    roomId, "m.room.topic", {topic: "Hello world"}, ""
                );
            })
        });

        it("should get the power levels before sending if it doesn't know them",
        function() {
            client.sendStateEvent.and.returnValue(Promise.resolve({}));
            client.getStateEvent.and.returnValue(
                Promise.resolve(validPowerLevels.content)
            );

            return intent.setRoomTopic(roomId, "Hello world").then(function() {
                expect(client.getStateEvent).toHaveBeenCalledWith(
                    roomId, "m.room.power_levels", ""
                );
                expect(client.sendStateEvent).toHaveBeenCalledWith(
                    roomId, "m.room.topic", {topic: "Hello world"}, ""
                );
            })
        });

        it("should modify power levels before sending if client is too low",
        function() {
            client.sendStateEvent.and.callFake(function() {
                if (botClient.setPowerLevel.calls.count() > 0) {
                    return Promise.resolve({});
                }
                return Promise.reject({
                    errcode: "M_FORBIDDEN",
                    error: "Not enough powaaaaaa"
                });
            });
            botClient.setPowerLevel.and.returnValue(Promise.resolve({}));
            // give the power to the bot
            invalidPowerLevels.content.users[botUserId] = 100;
            intent.onEvent(invalidPowerLevels);

            return intent.setRoomTopic(roomId, "Hello world").then(function() {
                expect(client.sendStateEvent).toHaveBeenCalledWith(
                    roomId, "m.room.topic", {topic: "Hello world"}, ""
                );
                expect(botClient.setPowerLevel).toHaveBeenCalledWith(
                    roomId, userId, 50, jasmine.any(Object)
                );
            })
        });

        it("should fail if the bot cannot modify power levels and the client is too low",
        function() {
            // bot has NO power
            intent.onEvent(invalidPowerLevels);

            return intent.setRoomTopic(roomId, "Hello world").catch(function() {
                expect(client.sendStateEvent).not.toHaveBeenCalled();
                expect(botClient.setPowerLevel).not.toHaveBeenCalled();
            })
        });
    });

    describe("sending message events", function() {
        const content = {
            body: "hello world",
            msgtype: "m.text",
        };

        beforeEach(function() {
            intent = new Intent(client, botClient, {
                ...alreadyRegistered,
                dontCheckPowerLevel: true,
            });
            // not interested in joins, so no-op them.
            intent.onEvent({
                event_id: "test",
                type: "m.room.member",
                state_key: userId,
                room_id: roomId,
                content: {
                    membership: "join"
                }
            });
        });

        it("should immediately try to send the event if joined/have pl", function() {
            client.sendEvent.and.returnValue(Promise.resolve({
                event_id: "$abra:kadabra"
            }));
            return intent.sendMessage(roomId, content).then(function() {
                expect(client.sendEvent).toHaveBeenCalledWith(
                    roomId, "m.room.message", content
                );
                expect(client.joinRoom).not.toHaveBeenCalled();
            });
        });

        it("should fail if get an error that isn't M_FORBIDDEN", function() {
            client.sendEvent.and.callFake(() => Promise.reject({
                error: "Oh no",
                errcode: "M_UNKNOWN"
            }));
            return intent.sendMessage(roomId, content).catch(function() {
                expect(client.sendEvent).toHaveBeenCalledWith(
                    roomId, "m.room.message", content
                );
                expect(client.joinRoom).not.toHaveBeenCalled();
            });
        });

        it("should try to join the room on M_FORBIDDEN then resend", function() {
            let isJoined = false;
            client.sendEvent.and.callFake(function() {
                if (isJoined) {
                    return Promise.resolve({
                        event_id: "$12345:6789"
                    });
                }
                return Promise.reject({
                    error: "You are not joined",
                    errcode: "M_FORBIDDEN"
                });
            });
            client.joinRoom.and.callFake(function(joinRoomId) {
                isJoined = true;
                return Promise.resolve({
                    room_id: joinRoomId,
                });
            });
            return intent.sendMessage(roomId, content).then(function(eventId) {
                expect(client.sendEvent).toHaveBeenCalledWith(
                    roomId, "m.room.message", content
                );
                expect(client.joinRoom).toHaveBeenCalledWith(roomId, { syncRoom: false });
                expect(eventId).toEqual({event_id: "$12345:6789"});
            });
        });

        it("should fail if the join on M_FORBIDDEN fails", function() {
            client.sendEvent.and.callFake(function() {
                return Promise.reject({
                    error: "You are not joined",
                    errcode: "M_FORBIDDEN"
                });
            });
            client.joinRoom.and.callFake(() => Promise.reject({
                error: "Never!",
                errcode: "M_YOU_ARE_A_FISH"
            }));
            return intent.sendMessage(roomId, content).catch(function() {
                expect(client.sendEvent).toHaveBeenCalledWith(
                    roomId, "m.room.message", content
                );
                expect(client.joinRoom).toHaveBeenCalledWith(roomId, { syncRoom: false });
            });
        });

        it("should fail if the resend after M_FORBIDDEN fails", function() {
            let isJoined = false;
            client.sendEvent.and.callFake(function() {
                if (isJoined) {
                    return Promise.reject({
                        error: "Internal Server Error",
                        errcode: "M_WHOOPSIE",
                    });
                }
                return Promise.reject({
                    error: "You are not joined",
                    errcode: "M_FORBIDDEN",
                });
            });
            client.joinRoom.and.callFake(function(joinRoomId) {
                isJoined = true;
                return Promise.resolve({
                    room_id: joinRoomId,
                });
            });
            return intent.sendMessage(roomId, content).catch(function() {
                expect(client.sendEvent).toHaveBeenCalledWith(
                    roomId, "m.room.message", content
                );
                expect(client.joinRoom).toHaveBeenCalledWith(roomId, { syncRoom: false });
            });
        });
    });

    describe("signaling bridge error", function() {
        const reason = "m.event_not_handled"
        let affectedUsers, eventId, bridge;

        beforeEach(function() {
            intent = new Intent(client, botClient, {
                ...alreadyRegistered,
                dontCheckPowerLevel: true,
            });
            // not interested in joins, so no-op them.
            intent.onEvent({
                event_id: "test",
                type: "m.room.member",
                state_key: userId,
                room_id: roomId,
                content: {
                    membership: "join"
                }
            });
            eventId = "$random:event.id";
            bridge = "International Pidgeon Post";
            affectedUsers = ["@_pidgeonpost_.*:home.server"];
        });

        it("should send an event", function() {
            client.sendEvent.and.returnValue(Promise.resolve({
                event_id: "$abra:kadabra"
            }));
            return intent
            .unstableSignalBridgeError(roomId, eventId, bridge, reason, affectedUsers)
            .then(() => {
                expect(client.sendEvent).toHaveBeenCalledWith(
                    roomId,
                    "de.nasnotfound.bridge_error",
                    {
                        "network_name": bridge,
                        "reason": reason,
                        "affected_users": affectedUsers,
                        "m.relates_to": {
                            "rel_type": "m.reference",
                            "event_id": eventId
                        }
                    }
                );
                expect(client.joinRoom).not.toHaveBeenCalled();
            });
        });
    });
});
<|endoftext|>


<commit_before>var User = require('../../server/api/users/userModel.js'),
    Roadmap = require('../../server/api/roadmaps/roadmapModel.js'),
    Node = require('../../server/api/nodes/nodeModel.js');

var users = [
  {
    username : 'bowieloverx950',
    password : 'c',
    firstName: 'Bob',
    lastName : 'Johnson',
    roadmaps : []
  },

  {
    username : 'supercoder31337',
    password : 'a',
    firstName: 'Susan',
    lastName : 'Kozlowski',
    roadmaps : []
  },

  {
    username : 'alex<3hiphop',
    password : 'b',
    firstName: 'Alejandro',
    lastName : 'Bautista',
    roadmaps : []
  }
];

var maps = [
  {
    title      : 'Understanding Bowie',
    description: 'This roadmap will help you learn about David Bowie',
    nodes      : []
  },

  {
    title      : 'Learning JavaScript',
    description: '134rn 2 b 4n 1337 h4xx0rz',
    nodes      : []
  },

  {
    title      : 'Straight Outta Knowing Nothing About Straight Outta Compton',
    description: 'Your hip hop history. Learn it.',
    nodes      : []
  }
];

var nodes = [
  {
    title        : 'Watch Life On Mars?',
    description  : 'Watch this classic. Absorb the Bowie.',
    resourceType : 'Music Video',
    resourceURL  : 'https://www.youtube.com/watch?v=v--IqqusnNQ',
    parentRoadmap: []
  },

  {
    title        : 'Take Codecademy\'s Intro Course',
    description  : 'OMG 1f u c4nt h4x th15 w4t h0p d0 u h4v???',
    resourceType : 'Online Course',
    resourceURL  : 'https://www.codecademy.com/learn/javascript',
    parentRoadmap: []
  },

  {
    title        : 'Watch Straight Outta Compton',
    description  : 'It\'s in the name. Watch it.',
    resourceType : 'Music Video',
    resourceURL  : 'https://www.youtube.com/watch?v=TMZi25Pq3T8',
    parentRoadmap: []
  }
];

module.exports.users = users;
module.exports.maps = maps;
module.exports.nodes = nodes;

module.exports.seedUsers = function(next) {

  var addUser = function(i) {
    if (i >= users.length) {
      if (next) next();
      return;
    }

    User(users[i]).save()
      .then(function (user) {
        if (user) users[i] = user;
        addUser(i + 1);
      });
  };

  addUser(0);
};

module.exports.clearUsers = function(next) {

  var clearUser = function(i) {
    if (i >= maps.length) {
      if (next) next();
      return;
    }

    User.findOne(users[i].username)
      .then(function (user) {
        if (user) user.remove();
        clearUser(i + 1);
      });
  };

  clearUser(0);
};

module.exports.seedData = function(next) {

  var addRoadmap = function(i) {
    if (i >= maps.length) {
      if (next) next();
      return;
    }

    maps[i].author = users[i]._id;

    Roadmap(maps[i]).save()
      .then(function (map) {
        if (map) maps[i] = map;
        addRoadmap(i + 1);
      });
  };

  module.exports.seedUsers(addRoadmap.bind(null, 0));
};

module.exports.clearData = function(next) {

  var clearRoadmap = function(i) {
    if (i >= maps.length) {
      if (next) next();
      return;
    }

    Roadmap.findOne(maps[i]._id)
      .then(function (map) {
        if (map) map.remove();
        clearRoadmap(i + 1);
      });
  };

  module.exports.clearUsers(clearRoadmap.bind(null, 0));
};
<commit_msg>(refactor) Clean up testData.js next calls.<commit_after>var User = require('../../server/api/users/userModel.js'),
    Roadmap = require('../../server/api/roadmaps/roadmapModel.js'),
    Node = require('../../server/api/nodes/nodeModel.js');

var users = [
  {
    username : 'bowieloverx950',
    password : 'c',
    firstName: 'Bob',
    lastName : 'Johnson',
    roadmaps : []
  },

  {
    username : 'supercoder31337',
    password : 'a',
    firstName: 'Susan',
    lastName : 'Kozlowski',
    roadmaps : []
  },

  {
    username : 'alex<3hiphop',
    password : 'b',
    firstName: 'Alejandro',
    lastName : 'Bautista',
    roadmaps : []
  }
];

var maps = [
  {
    title      : 'Understanding Bowie',
    description: 'This roadmap will help you learn about David Bowie',
    nodes      : []
  },

  {
    title      : 'Learning JavaScript',
    description: '134rn 2 b 4n 1337 h4xx0rz',
    nodes      : []
  },

  {
    title      : 'Straight Outta Knowing Nothing About Straight Outta Compton',
    description: 'Your hip hop history. Learn it.',
    nodes      : []
  }
];

var nodes = [
  {
    title        : 'Watch Life On Mars?',
    description  : 'Watch this classic. Absorb the Bowie.',
    resourceType : 'Music Video',
    resourceURL  : 'https://www.youtube.com/watch?v=v--IqqusnNQ',
    parentRoadmap: []
  },

  {
    title        : 'Take Codecademy\'s Intro Course',
    description  : 'OMG 1f u c4nt h4x th15 w4t h0p d0 u h4v???',
    resourceType : 'Online Course',
    resourceURL  : 'https://www.codecademy.com/learn/javascript',
    parentRoadmap: []
  },

  {
    title        : 'Watch Straight Outta Compton',
    description  : 'It\'s in the name. Watch it.',
    resourceType : 'Music Video',
    resourceURL  : 'https://www.youtube.com/watch?v=TMZi25Pq3T8',
    parentRoadmap: []
  }
];

module.exports.users = users;
module.exports.maps = maps;
module.exports.nodes = nodes;

module.exports.seedUsers = function(next) {

  var addUser = function(i) {
    if (i >= users.length) return next && next();

    User(users[i]).save()
      .then(function (user) {
        if (user) users[i] = user;
        addUser(i + 1);
      });
  };

  addUser(0);
};

module.exports.clearUsers = function(next) {

  var clearUser = function(i) {
    if (i >= users.length) return next && next();

    User.findOne(users[i].username)
      .then(function (user) {
        if (user) user.remove();
        clearUser(i + 1);
      });
  };

  clearUser(0);
};

module.exports.seedData = function(next) {

  var addRoadmap = function(i) {
    if (i >= maps.length) return next && next();

    maps[i].author = users[i]._id;

    Roadmap(maps[i]).save()
      .then(function (map) {
        if (map) maps[i] = map;
        addRoadmap(i + 1);
      });
  };

  module.exports.seedUsers(addRoadmap.bind(null, 0));
};

module.exports.clearData = function(next) {

  var clearRoadmap = function(i) {
    if (i >= maps.length) return next && next();

    Roadmap.findOne(maps[i]._id)
      .then(function (map) {
        if (map) map.remove();
        clearRoadmap(i + 1);
      });
  };

  module.exports.clearUsers(clearRoadmap.bind(null, 0));
};
<|endoftext|>