<commit_before>(function (root, factory) {
	if (typeof exports === 'object') {
		// For nodejs
		module.exports = factory();
	} else {
		// For browsers
		root.opening_hours = factory();
	}
}(this, function () {
	return function(value) {
		//======================================================================
		// Constants
		//======================================================================
		var months = { Jan: 0, Feb: 1, Mar: 2, Apr: 3, May: 4, Jun: 5, Jul: 6, Aug: 7, Sep: 8, Oct: 9, Nov: 10, Dec: 11 };
		var weekdays = { Su: 0, Mo: 1, Tu: 2, We: 3, Th: 4, Fr: 5, Sa: 6 };

		//======================================================================
		// Constructor - entry to parsing code
		//======================================================================
		// Terminology:
		//
		// Mo-Fr 10:00-11:00; Th 10:00-12:00
		// \_____block_____/  \____block___/
		//
		// Mo-Fr Jan 10:00-11:00
		// \__/  \_/ \_________/
		// selectors (left to right: weekday, month, time)
		//
		// Logic:
		// - Split blocks; foreach block:
		// - Tokenize
		// - Run toplevel (block) parser
		//   - Which calls subparser for specific selector types
		//     - Which produce selectors
		var blocks = value.split(/\s*;\s*/);

		for (var block = 0; block < blocks.length; block++) {
			var tokens = tokenize(blocks[block]);

			var selectors = {
				time: [],
				weekday: [],

				meaning: true,
			};

			parseGroup(tokens, 0, selectors);

			blocks[block] = selectors;
		}

		// Tokenization function: splits string into parts
		// output: array of pairs [content, type]
		function tokenize(value) {
			var tokens = new Array();

			while (value != '') {
				var tmp;
				if (tmp = value.match(/^(?:week|24\/7|off)/)) {
					// reserved word
					tokens.push([tmp[0], tmp[0]]);
					value = value.substr(tmp[0].length)
				} else if (tmp = value.match(/^(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)/)) {
					// month name
					tokens.push([months[tmp[0]], 'month']);
					value = value.substr(3);
				} else if (tmp = value.match(/^(?:Mo|Tu|We|Th|Fr|Sa|Su)/)) {
					// weekday name
					tokens.push([weekdays[tmp[0]], 'weekday']);
					value = value.substr(2);
				} else if (tmp = value.match(/^\d+/)) {
					// number
					tokens.push([tmp[0], 'number']);
					value = value.substr(tmp[0].length);
				} else if (value.match(/^\s/)) {
					// whitespace are fixed
					value = value.substr(1)
				} else {
					// other single-character tokens
					tokens.push([value[0], value[0]]);
					value = value.substr(1)
				}
			}

			return tokens;
		}

		// Function to check token array for specific pattern
		function matchTokens(tokens, at, match) {
			for (var i = 0; i < match.length; i++) {
				if (typeof tokens[at + i] === 'undefined')
					return false;
				if (tokens[at + i][1] !== match[i])
					return false;
			}

			return true;
		}

		//======================================================================
		// Top-level parser
		//======================================================================
		function parseGroup(tokens, at, selectors) {
			while (at < tokens.length) {
				if (tokens[at][1] == 'weekday') {
					at = parseWeekdayRange(tokens, at, selectors);
				} else if (tokens[at][1] == 'month' && tokens[at+1][1] == 'number') {
					at = parseMonthDayRange(tokens, at);
				} else if (tokens[at][1] == 'month') {
					at = parseMonthRange(tokens, at);
				} else if (tokens[at][0] == 'week') {
					at = parseWeekRange(tokens, at);
				} else if (tokens[at][1] == 'number') {
					at = parseTimeRange(tokens, at, selectors);
				} else if (tokens[at][0] == 'off') {
					selectors.meaning = false;
					at++;
				} else if (tokens[at][0] == '24/7') {
					selectors.time.push(function(date) { return [true]; });
					at++;
				} else {
					throw 'Unexpected token: "' + tokens[at] + '"';
				}
			}

			return tokens;
		}

		//======================================================================
		// Time range parser (10:00-12:00,14:00-16:00)
		//======================================================================
		function parseTimeRange(tokens, at, selectors) {
			var minutes_in_day = 24 * 60;

			while (at < tokens.length) {
				if (tokens[at][1] == 'number' && tokens[at+1][0] == ':' && tokens[at+2][1] == 'number' && tokens[at+3][0] == '-' && tokens[at+4][1] == 'number' && tokens[at+5][0] == ':' && tokens[at+6][1] == 'number') {
					// Time range
					selectors.time.push(function(tokens, at) { return function(date) {
						var ourminutes = date.getHours() * 60 + date.getMinutes();
						var minutes_from = tokens[at][0] * 60 + tokens[at+2][0] * 1;
						var minutes_to = tokens[at+4][0] * 60 + tokens[at+6][0] * 1;

						// normalize minutes into range
						// XXX: what if it's further than tomorrow?
						// XXX: this is incorrect, as it assumes the same day
						//      should cooperate with date selectors to select the next day
						if (minutes_to > minutes_in_day)
							minutes_to -= minutes_in_day;

						// handle reversed range
						var inside = true;

						if (minutes_to < minutes_from) {
							var tmp = minutes_to;
							minutes_to = minutes_from;
							minutes_from = tmp;
							inside = false;
						}

						if (ourminutes < minutes_from)
							return [!inside, dateAtNextDayMinutes(date, minutes_from)];
						else if (ourminutes < minutes_to)
							return [inside, dateAtNextDayMinutes(date, minutes_to)];
						else
							return [!inside, dateAtNextDayMinutes(date, minutes_from + minutes_in_day)];
					}}(tokens, at));

					at += 7;
				} else {
					throw 'Unexpected token in time range: "' + tokens[at][0] + '"';
				}

				if (at >= tokens.length)
					break;

				if (tokens[at][0] == ',')
					at++;
				else
					break;

			}

			return at;
		}

		// for given date, returns date moved to the start of specified day minute
		function dateAtNextDayMinutes(date, minutes) {
			var tmpdate = new Date(date);
			tmpdate.setHours(0, minutes, 0, 0);
			return tmpdate;
		}

		//======================================================================
		// Weekday range parser (Mo,We-Fr,Sa[1-2,-1])
		//======================================================================
		function parseWeekdayRange(tokens, at, selectors) {
			while (at < tokens.length) {
				if (tokens[at][1] == 'weekday' && tokens[at+1][0] == '[') {
					// Conditional weekday (Mo[3])
					at = parseNumRange(tokens, at+2);
					if (tokens[at][1] !== ']')
						throw '"]" expected';
					at++;
				} else if (tokens[at][1] == 'weekday') {
					// Single weekday (Mo) or weekday range (Mo-Fr)
					var is_range = tokens[at+1][0] == '-' && tokens[at+2][1] == 'weekday';

					selectors.weekday.push(function(tokens, at, is_range) { return function(date) {
						var ourweekday = date.getDay();
						var weekday_from = tokens[at][0];
						var weekday_to = is_range ? tokens[at+2][0] : weekday_from;

						// handle reversed range
						var inside = true;

						if (weekday_to < weekday_from) {
							var tmp = weekday_to;
							weekday_to = weekday_from - 1;
							weekday_from = tmp + 1;
							inside = false;
						}

						if (ourweekday < weekday_from || ourweekday > weekday_to) {
							return [!inside, dateAtNextWeekday(date, weekday_from)];
						} else {
							return [inside, dateAtNextWeekday(date, weekday_to + 1)];
						}
					}}(tokens, at, is_range));

					at += is_range ? 3 : 1;
				} else {
					throw 'Unexpected token in weekday range: "' + tokens[at] + '"';
				}

				if (at >= tokens.length)
					break;

				if (tokens[at][0] == ',')
					at++;
				else
					break;
			}

			return at;
		}

		// Numeric list parser (1,2,3-4,-1), used in weekday parser above
		function parseNumRange(tokens, at) {
			while (at < tokens.length) {
				if (tokens[at][1] == 'number' && tokens[at+1][0] == '-' && tokens[at+2][1] == 'number') {
					// Number range
					at += 3;
				} else if (tokens[at][0] == '-' && tokens[at+1][1] == 'number') {
					// Negative number
					at += 2
				} else if (tokens[at][1] == 'number') {
					// Single number
					at++;
				} else {
					throw 'Unexpected token in number range: "' + tokens[at][0] + '"';
				}

				if (at >= tokens.length)
					break;

				if (tokens[at][0] == ',')
					at++;
				else
					break;
			}

			return at;
		}

		// for given date, returns date moved to the specific day of week
		function dateAtNextWeekday(date, day) {
			var tmpdate = new Date(date);

			if (tmpdate.getDay() < day)
				tmpdate.setDate(tmpdate.getDate() + day - tmpdate.getDay());
			else if (tmpdate.getDay() > day)
				tmpdate.setDate(tmpdate.getDate() + 7 + day - tmpdate.getDay());

			tmpdate.setHours(0, 0, 0, 0);

			return tmpdate;
		}

		//======================================================================
		// Month range parser (Jan,Feb-Mar)
		//======================================================================
		function parseMonthDayRange(tokens, at) {
			return at + 1;
		}

		//======================================================================
		// Main selector traversing function
		//======================================================================
		function getState(date) {
			if (typeof date === 'undefined')
				date = new Date();

			var resultstate = false;
			var changedate;

			var dateseltypes = [ 'weekday' ];
			var date_matching_blocks = [];

			for (var block = 0; block < blocks.length; block++) {
				var has_date_selectors = false;
				var matching_date_block = true;

				// Try each date selector type
				for (var dateseltype = 0; dateseltype < dateseltypes.length; dateseltype++) {
					var dateselectors = blocks[block][dateseltypes[dateseltype]];
					if (dateselectors.length > 0)
						has_date_selectors = true;
					var has_matching_selector = false;
					for (var datesel = 0; datesel < dateselectors.length; datesel++) {
						var res = dateselectors[datesel](date);
						if (res[0])
							has_matching_selector = true;
						if (typeof changedate === 'undefined' || (typeof res[1] !== 'undefined' && res[1] < changedate))
							changedate = res[1];
					}

					if (has_date_selectors && !has_matching_selector) {
						matching_date_block = false;
						// XXX: do we need to break here, or we need to adjust time?
					}
				}

				if (matching_date_block) {
					if (has_date_selectors && blocks[block].meaning)
						date_matching_blocks = [];
					date_matching_blocks.push(block);
				}
			}

			for (var nblock = 0; nblock < date_matching_blocks.length; nblock++) {
				var block = date_matching_blocks[nblock];

				for (var timesel = 0; timesel < blocks[block].time.length; timesel++) {
					var res = blocks[block].time[timesel](date);
					if (res[0])
						resultstate = blocks[block].meaning;
					if (typeof changedate === 'undefined' || (typeof res[1] !== 'undefined' && res[1] < changedate))
						changedate = res[1];
				}
			}

			return [ resultstate, changedate ];
		}

		//======================================================================
		// Public interface
		//======================================================================

		// check whether facility is `open' on the given date (or now)
		this.isOpen = function(date) {
			return getState(date)[0];
		}

		// returns time of next status change
		this.nextChange = function(date) {
			var state = getState(date);
			var firststate = state;
			var prevstate;
			var maxdelta = 60*60*24*365*1000;
			do {
				prevstate = state;
				state = getState(prevstate[1]);

				// this indicates error in some selector generating code above
				if (state[1].getTime() <= prevstate[1].getTime())
					throw "Fatal: infinite loop in nextChange";

				// this may happen if the facility is always open/closed,
				// we may need a better way of checking for that
				if (state[1].getTime() - firststate[1].getTime() > maxdelta)
					return undefined;
			} while (state[0] === prevstate[0]);
			return prevstate[1];
		}

		// return array of open intervals between two dates
		this.openIntervals = function(from, to) {
			var res = [];

			var state = this.isOpen(from);
			var prevdate = from, curdate = this.nextChange(from);

			if (state)
				res.push([from]);

			for (; curdate.getTime() < to.getTime(); curdate = this.nextChange(curdate)) {
				state = !state;

				if (state)
					res.push([curdate]);
				else
					res[res.length - 1].push(curdate);

				prevdate = curdate;
			}

			if (state)
				res[res.length - 1].push(to);

			return res;
		}

		// return total number of milliseconds a facility is open without a given date range
		this.openDuration = function(from, to) {
			var res = 0;

			var state = this.isOpen(from);
			var prevdate = from, curdate = this.nextChange(prevdate);

			for (;curdate.getTime() < to.getTime(); curdate = this.nextChange(curdate)) {
				state = !state;

				if (!state)
					res += curdate.getTime() - prevdate.getTime();

				prevdate = curdate;
			}

			if (state)
				res += to.getTime() - prevdate.getTime()

			return res;
		}
	}
}));
<commit_msg>Fix for some always-open cases<commit_after>(function (root, factory) {
	if (typeof exports === 'object') {
		// For nodejs
		module.exports = factory();
	} else {
		// For browsers
		root.opening_hours = factory();
	}
}(this, function () {
	return function(value) {
		//======================================================================
		// Constants
		//======================================================================
		var months = { Jan: 0, Feb: 1, Mar: 2, Apr: 3, May: 4, Jun: 5, Jul: 6, Aug: 7, Sep: 8, Oct: 9, Nov: 10, Dec: 11 };
		var weekdays = { Su: 0, Mo: 1, Tu: 2, We: 3, Th: 4, Fr: 5, Sa: 6 };

		//======================================================================
		// Constructor - entry to parsing code
		//======================================================================
		// Terminology:
		//
		// Mo-Fr 10:00-11:00; Th 10:00-12:00
		// \_____block_____/  \____block___/
		//
		// Mo-Fr Jan 10:00-11:00
		// \__/  \_/ \_________/
		// selectors (left to right: weekday, month, time)
		//
		// Logic:
		// - Split blocks; foreach block:
		// - Tokenize
		// - Run toplevel (block) parser
		//   - Which calls subparser for specific selector types
		//     - Which produce selectors
		var blocks = value.split(/\s*;\s*/);

		for (var block = 0; block < blocks.length; block++) {
			var tokens = tokenize(blocks[block]);

			var selectors = {
				time: [],
				weekday: [],

				meaning: true,
			};

			parseGroup(tokens, 0, selectors);

			blocks[block] = selectors;
		}

		// Tokenization function: splits string into parts
		// output: array of pairs [content, type]
		function tokenize(value) {
			var tokens = new Array();

			while (value != '') {
				var tmp;
				if (tmp = value.match(/^(?:week|24\/7|off)/)) {
					// reserved word
					tokens.push([tmp[0], tmp[0]]);
					value = value.substr(tmp[0].length)
				} else if (tmp = value.match(/^(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)/)) {
					// month name
					tokens.push([months[tmp[0]], 'month']);
					value = value.substr(3);
				} else if (tmp = value.match(/^(?:Mo|Tu|We|Th|Fr|Sa|Su)/)) {
					// weekday name
					tokens.push([weekdays[tmp[0]], 'weekday']);
					value = value.substr(2);
				} else if (tmp = value.match(/^\d+/)) {
					// number
					tokens.push([tmp[0], 'number']);
					value = value.substr(tmp[0].length);
				} else if (value.match(/^\s/)) {
					// whitespace are fixed
					value = value.substr(1)
				} else {
					// other single-character tokens
					tokens.push([value[0], value[0]]);
					value = value.substr(1)
				}
			}

			return tokens;
		}

		// Function to check token array for specific pattern
		function matchTokens(tokens, at, match) {
			for (var i = 0; i < match.length; i++) {
				if (typeof tokens[at + i] === 'undefined')
					return false;
				if (tokens[at + i][1] !== match[i])
					return false;
			}

			return true;
		}

		//======================================================================
		// Top-level parser
		//======================================================================
		function parseGroup(tokens, at, selectors) {
			while (at < tokens.length) {
				if (tokens[at][1] == 'weekday') {
					at = parseWeekdayRange(tokens, at, selectors);
				} else if (tokens[at][1] == 'month' && tokens[at+1][1] == 'number') {
					at = parseMonthDayRange(tokens, at);
				} else if (tokens[at][1] == 'month') {
					at = parseMonthRange(tokens, at);
				} else if (tokens[at][0] == 'week') {
					at = parseWeekRange(tokens, at);
				} else if (tokens[at][1] == 'number') {
					at = parseTimeRange(tokens, at, selectors);
				} else if (tokens[at][0] == 'off') {
					selectors.meaning = false;
					at++;
				} else if (tokens[at][0] == '24/7') {
					selectors.time.push(function(date) { return [true]; });
					at++;
				} else {
					throw 'Unexpected token: "' + tokens[at] + '"';
				}
			}

			return tokens;
		}

		//======================================================================
		// Time range parser (10:00-12:00,14:00-16:00)
		//======================================================================
		function parseTimeRange(tokens, at, selectors) {
			var minutes_in_day = 24 * 60;

			while (at < tokens.length) {
				if (tokens[at][1] == 'number' && tokens[at+1][0] == ':' && tokens[at+2][1] == 'number' && tokens[at+3][0] == '-' && tokens[at+4][1] == 'number' && tokens[at+5][0] == ':' && tokens[at+6][1] == 'number') {
					// Time range
					selectors.time.push(function(tokens, at) { return function(date) {
						var ourminutes = date.getHours() * 60 + date.getMinutes();
						var minutes_from = tokens[at][0] * 60 + tokens[at+2][0] * 1;
						var minutes_to = tokens[at+4][0] * 60 + tokens[at+6][0] * 1;

						// normalize minutes into range
						// XXX: what if it's further than tomorrow?
						// XXX: this is incorrect, as it assumes the same day
						//      should cooperate with date selectors to select the next day
						if (minutes_to > minutes_in_day)
							minutes_to -= minutes_in_day;

						// handle reversed range
						var inside = true;

						if (minutes_to < minutes_from) {
							var tmp = minutes_to;
							minutes_to = minutes_from;
							minutes_from = tmp;
							inside = false;
						}

						if (ourminutes < minutes_from)
							return [!inside, dateAtNextDayMinutes(date, minutes_from)];
						else if (ourminutes < minutes_to)
							return [inside, dateAtNextDayMinutes(date, minutes_to)];
						else
							return [!inside, dateAtNextDayMinutes(date, minutes_from + minutes_in_day)];
					}}(tokens, at));

					at += 7;
				} else {
					throw 'Unexpected token in time range: "' + tokens[at][0] + '"';
				}

				if (at >= tokens.length)
					break;

				if (tokens[at][0] == ',')
					at++;
				else
					break;

			}

			return at;
		}

		// for given date, returns date moved to the start of specified day minute
		function dateAtNextDayMinutes(date, minutes) {
			var tmpdate = new Date(date);
			tmpdate.setHours(0, minutes, 0, 0);
			return tmpdate;
		}

		//======================================================================
		// Weekday range parser (Mo,We-Fr,Sa[1-2,-1])
		//======================================================================
		function parseWeekdayRange(tokens, at, selectors) {
			while (at < tokens.length) {
				if (tokens[at][1] == 'weekday' && tokens[at+1][0] == '[') {
					// Conditional weekday (Mo[3])
					at = parseNumRange(tokens, at+2);
					if (tokens[at][1] !== ']')
						throw '"]" expected';
					at++;
				} else if (tokens[at][1] == 'weekday') {
					// Single weekday (Mo) or weekday range (Mo-Fr)
					var is_range = tokens[at+1][0] == '-' && tokens[at+2][1] == 'weekday';

					selectors.weekday.push(function(tokens, at, is_range) { return function(date) {
						var ourweekday = date.getDay();
						var weekday_from = tokens[at][0];
						var weekday_to = is_range ? tokens[at+2][0] : weekday_from;

						// handle reversed range
						var inside = true;

						if (weekday_to < weekday_from) {
							var tmp = weekday_to;
							weekday_to = weekday_from - 1;
							weekday_from = tmp + 1;
							inside = false;
						}

						if (ourweekday < weekday_from || ourweekday > weekday_to) {
							return [!inside, dateAtNextWeekday(date, weekday_from)];
						} else {
							return [inside, dateAtNextWeekday(date, weekday_to + 1)];
						}
					}}(tokens, at, is_range));

					at += is_range ? 3 : 1;
				} else {
					throw 'Unexpected token in weekday range: "' + tokens[at] + '"';
				}

				if (at >= tokens.length)
					break;

				if (tokens[at][0] == ',')
					at++;
				else
					break;
			}

			return at;
		}

		// Numeric list parser (1,2,3-4,-1), used in weekday parser above
		function parseNumRange(tokens, at) {
			while (at < tokens.length) {
				if (tokens[at][1] == 'number' && tokens[at+1][0] == '-' && tokens[at+2][1] == 'number') {
					// Number range
					at += 3;
				} else if (tokens[at][0] == '-' && tokens[at+1][1] == 'number') {
					// Negative number
					at += 2
				} else if (tokens[at][1] == 'number') {
					// Single number
					at++;
				} else {
					throw 'Unexpected token in number range: "' + tokens[at][0] + '"';
				}

				if (at >= tokens.length)
					break;

				if (tokens[at][0] == ',')
					at++;
				else
					break;
			}

			return at;
		}

		// for given date, returns date moved to the specific day of week
		function dateAtNextWeekday(date, day) {
			var tmpdate = new Date(date);

			if (tmpdate.getDay() < day)
				tmpdate.setDate(tmpdate.getDate() + day - tmpdate.getDay());
			else if (tmpdate.getDay() > day)
				tmpdate.setDate(tmpdate.getDate() + 7 + day - tmpdate.getDay());

			tmpdate.setHours(0, 0, 0, 0);

			return tmpdate;
		}

		//======================================================================
		// Month range parser (Jan,Feb-Mar)
		//======================================================================
		function parseMonthDayRange(tokens, at) {
			return at + 1;
		}

		//======================================================================
		// Main selector traversing function
		//======================================================================
		function getState(date) {
			if (typeof date === 'undefined')
				date = new Date();

			var resultstate = false;
			var changedate;

			var dateseltypes = [ 'weekday' ];
			var date_matching_blocks = [];

			for (var block = 0; block < blocks.length; block++) {
				var has_date_selectors = false;
				var matching_date_block = true;

				// Try each date selector type
				for (var dateseltype = 0; dateseltype < dateseltypes.length; dateseltype++) {
					var dateselectors = blocks[block][dateseltypes[dateseltype]];
					if (dateselectors.length > 0)
						has_date_selectors = true;
					var has_matching_selector = false;
					for (var datesel = 0; datesel < dateselectors.length; datesel++) {
						var res = dateselectors[datesel](date);
						if (res[0])
							has_matching_selector = true;
						if (typeof changedate === 'undefined' || (typeof res[1] !== 'undefined' && res[1] < changedate))
							changedate = res[1];
					}

					if (has_date_selectors && !has_matching_selector) {
						matching_date_block = false;
						// XXX: do we need to break here, or we need to adjust time?
					}
				}

				if (matching_date_block) {
					if (has_date_selectors && blocks[block].meaning)
						date_matching_blocks = [];
					date_matching_blocks.push(block);
				}
			}

			for (var nblock = 0; nblock < date_matching_blocks.length; nblock++) {
				var block = date_matching_blocks[nblock];

				for (var timesel = 0; timesel < blocks[block].time.length; timesel++) {
					var res = blocks[block].time[timesel](date);
					if (res[0])
						resultstate = blocks[block].meaning;
					if (typeof changedate === 'undefined' || (typeof res[1] !== 'undefined' && res[1] < changedate))
						changedate = res[1];
				}
			}

			return [ resultstate, changedate ];
		}

		//======================================================================
		// Public interface
		//======================================================================

		// check whether facility is `open' on the given date (or now)
		this.isOpen = function(date) {
			return getState(date)[0];
		}

		// returns time of next status change
		this.nextChange = function(date) {
			var state = getState(date);
			var firststate = state;
			var prevstate;
			var maxdelta = 60*60*24*365*1000;
			do {
				prevstate = state;
				state = getState(prevstate[1]);

				// this indicates error in some selector generating code above
				if (state[1].getTime() <= prevstate[1].getTime())
					throw "Fatal: infinite loop in nextChange";

				// this may happen if the facility is always open/closed,
				// we may need a better way of checking for that
				if (state[1].getTime() - firststate[1].getTime() > maxdelta)
					return undefined;
			} while (state[0] === prevstate[0]);
			return prevstate[1];
		}

		// return array of open intervals between two dates
		this.openIntervals = function(from, to) {
			var res = [];

			var state = this.isOpen(from);
			var prevdate = from, curdate = this.nextChange(from);

			if (state)
				res.push([from]);

			for (; typeof curdate !== 'undefined' && curdate.getTime() < to.getTime(); curdate = this.nextChange(curdate)) {
				state = !state;

				if (state)
					res.push([curdate]);
				else
					res[res.length - 1].push(curdate);

				prevdate = curdate;
			}

			if (state)
				res[res.length - 1].push(to);

			return res;
		}

		// return total number of milliseconds a facility is open without a given date range
		this.openDuration = function(from, to) {
			var res = 0;

			var state = this.isOpen(from);
			var prevdate = from, curdate = this.nextChange(prevdate);

			for (; typeof curdate !== 'undefined' && curdate.getTime() < to.getTime(); curdate = this.nextChange(curdate)) {
				state = !state;

				if (!state)
					res += curdate.getTime() - prevdate.getTime();

				prevdate = curdate;
			}

			if (state)
				res += to.getTime() - prevdate.getTime()

			return res;
		}
	}
}));
<|endoftext|>


<commit_before>/*
 * Copyright 2010 ITA Software, Inc.
 *
 * 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.
 */

/******* GLOBALS ********/

// Base for data manipulation
// TODO: Find a way to remove global variable?
base = 0;

/******* FLOT HELPER FUNCTIONS *******/
$.plot.formatDate = function(d, fmt, monthNames) {
    var leftPad = function(n) {
        n = "" + n;
        return n.length == 1 ? "0" + n : n;
    };

    var r = [];
    var escape = false, padNext = false;
    var hours = d.getUTCHours();
    var form_data = localStorageGet('form_configurator');
    if  (form_data && form_data['localtime']) {
        hours = d.getHours();
    }
    var isAM = hours < 12;
    if (monthNames == null)
        monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];

    if (fmt.search(/%p|%P/) != -1) {
        if (hours > 12) {
            hours = hours - 12;
        } else if (hours == 0) {
            hours = 12;
        }
    }
    for (var i = 0; i < fmt.length; ++i) {
        var c = fmt.charAt(i);
            if (escape) {
                var form_data = localStorageGet('form_configurator');
                if  (form_data && form_data['localtime']) {
                    switch (c) {
                        case 'h': c = "" + hours; break;
                        case 'H': c = leftPad(hours); break;
                        case 'M': c = leftPad(d.getMinutes()); break;
                        case 'S': c = leftPad(d.getSeconds()); break;
                        case 'd': c = "" + d.getDate(); break;
                        case 'm': c = "" + (d.getMonth() + 1); break;
                        case 'y': c = "" + d.getFullYear(); break;
                        case 'b': c = "" + monthNames[d.getMonth()]; break;
                        case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break;
                        case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break;
                        case '0': c = ""; padNext = true; break;
                    }
                }

                else {
                    switch (c) {
                        case 'h': c = "" + hours; break;
                        case 'H': c = leftPad(hours); break;
                        case 'M': c = leftPad(d.getUTCMinutes()); break;
                        case 'S': c = leftPad(d.getUTCSeconds()); break;
                        case 'd': c = "" + d.getUTCDate(); break;
                        case 'm': c = "" + (d.getUTCMonth() + 1); break;
                        case 'y': c = "" + d.getUTCFullYear(); break;
                        case 'b': c = "" + monthNames[d.getUTCMonth()]; break;
                        case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break;
                        case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break;
                        case '0': c = ""; padNext = true; break;
                    }
                }
                if (c && padNext) {
                    c = leftPad(c);
                    padNext = false;
                }
                r.push(c);
                if (!padNext) {
                    escape = false;
                }
        }
        else {
            if (c == "%")
                escape = true;
            else
                r.push(c);
        }
    }
    return r.join("");
};
// Choose a base for graph axis
function chooseBase(max) {
    // Memoizes results!
    if (this.chooseBase.result != undefined &&
        this.chooseBase.result.max == max) {
        return result;
    }
    bases = ['', 'K', 'M', 'G', 'T'];
    for(interval = 0; interval < bases.length; interval++) {
        if(max / (Math.pow(base, interval)) <= base) {
            break;
        }
    }
    var result = new Array();
    result.interval = interval;
    result.bases = bases;
    result.max = max;
    return result;
}

// Generate ticks, passed as an option to Flot
function tickGenerator(range) {
    result = chooseBase(range.max);
    interval = result.interval

    final_base = Math.pow(base, interval);

    var noTicks = 0.3 * Math.sqrt($(".graph").height());

    var delta = ((range.max - range.min) / final_base) / noTicks,
        size, generator, unit, formatter, i, magn, norm;

    // Pretty rounding of base-10 numbers
    var dec = -Math.floor(Math.log(delta) / Math.LN10);

    magn = Math.pow(10, -dec);
    norm = delta / magn; // norm is between 1.0 and 10.0

    if (norm < 1.5)
        size = 1;
    else if (norm < 3) {
        size = 2;
        // special case for 2.5, requires an extra decimal
        if (norm > 2.25) {
            size = 2.5;
            ++dec;
        }
    }
    else if (norm < 7.5)
        size = 5;
    else
        size = 10;

    size *= magn;
    size *= final_base;

    var ticks = [];
    x = 0;
    for(tick = range.min; tick < range.max+size; tick+=size) {
        ticks[x++] = tick;
    }
    return ticks;
}

// Format ticks for displayed, passed as option to Flot
function tickFormatter(val, axis) {
    result = chooseBase(axis.max);
    interval = result.interval;
    bases = result.bases;

    final_base = (Math.pow(base, interval));

    // flot computes tickDecimals before dividing by final_base, so we update
    // tickDecimals accordingly
    if (final_base != 1) {
        tickDecimals = axis.tickDecimals;
        while (tickDecimals <= 3 &&
			(val / final_base).toFixed(tickDecimals) != val / final_base)
            tickDecimals++;

		if (tickDecimals <= 3)
			axis.tickDecimals = tickDecimals;
    }

    return (val / final_base).toFixed(axis.tickDecimals) + bases[interval];
}

// Format a label, passed to Flot
function labelFormatter(label, series) {
    return label.replace(/_/g, ' ');
}

/******* GRAPH GENERATION/MANIPULTION *******/

// Takes the raw data and sets up required Flot formatting options
function formatGraph(element, data) {
    base = data.base;

    data.options.yaxis.ticks = tickGenerator;
    data.options.yaxis.tickFormatter = tickFormatter;

    // TODO: Cleanup legend and axis label creation
    data.options.legend = {}
    data.options.legend.container = $(element).next('.legend');
    data.options.legend.labelFormatter = labelFormatter;
    return data;
}

// Creates a graph
function createGraph(element, path, callback, zoom) {
    // If the graph isn't busy
    if(!$(element).data('busy')) {
        $(element).data('busy', true);
        $(element).append('

');
        $(element).remove('.empty');
        $.ajax({
            url: '/railroad/parserrd/' + path,
            dataType: 'json',
            success: function(data) {
                // If we are zooming and there's no data, just bail with an
                // error
                if(zoom && data.empty) {
                    plot = $(element).data('plot');
                    plot.clearSelection();
                    $(element).append('
no data to ' +
                                      'zoom
');
                    // Nice fadeOut won't let us remove the element, so use a
                    // callback
                    $(element).find('.error')
                              .delay(500)
                              .fadeOut(500,
                                       function() {
                                           $(this).remove();
                                       });
                } else {
                    data = formatGraph(element, data);
                    $(element).data('plot',
                                    $.plot($(element),
                                    data.data,
                                    data.options));
                    $(element).data('start',
                                    data.start);
                    $(element).data('end',
                                    data.end);
                    if(data.options.yaxis.label) {
                        // if there isn't already a ylabel
                        if ($(element).siblings('.ylabel').length == 0) {
                            $(element).before('
' +
                                              data.options.yaxis.label + '
');
                        }
                    }
                    if(data.empty == true) {
                        $(element).append('
no data
');
                    }

                    update = $(element).closest('.graph_container') .find('.update')
                                 .html('updated: ' + data.current_time);

                    // get the graphs collapsed/expanded as they should be.
                    $('#expansion_by_type').children().trigger('change');

                    if(callback != null) {
                        callback(data);
                    }
                }
                $(element).find('.throbber').remove();
            },
            // If there's an error, toss out a warning about it
            error: function(request, status, error) {
                plot = $(element).data('plot');
                if(zoom) {
                    plot.clearSelection();
                }
                $(element).find('.throbber').remove();
                $(element).append('
error
');
                if(zoom) {
                    // Nice fadeOut won't let us remove the element, so use a
                    // callback
                    $(element).find('.error')
                              .delay(500)
                              .fadeOut(500,
                                       function() {
                                           $(this).remove();
                                       });
                }
            }
        });
    // Release the graph
    $(element).data('busy', null);
    }
}

// Parse and setup graphs on the page
function parseGraphs(index, element) {

    // Don't set up graphs already set up
    $(element).addClass('setup');

    // Store the graph data for usage later
    path = $(element).find('a').attr('href');
    splitPath = path.split('/');
    $(element).data('host', splitPath[0]);
    $(element).data('service', splitPath[1]);
    $(element).data('start', splitPath[2]);
    $(element).data('end', splitPath[3]);
    $(element).data('res', splitPath[4]);

    createGraph(element, path, sortGraphs);

    // Allow for zooming
    $(element).bind('plotselected', function (event, ranges) {
        // The graph isn't busy anymore, allow updates
        $(element).data('busy', null);

        // If we are supposed to sync the graphs, loop over all graphs
        if($('#sync').attr('checked')) {
            graphs = $('.graph');
            // Allow us to zoom even when it makes no sense if we are
            // synced
            zoom = false;
        // Otherwise only loop over the graph associated with this button
        } else {
            graphs = $(element);
            zoom = true;
        }

        graphs.each(function(index, element) {

            serviceData = $(element).data();

            path = [serviceData.host,
                    serviceData.service,
                    parseInt(ranges.xaxis.from / 1000),
                    parseInt(ranges.xaxis.to / 1000),
                    serviceData.res].join('/');

            createGraph(element,
                        path,
                        function() {
                            $(element).removeClass('ajax');
                            zoomButton = $(element)
                                            .closest('.graph_container')
                                            .find('.zoom');
                            selected = $(element)
                                            .closest('.graph_container')
                                            .find('.selected');
                            selected.removeClass('selected');
                            zoomButton.css('visibility', 'visible');
                            zoomButton.addClass('selected');
                        },
                        zoom);
        });
    });
    $(element).bind('plotselecting', function() {
        // If we are selecting, mark the graph as busy so no AJAX fires
        $(element).data('busy', true);
    });

}

// Function to automatically update any graphs which are set to ajax load
function autoFetchData() {
    $('.graph.ajax').each(function(index, element) {
        serviceData = $(element).data();
        time = new Date();
        end = parseInt(time.getTime() / 1000);
        start = parseInt(end - 60 * 60 * 24);

        path = [serviceData.host,
                serviceData.service,
                start,
                end,
                serviceData.res].join('/');

        createGraph(element, path);
    });
    setTimeout(autoFetchData, 60 * 1000);
}

// Sort the graphs.
function reverse(func) {
    return function(a,b) {
        return func(b,a);
    }
}
function makeComparer(val) {
    return function(a,b) {
        return val(a) > val(b);
    }
}
var sorts = {
    'service': makeComparer(function(e) {
            return $(e).find('td.status_text h2').last().text();
        }),
    'host': makeComparer(function(e) {
            return $(e).find('td.status_text h2').first().text();
        }),
    'status': reverse(makeComparer(function(e) {
            e_td = $(e).find('td.status_text');
            if (e_td.hasClass('state_ok')) {
                return 0;
            } else if (e_td.hasClass('state_warning')) {
                return 1;
            } else if (e_td.hasClass('state_critical')) {
                return 2;
            } else {
                return 3;
            }
        })),
    'value': makeComparer(function(e) {
            var plot = $(e).find('.graph').first().data('plot');
            if (!plot) {
                return NaN;
            }
            series = plot.getData()[0].data;
            latest = parseInt(series[series.length-1][1]);
            return latest;
        })
}
function sortGraphs() {
    console.log('sorting... #of trs: {0}'.format($('tr.service_row').length));
    var name = $('#sortby').val();
    var sorter = sorts[name];
    if ($('#reverse_sort').prop('checked')) {
        sorter = reverse(sorter);
    }
    $('tr.service_row').sort(sorter).appendTo('#graphs');
}

/******* Local Storage Hooks *******/
function updateDebug() {
    if (localStorageGet('debug')) {
        $('#debug input').prop('checked', true);
        $('#debug ul li').remove();
        for (var prop in localStorage) {
            var desc = localStorage[prop];
            $('#debug ul').append('
({0}) {1}: {2}
'.format(typeof(desc), prop, desc));
        }
        $('#debug ul').append('
Reset localStorage
');
    } else {
        $('#debug input').prop('checked', false);
        $('#debug ul li').remove();
    }
}

function localStorageSupport() {
    try {
        return 'localStorage' in window && window['localStorage'] !== null;
    } catch (e) {
        return false;
    }
}
function localStorageSet(key, value) {
    if (localStorageSupport()) {
        var json = JSON.stringify(value)
        localStorage[key] = json;
        updateDebug();
        return true;
    }
    // Should we try other methods of storing data?
    return false;
}
function localStorageGet(key) {
    if (localStorageSupport()) {
        var ob;
        try {
            ob = JSON.parse(localStorage[key]);
        } catch(e) {
            ob = localStorage[key];
        }
        return ob;
    }
    // Should we try other methods of storing data?
    return null;
}
function localStorageClear() {
    if (localStorageSupport()) {
        localStorage.clear();
        updateDebug();
        return true;
    }
    return false;
}

/******* Misc helper functions *******/
// Give strings a format function.
// Use it like this
//    "Hello {0}, how are you this find {1}?".format(user_name, time_of_day);
//    Returns "Hello Mike, how are you this fine morning?"
String.prototype.format = function() {
    var formatted = this;
    for (var i = 0; i < arguments.length; i++) {
        var regexp = new RegExp('\\{'+i+'\\}', 'gi');
        formatted = formatted.replace(regexp, arguments[i]);
    }
    return formatted;
};

/******* DOM HOOK SETUP *******/

// Execute setup code when page loads
$(document).ready(function() {

    /**** GRAPH SETUP ****/

    // Bind the graph time range selection buttons
    $('.options ul li').live('click', function() {
        clicked = $(this);
        // If we are supposed to sync the graphs, loop over all graphs
        if($('#sync').attr('checked')) {
            graphs = $('.graph');
        // Otherwise only loop over the graph associated with this button
        } else {
            graphs = $(this).closest('.graph_container')
                             .find('.graph');
        }
        graphs.each(function(index, graph) {
            button = $(graph).closest('.graph_container')
                             .find('.year');
            time = new Date();
            end = parseInt(time.getTime() / 1000);

            // Depending on which button is hit, change the behavior
            // TODO: Look at possibilities at cleaning this up

            if(clicked.hasClass('zoom')) {
                return;
            }

            $(graph).closest('.graph_container')
                    .find('.zoom')
                    .css('visibility', 'hidden');

            if(clicked.hasClass('reset')) {
                $(graph).addClass('ajax');
            } else {
                $(graph).removeClass('ajax');
            }

            if(clicked.hasClass('day') || clicked.hasClass('reset')) {
                start = parseInt(end - 60 * 60 * 24);
            } else if(clicked.hasClass('week')) {
                start = parseInt(end - 60 * 60 * 24 * 7);
            } else if(clicked.hasClass('month')) {
                start = parseInt(end - 60 * 60 * 24 * 30);
            } else if(clicked.hasClass('year')) {
                start = parseInt(end - 60 * 60 * 24 * 365);
            }

            serviceData = $(graph).data();

            path = [serviceData.host,
                    serviceData.service,
                    start,
                    end,
                    serviceData.res].join('/');

            createGraph(graph,
                        path,
                        function() {
                            $(graph).closest('.graph_container')
                                    .find('.options .selected')
                                    .removeClass('selected');
                            // This is pretty horrible, perhaps there is a
                            // better way
                            if(clicked.hasClass('reset')) {
                                buttonClass = '.reset';
                            } else if(clicked.hasClass('day')) {
                                buttonClass = '.day';
                            } else if(clicked.hasClass('week')) {
                                buttonClass = '.week';
                            } else if(clicked.hasClass('month')) {
                                buttonClass = '.month';
                            } else if(clicked.hasClass('year')) {
                                buttonClass = '.year';
                            } else if(clicked.hasClass('zoom')) {
                                buttonClass = '.zoom';
                            }
                            $(graph).closest('.graph_container')
                                    .find(buttonClass)
                                    .addClass('selected');
                        });
        });
    });

    // Initialize the data for any graphs already on the page
    $(".graph").each(parseGraphs);

    /**** CONFIGURATOR SETUP ****/

    $('#debug_check').prop('checked', localStorageGet('debug'));
    updateDebug();

    $('#debug_check').change(function () {
        localStorageSet('debug', $('#debug_check').prop('checked'));
        updateDebug();
    });

    $('#debug a').live('click', function() {
        localStorageClear();
    });

    /*** Persistent form settings ***/
    // Anything in #configurator with a class of "... persist ..." will get persistence.
    $('#configurator').change(function() {
        var store = {};
        var value = null;
        $(this).find('.persist').each(function() {
            if ($(this).is('input')) {
                if ($(this).attr('type') == 'checkbox') {
                    value = $(this).prop('checked');
                } else if ($(this).attr('type') == 'radio') {
                    value = $(this).prop('checked');
                }
            } else if ($(this).is('select')) {
                value = $(this).val();
            }

            if (value != null) {
                store[$(this).attr('id')] = value;
            }
        });
        localStorageSet('form_configurator', store);
    });

    // Restore persisted objects
    var form_store = localStorageGet('form_configurator');
    for (key in form_store) {
        element = $('#configurator').find('#' + key);
        if ($(element).is('input')) {
            if ($(element).attr('type') == 'checkbox') {
                $(element).prop('checked', form_store[key]);
            } else if ($(element).attr('type') == 'radio') {
                $(element).prop('checked', form_store[key]);
            }
        } else if ($(element).is('select')) {
            $(element).val(form_store[key]);
        }
    }

    // Autocomplete anything with class = "... autocomplete ..."
    $('.autocomplete').each(function () {
        $(this).autocomplete ( { source : "/railroad/ajax/autocomplete/" + $(this).attr('name' ), minLength : 1, autoFocus: true})
    });

    $('#cleargraphs').click(function () {
        $('.service_row').remove();
        $('#configurator').data('changed', true);
    });

    $('#clearform').bind('click', function () {
        $('#host').val("");
        $('#group').val("");
        $('#service').val("");
    });

    // Handle configurator form submissions
    $('#configurator').submit(function() {
        $(this).append('

');
        // Enable the fields again so they can be submitted
        $('[id^=type]').attr('disabled', null);
        $('[id^=value]').attr('disabled', null);

        fields = $('#configurator').formSerialize();
        $.ajax({
            data: fields,
            dataType: 'html',
            url: $('#configurator').attr('action'),
            success: function(data, textStatus, XMLHttpRequest) {
                //reset_fields();
                $('#clearform').trigger('click');
                // Add the new graph and setup the new graphs
                $('#graphs').append(data);
                $('.graph:not(.setup)').each(parseGraphs);
                $('#configurator').find('.throbber').remove();
                sortGraphs();
            },
            error: function(XMLHttpRequest, textStatus, errorThrown) {
                // TODO: Indicate error somehow, probably
                $('#configurator').find('.throbber').remove();
            }
        });
        $('#configurator').data('changed', true);
        // Prevent normal form submission
        return false;
    });

    // **********  Functions to manipulate rows **************
    var animate_time = 250;
    function collapse_row(row) {
        // Hide the graph and status text
        $(row).children('.graph_container').children().hide(animate_time);
        $(row).children('.status_text').children('p').hide(animate_time);
        $(row).children('.status_text').children('h2').css({'display': 'inline'});

        // change the button to expand
        $(row).children('.controls').children('div').removeClass('collapse_row');
        $(row).children('.controls').children('div').addClass('expand_row');
    }
    function expand_row(row) {
        // Hide the graph and status text
        $(row).children('.graph_container').children().show(animate_time);
        $(row).children('.status_text').children('p').show(animate_time);
        $(row).children('.status_text').children('h2').css({'display': 'block'});

        // change the button to expand
        $(row).children('.controls').children('div').removeClass('expand_row');
        $(row).children('.controls').children('div').addClass('collapse_row');
    }

    $('#expandall').click(function() {
        $('tr.service_row').each(function(index, row) {
            expand_row(row);
        });
    });
    $('#collapseall').click(function() {
        $('tr.service_row').each(function(index, row) {
            collapse_row(row);
        });
    });

    $('.collapse_row').live('click', function() {
        collapse_row($(this).parents().parents().first());
    });
    $('.expand_row').live('click', function() {
        expand_row($(this).parents().parents().first());
    });
    $('.remove_row').live('click', function() {
        var tr = $(this).parents().parents().first();
        tr.hide(animate_time, function() {
            $(tr).remove();
        });
    });

    // *************************** Auto collapse ***************************
    // set up events on each checkbox to collapse/expand the service_rows to
    // match the current state of the checkbox.
    $('#expansion_by_type').children().bind('change', function() {
        var checkbox = this;
        $('.service_row').each(function(index, element) {
            if ($(element).find('*').hasClass($(checkbox).attr('id'))) {
                if ($(checkbox).prop('checked')) {
                    expand_row(element);
                } else {
                    collapse_row(element);
                }
            }
        });
    });

    // Handle configurator link generation
    $('#static').click(function() {
        function paint_link(data) {
            $('#link').html('Link: <input type="text" name="link"' +
                            ' readonly value="' + data + '" size="25" />');
            $('#link input').focus()
                            .select();
        }

        // If there are any service rows, we have a custom link to generate
        if($('.service_row').size()) {
            services = new Array();
            $('.service_row').each(function(index, element) {
                temp = $(element).find('.service_data').attr('href').split('/');
                host = temp[0];
                service = temp[1];
                start = $(element).find('.graph').data('start');
                end = $(element).find('.graph').data('end');
                services[index] = [host, service, start, end];
            });
            data = {services: services};
            $.ajax({
                data: data,
                dataType: 'json',
                type: 'POST',
                url: '/railroad/configurator/generatelink',
                success: function(data) {
                    paint_link(data);
                },
                error: function(XMLHttpRequest, textStatus, errorThrown) {
                    // TODO: Handle error?
                }
            });
        // If not, give a link to the page
        } else {
             paint_link(window.location);
        }
    });

    // Handle configurator link generation
    $('#live').click(function() {
        function paint_link(data) {
            $('#link').html('Link: <input type="text" name="link"' +
                            ' readonly value="' + data + '" size="25" />');
            $('#link input').focus()
                            .select();
        }

        // If anything on the page has changed, give them a link to that exact
        // zoom, configuration, etc
        if($('#configurator').data('changed')) {
            services = new Array();
            $('.service_row').each(function(index, element) {
                temp = $(element).find('.service_data').attr('href').split('/');
                host = temp[0];
                service = temp[1];
                if($(element).find('.graph').data('start')) {
                    start = -1;
                } else {
                    start = null;
                }
                if($(element).find('.graph').data('end')) {
                    end = -1;
                } else {
                    end = null;
                }

                services[index] = [host, service, start, end];
            });
            data = {services: services};
            $.ajax({
                data: data,
                dataType: 'json',
                type: 'POST',
                url: '/railroad/configurator/generatelink',
                success: function(data) {
                    paint_link(data);
                },
                error: function(XMLHttpRequest, textStatus, errorThrown) {
                    // TODO: Handle error?
                }
            });
        // If not, give them a generic link to this page
        } else {
            paint_link(window.location);
        }
    });

    // Automatically focus the link when someone clicks
    $('#link input').live('click', function() {
        $(this).focus()
               .select();
    });

    // Automatically delete the link if we defocus
    $('#link input').live('blur', function() {
        $('#link').empty();
    });

    // Start the AJAX graph refreshes if enableautofetch is true
    if (localStorageGet('form_configurator') || $('#enableautofetch').prop('checked')) {
        //Separate ifs in case form_configurator doesn't exist, null['autofetch'] won't error
        if(localStorageGet('form_configurator')['enableautofetch'] || $('#enableautofetch').prop('checked')) {
            setTimeout(autoFetchData, 60 * 1000);
        }
    }

    /******* Hint System *******/
    $('.hint').append('');

    $('.hint .hide_hint').bind('click',
        function() {
            var hint_id = $(this).parent().attr('id');
            var hints_hidden = localStorageGet('hints_hidden');
            if (hints_hidden == null) {
                hints_hidden = {};
            }
            hints_hidden[hint_id] = true;
            localStorageSet('hints_hidden', hints_hidden);
            $(this).parent().remove();
        });

    var hints_hidden = localStorageGet('hints_hidden');
    if (hints_hidden == null) {
        hints_hidden = {};
    }
    $('.hint').each(function() {
        if (! hints_hidden[$(this).attr('id')]) {
            $(this).css('display', 'block');
        }
    });

    /******** Sorting *********/
    $('#sortby').bind('change', sortGraphs);
    $('#reverse_sort').bind('change', sortGraphs);
});
<commit_msg>rr.js is no longer needed<commit_after><|endoftext|>


<commit_before>import {Tree} from '../base';
import {Empty, Deep} from '.';
import {_EMPTY, cache, Split} from '../../0-core';
import {One} from '../../1-digit';

export function Single(M, element) {
	this.M = M;
	this.element = element;
	this.v = M.measure(element);
}

Single.prototype = new Tree();

Single.prototype.measure = function () {
	return this.v;
};

Single.prototype.empty = function () {
	return false;
};

Single.prototype.head = function () {
	return this.element;
};

Single.prototype.last = function () {
	return this.element;
};

Single.prototype.tail = function () {
	return new Empty(this.M);
};

Single.prototype.init = function () {
	return new Empty(this.M);
};

Single.prototype.cons = function (value) {
	return new Deep(
		this.M,
		new One(value),
		new Empty(cache(this.M)),
		new One(this.element)
	);
};

Single.prototype.push = function (value) {
	return new Deep(
		this.M,
		new One(this.element),
		new Empty(cache(this.M)),
		new One(value)
	);
};

Single.prototype.concat = function (other) {
	return other.cons(this.element);
};

Single.prototype[Symbol.iterator] = function* () {
	yield this.element;
};

/**
 * It is assumed that p(|this|) is true.
 */
Single.prototype.splitTree = function (p, i) {
	return new Split(new Empty(this.M), this.element, new Empty(this.M));
};

Single.prototype.split = function (p) {
	return p(this.measure())
		? [new Empty(this.M), this]
		: [this, new Empty(this.M)];
};
<commit_msg>:recycle: refactor(Single): element -> value, this.element -> this.a<commit_after>import {Tree} from '../base';
import {Empty, Deep} from '.';
import {_EMPTY, cache, Split} from '../../0-core';
import {One} from '../../1-digit';

export function Single(M, value) {
	this.M = M;
	this.a = value;
	this.v = M.measure(value);
}

Single.prototype = new Tree();

Single.prototype.measure = function () {
	return this.v;
};

Single.prototype.empty = function () {
	return false;
};

Single.prototype.head = function () {
	return this.a;
};

Single.prototype.last = function () {
	return this.a;
};

Single.prototype.tail = function () {
	return new Empty(this.M);
};

Single.prototype.init = function () {
	return new Empty(this.M);
};

Single.prototype.cons = function (value) {
	return new Deep(
		this.M,
		new One(value),
		new Empty(cache(this.M)),
		new One(this.a)
	);
};

Single.prototype.push = function (value) {
	return new Deep(
		this.M,
		new One(this.a),
		new Empty(cache(this.M)),
		new One(value)
	);
};

Single.prototype.concat = function (other) {
	return other.cons(this.a);
};

Single.prototype[Symbol.iterator] = function* () {
	yield this.a;
};

/**
 * It is assumed that p(|this|) is true.
 */
Single.prototype.splitTree = function (p, i) {
	return new Split(new Empty(this.M), this.a, new Empty(this.M));
};

Single.prototype.split = function (p) {
	return p(this.measure())
		? [new Empty(this.M), this]
		: [this, new Empty(this.M)];
};
<|endoftext|>


<commit_before>import environment from 'platform/utilities/environment';
import recordEvent from 'platform/monitoring/record-event';
import localStorage from 'platform/utilities/storage/localStorage';
import { teardownProfileSession } from 'platform/user/profile/utilities';
import { updateLoggedInStatus } from 'platform/user/authentication/actions';
import {
  AUTH_EVENTS,
  EXTERNAL_APPS,
  GA,
  SIGNUP_TYPES,
} from 'platform/user/authentication/constants';
import { externalApplicationsConfig } from 'platform/user/authentication/usip-config';
import {
  ALL_STATE_AND_VERIFIERS,
  API_SIGN_IN_SERVICE_URL,
  CLIENT_IDS,
  COOKIES,
  OAUTH_ALLOWED_PARAMS,
  OAUTH_ENDPOINTS,
  OAUTH_KEYS,
} from './constants';
import * as oauthCrypto from './crypto';

export async function pkceChallengeFromVerifier(v) {
  if (!v || !v.length) return null;
  const hashed = await oauthCrypto.sha256(v);
  return { codeChallenge: oauthCrypto.base64UrlEncode(hashed) };
}

export const saveStateAndVerifier = type => {
  const storage = localStorage;

  // Create and store a random "state" value
  const state = oauthCrypto.generateRandomString(28);

  // Create and store a new PKCE code_verifier (the plaintext random secret)
  const codeVerifier = oauthCrypto.generateRandomString(64);

  // Sign up/Create account
  if (Object.values(SIGNUP_TYPES).includes(type)) {
    storage.setItem(`${type}_state`, state);
    storage.setItem(`${type}_code_verifier`, codeVerifier);
  } else {
    // Sign in
    storage.setItem(OAUTH_KEYS.STATE, state);
    storage.setItem(OAUTH_KEYS.CODE_VERIFIER, codeVerifier);
  }

  return { state, codeVerifier };
};

export const removeStateAndVerifier = () => {
  const storage = localStorage;

  Object.keys(storage)
    .filter(key => ALL_STATE_AND_VERIFIERS.includes(key))
    .forEach(key => {
      storage.removeItem(key);
    });
};

export const updateStateAndVerifier = csp => {
  const storage = localStorage;

  storage.setItem(OAUTH_KEYS.STATE, storage.getItem(`${csp}_signup_state`));
  storage.setItem(
    OAUTH_KEYS.CODE_VERIFIER,
    storage.getItem(`${csp}_signup_code_verifier`),
  );

  const signupTypesMap = [
    `logingov_signup_state`,
    `logingov_signup_code_verifier`,
    `idme_signup_state`,
    `idme_signup_code_verifier`,
  ];

  Object.keys(storage)
    .filter(key => signupTypesMap.includes(key))
    .forEach(key => {
      storage.removeItem(key);
    });
};

/**
 *
 * @param {String} type
 */
export async function createOAuthRequest({
  application = '',
  clientId,
  config,
  passedQueryParams = {},
  passedOptions = {},
  type = '',
  acr,
}) {
  const isDefaultOAuth = !application || clientId === CLIENT_IDS.WEB;
  const isMobileOAuth =
    [EXTERNAL_APPS.VA_FLAGSHIP_MOBILE, EXTERNAL_APPS.VA_OCC_MOBILE].includes(
      application,
    ) || clientId === CLIENT_IDS.MOBILE;
  const { oAuthOptions } =
    config ??
    (externalApplicationsConfig[application] ||
      externalApplicationsConfig.default);
  const useType = passedOptions.isSignup
    ? type.slice(0, type.indexOf('_'))
    : type;

  /*
    Web - Generate state & codeVerifier if default oAuth
  */
  const { state, codeVerifier } = isDefaultOAuth && saveStateAndVerifier(type);

  /*
    Mobile - Use passed code_challenge
    Web - Generate code_challenge
  */
  const { codeChallenge } =
    isMobileOAuth && passedQueryParams
      ? passedQueryParams
      : await pkceChallengeFromVerifier(codeVerifier);

  // Build the authorization URL query params from config
  const oAuthParams = {
    [OAUTH_KEYS.CLIENT_ID]: encodeURIComponent(
      clientId || oAuthOptions.clientId,
    ),
    [OAUTH_KEYS.ACR]:
      acr ||
      (passedOptions.isSignup
        ? oAuthOptions.acrSignup[type]
        : oAuthOptions.acr[type]),
    [OAUTH_KEYS.RESPONSE_TYPE]: OAUTH_ALLOWED_PARAMS.CODE,
    ...(isDefaultOAuth && { [OAUTH_KEYS.STATE]: state }),
    ...(passedQueryParams.gaClientId && {
      [GA.queryParams.sis]: passedQueryParams.gaClientId,
    }),
    [OAUTH_KEYS.CODE_CHALLENGE]: codeChallenge,
    [OAUTH_KEYS.CODE_CHALLENGE_METHOD]: OAUTH_ALLOWED_PARAMS.S256,
  };

  const url = new URL(API_SIGN_IN_SERVICE_URL({ type: useType }));

  Object.keys(oAuthParams).forEach(param =>
    url.searchParams.append(param, oAuthParams[param]),
  );

  recordEvent({ event: `login-attempted-${type}-oauth-${clientId}` });

  return url.toString();
}

export const getCV = () => {
  const storage = localStorage;
  const codeVerifier = storage.getItem(OAUTH_KEYS.CODE_VERIFIER);
  return { codeVerifier };
};

export function buildTokenRequest({
  code,
  redirectUri = `${environment.BASE_URL}`,
} = {}) {
  const { codeVerifier } = getCV();

  if (!code || !codeVerifier) return null;

  // Build the authorization URL
  const oAuthParams = {
    [OAUTH_KEYS.GRANT_TYPE]: OAUTH_ALLOWED_PARAMS.AUTH_CODE,
    [OAUTH_KEYS.CLIENT_ID]: encodeURIComponent(CLIENT_IDS.WEB),
    [OAUTH_KEYS.REDIRECT_URI]: encodeURIComponent(redirectUri),
    [OAUTH_KEYS.CODE]: code,
    [OAUTH_KEYS.CODE_VERIFIER]: codeVerifier,
  };

  const url = new URL(
    API_SIGN_IN_SERVICE_URL({ endpoint: OAUTH_ENDPOINTS.TOKEN }),
  );

  Object.keys(oAuthParams).forEach(param =>
    url.searchParams.append(param, oAuthParams[param]),
  );

  return url;
}

export const requestToken = async ({ code, redirectUri, csp }) => {
  const url = buildTokenRequest({
    code,
    redirectUri,
  });

  if (!url) return null;

  const response = await fetch(url.toString(), {
    method: 'POST',
    credentials: 'include',
  });

  recordEvent({
    event: response.ok
      ? `login-success-${csp}-oauth-tokenexchange`
      : `login-failure-${csp}-oauth-tokenexchange`,
  });

  if (response.ok) {
    removeStateAndVerifier();
  }

  return response;
};

export const refresh = async ({ type }) => {
  const url = new URL(
    API_SIGN_IN_SERVICE_URL({ endpoint: OAUTH_ENDPOINTS.REFRESH, type }),
  );

  return fetch(url.href, {
    method: 'POST',
    credentials: 'include',
  });
};

export const infoTokenExists = () => {
  return document.cookie.includes(COOKIES.INFO_TOKEN);
};

export const formatInfoCookie = cookieStringRaw => {
  const decoded = cookieStringRaw.includes('%')
    ? decodeURIComponent(cookieStringRaw)
    : cookieStringRaw;
  return decoded.split(',+:').reduce((obj, cookieString) => {
    const [key, value] = cookieString.replace(/{:|}/g, '').split('=>');
    const formattedValue = value.replaceAll('++00:00', '').replaceAll('+', ' ');
    return { ...obj, [key]: new Date(formattedValue) };
  }, {});
};

export const getInfoToken = () => {
  if (!infoTokenExists()) return null;

  return document.cookie
    .split(';')
    .map(cookie => cookie.split('='))
    .reduce((_, [cookieKey, cookieValue]) => ({
      ..._,
      ...(cookieKey.includes(COOKIES.INFO_TOKEN) && {
        ...formatInfoCookie(decodeURIComponent(cookieValue)),
      }),
    }));
};

export const removeInfoToken = () => {
  if (!infoTokenExists()) return null;

  const updatedCookie = document.cookie.split(';').reduce((_, cookie) => {
    let tempCookieString = _;
    if (!cookie.includes(COOKIES.INFO_TOKEN)) {
      tempCookieString += `${cookie};`.trim();
    }
    return tempCookieString;
  }, '');
  document.cookie = updatedCookie;
  return undefined;
};

export const checkOrSetSessionExpiration = response => {
  const sessionExpirationSAML = response.headers.get('X-Session-Expiration');

  if (sessionExpirationSAML) {
    localStorage.setItem('sessionExpiration', sessionExpirationSAML);
    return true;
  }

  if (infoTokenExists()) {
    const {
      access_token_expiration: atExpiration,
      refresh_token_expiration: sessionExpirationOAuth,
    } = getInfoToken();

    localStorage.setItem('atExpires', atExpiration);
    localStorage.setItem('sessionExpiration', sessionExpirationOAuth);
    return true;
  }

  return false;
};

export const logoutUrlSiS = () => {
  return new URL(API_SIGN_IN_SERVICE_URL({ endpoint: 'logout' })).href;
};

export const logoutEvent = async (signInServiceName, wait = {}) => {
  const { duration = 500, shouldWait } = wait;
  const sleep = time => {
    return new Promise(resolve => setTimeout(resolve, time));
  };
  recordEvent({ event: `${AUTH_EVENTS.OAUTH_LOGOUT}-${signInServiceName}` });

  updateLoggedInStatus(false);

  if (shouldWait) {
    await sleep(duration);
    teardownProfileSession();
  } else {
    teardownProfileSession();
  }
};
<commit_msg>fixes the format of infoToken parsing in formatInfoCookie (#22429)<commit_after>import environment from 'platform/utilities/environment';
import recordEvent from 'platform/monitoring/record-event';
import localStorage from 'platform/utilities/storage/localStorage';
import { teardownProfileSession } from 'platform/user/profile/utilities';
import { updateLoggedInStatus } from 'platform/user/authentication/actions';
import {
  AUTH_EVENTS,
  EXTERNAL_APPS,
  GA,
  SIGNUP_TYPES,
} from 'platform/user/authentication/constants';
import { externalApplicationsConfig } from 'platform/user/authentication/usip-config';
import {
  ALL_STATE_AND_VERIFIERS,
  API_SIGN_IN_SERVICE_URL,
  CLIENT_IDS,
  COOKIES,
  OAUTH_ALLOWED_PARAMS,
  OAUTH_ENDPOINTS,
  OAUTH_KEYS,
} from './constants';
import * as oauthCrypto from './crypto';

export async function pkceChallengeFromVerifier(v) {
  if (!v || !v.length) return null;
  const hashed = await oauthCrypto.sha256(v);
  return { codeChallenge: oauthCrypto.base64UrlEncode(hashed) };
}

export const saveStateAndVerifier = type => {
  const storage = localStorage;

  // Create and store a random "state" value
  const state = oauthCrypto.generateRandomString(28);

  // Create and store a new PKCE code_verifier (the plaintext random secret)
  const codeVerifier = oauthCrypto.generateRandomString(64);

  // Sign up/Create account
  if (Object.values(SIGNUP_TYPES).includes(type)) {
    storage.setItem(`${type}_state`, state);
    storage.setItem(`${type}_code_verifier`, codeVerifier);
  } else {
    // Sign in
    storage.setItem(OAUTH_KEYS.STATE, state);
    storage.setItem(OAUTH_KEYS.CODE_VERIFIER, codeVerifier);
  }

  return { state, codeVerifier };
};

export const removeStateAndVerifier = () => {
  const storage = localStorage;

  Object.keys(storage)
    .filter(key => ALL_STATE_AND_VERIFIERS.includes(key))
    .forEach(key => {
      storage.removeItem(key);
    });
};

export const updateStateAndVerifier = csp => {
  const storage = localStorage;

  storage.setItem(OAUTH_KEYS.STATE, storage.getItem(`${csp}_signup_state`));
  storage.setItem(
    OAUTH_KEYS.CODE_VERIFIER,
    storage.getItem(`${csp}_signup_code_verifier`),
  );

  const signupTypesMap = [
    `logingov_signup_state`,
    `logingov_signup_code_verifier`,
    `idme_signup_state`,
    `idme_signup_code_verifier`,
  ];

  Object.keys(storage)
    .filter(key => signupTypesMap.includes(key))
    .forEach(key => {
      storage.removeItem(key);
    });
};

/**
 *
 * @param {String} type
 */
export async function createOAuthRequest({
  application = '',
  clientId,
  config,
  passedQueryParams = {},
  passedOptions = {},
  type = '',
  acr,
}) {
  const isDefaultOAuth = !application || clientId === CLIENT_IDS.WEB;
  const isMobileOAuth =
    [EXTERNAL_APPS.VA_FLAGSHIP_MOBILE, EXTERNAL_APPS.VA_OCC_MOBILE].includes(
      application,
    ) || clientId === CLIENT_IDS.MOBILE;
  const { oAuthOptions } =
    config ??
    (externalApplicationsConfig[application] ||
      externalApplicationsConfig.default);
  const useType = passedOptions.isSignup
    ? type.slice(0, type.indexOf('_'))
    : type;

  /*
    Web - Generate state & codeVerifier if default oAuth
  */
  const { state, codeVerifier } = isDefaultOAuth && saveStateAndVerifier(type);

  /*
    Mobile - Use passed code_challenge
    Web - Generate code_challenge
  */
  const { codeChallenge } =
    isMobileOAuth && passedQueryParams
      ? passedQueryParams
      : await pkceChallengeFromVerifier(codeVerifier);

  // Build the authorization URL query params from config
  const oAuthParams = {
    [OAUTH_KEYS.CLIENT_ID]: encodeURIComponent(
      clientId || oAuthOptions.clientId,
    ),
    [OAUTH_KEYS.ACR]:
      acr ||
      (passedOptions.isSignup
        ? oAuthOptions.acrSignup[type]
        : oAuthOptions.acr[type]),
    [OAUTH_KEYS.RESPONSE_TYPE]: OAUTH_ALLOWED_PARAMS.CODE,
    ...(isDefaultOAuth && { [OAUTH_KEYS.STATE]: state }),
    ...(passedQueryParams.gaClientId && {
      [GA.queryParams.sis]: passedQueryParams.gaClientId,
    }),
    [OAUTH_KEYS.CODE_CHALLENGE]: codeChallenge,
    [OAUTH_KEYS.CODE_CHALLENGE_METHOD]: OAUTH_ALLOWED_PARAMS.S256,
  };

  const url = new URL(API_SIGN_IN_SERVICE_URL({ type: useType }));

  Object.keys(oAuthParams).forEach(param =>
    url.searchParams.append(param, oAuthParams[param]),
  );

  recordEvent({ event: `login-attempted-${type}-oauth-${clientId}` });

  return url.toString();
}

export const getCV = () => {
  const storage = localStorage;
  const codeVerifier = storage.getItem(OAUTH_KEYS.CODE_VERIFIER);
  return { codeVerifier };
};

export function buildTokenRequest({
  code,
  redirectUri = `${environment.BASE_URL}`,
} = {}) {
  const { codeVerifier } = getCV();

  if (!code || !codeVerifier) return null;

  // Build the authorization URL
  const oAuthParams = {
    [OAUTH_KEYS.GRANT_TYPE]: OAUTH_ALLOWED_PARAMS.AUTH_CODE,
    [OAUTH_KEYS.CLIENT_ID]: encodeURIComponent(CLIENT_IDS.WEB),
    [OAUTH_KEYS.REDIRECT_URI]: encodeURIComponent(redirectUri),
    [OAUTH_KEYS.CODE]: code,
    [OAUTH_KEYS.CODE_VERIFIER]: codeVerifier,
  };

  const url = new URL(
    API_SIGN_IN_SERVICE_URL({ endpoint: OAUTH_ENDPOINTS.TOKEN }),
  );

  Object.keys(oAuthParams).forEach(param =>
    url.searchParams.append(param, oAuthParams[param]),
  );

  return url;
}

export const requestToken = async ({ code, redirectUri, csp }) => {
  const url = buildTokenRequest({
    code,
    redirectUri,
  });

  if (!url) return null;

  const response = await fetch(url.toString(), {
    method: 'POST',
    credentials: 'include',
  });

  recordEvent({
    event: response.ok
      ? `login-success-${csp}-oauth-tokenexchange`
      : `login-failure-${csp}-oauth-tokenexchange`,
  });

  if (response.ok) {
    removeStateAndVerifier();
  }

  return response;
};

export const refresh = async ({ type }) => {
  const url = new URL(
    API_SIGN_IN_SERVICE_URL({ endpoint: OAUTH_ENDPOINTS.REFRESH, type }),
  );

  return fetch(url.href, {
    method: 'POST',
    credentials: 'include',
  });
};

export const infoTokenExists = () => {
  return document.cookie.includes(COOKIES.INFO_TOKEN);
};

export const formatInfoCookie = cookieStringRaw => {
  const decoded = cookieStringRaw.includes('%')
    ? decodeURIComponent(cookieStringRaw)
    : cookieStringRaw;
  return decoded.split(',+:').reduce((obj, cookieString) => {
    const [key, value] = cookieString.replace(/{:|}/g, '').split('=>');
    const formattedValue = value
      .replaceAll('++00:00', '')
      .replaceAll('+', ' ')
      .replace(',', '')
      .replace(/\.\d+/g, '');

    return { ...obj, [key]: new Date(formattedValue) };
  }, {});
};

export const getInfoToken = () => {
  if (!infoTokenExists()) return null;

  return document.cookie
    .split(';')
    .map(cookie => cookie.split('='))
    .reduce((_, [cookieKey, cookieValue]) => ({
      ..._,
      ...(cookieKey.includes(COOKIES.INFO_TOKEN) && {
        ...formatInfoCookie(decodeURIComponent(cookieValue)),
      }),
    }));
};

export const removeInfoToken = () => {
  if (!infoTokenExists()) return null;

  const updatedCookie = document.cookie.split(';').reduce((_, cookie) => {
    let tempCookieString = _;
    if (!cookie.includes(COOKIES.INFO_TOKEN)) {
      tempCookieString += `${cookie};`.trim();
    }
    return tempCookieString;
  }, '');
  document.cookie = updatedCookie;
  return undefined;
};

export const checkOrSetSessionExpiration = response => {
  const sessionExpirationSAML = response.headers.get('X-Session-Expiration');

  if (sessionExpirationSAML) {
    localStorage.setItem('sessionExpiration', sessionExpirationSAML);
    return true;
  }

  if (infoTokenExists()) {
    const {
      access_token_expiration: atExpiration,
      refresh_token_expiration: sessionExpirationOAuth,
    } = getInfoToken();

    localStorage.setItem('atExpires', atExpiration);
    localStorage.setItem('sessionExpiration', sessionExpirationOAuth);
    return true;
  }

  return false;
};

export const logoutUrlSiS = () => {
  return new URL(API_SIGN_IN_SERVICE_URL({ endpoint: 'logout' })).href;
};

export const logoutEvent = async (signInServiceName, wait = {}) => {
  const { duration = 500, shouldWait } = wait;
  const sleep = time => {
    return new Promise(resolve => setTimeout(resolve, time));
  };
  recordEvent({ event: `${AUTH_EVENTS.OAUTH_LOGOUT}-${signInServiceName}` });

  updateLoggedInStatus(false);

  if (shouldWait) {
    await sleep(duration);
    teardownProfileSession();
  } else {
    teardownProfileSession();
  }
};
<|endoftext|>


<commit_before><commit_msg>don't need to parse objects from api<commit_after><|endoftext|>


<commit_before>import axios from 'axios';
import {mount, shallowMount, createLocalVue} from '@vue/test-utils';
import BootstrapVue from 'bootstrap-vue';
import Vuex from 'vuex';
import notices from '../vuex/store/notices';
import ThankYouCard from '../components/index/cards/new_student/thank-you.vue';

import mockNotices from './mock_data/notices.json';

const localVue = createLocalVue();
localVue.use(BootstrapVue);
localVue.use(Vuex);

jest.mock('axios');

describe('Thank You Card', () => {
  let store;

  beforeEach(() => {
    store = new Vuex.Store({
      modules: {
        notices,
      },
      state: {
        user: {
          affiliations: {
            student: true,
          }
        }
      }
    });
  });

  it('Check the filter function - default', async () => {
    axios.get.mockResolvedValue({data: mockNotices});
    axios.put = jest.fn(() => Promise.resolve());
    const wrapper = mount(ThankYouCard, {store, localVue});
    // It takes like 10 ms to process the mock data through fetch postProcess
    await new Promise((r) => setTimeout(r, 10));
    let htmlDoc = new DOMParser().parseFromString(wrapper.html(), 'text/html');
    expect(htmlDoc.getElementsByClassName('ty-notices')[0]
           .getElementsByTagName('div').length).toBe(2);
    expect(wrapper.vm.isReady).toBeTruthy();
    expect(wrapper.vm.notices).toHaveLength(0);
    expect(wrapper.vm.showThankYou(mockNotices)).toBeTruthy();
  });

  it('Check the filter function - after notices read', async () => {
    mockNotices[5].is_read = true;
    mockNotices[22].is_read = true;
    axios.get.mockResolvedValue({data: mockNotices});
    axios.put = jest.fn(() => Promise.resolve());
    const wrapper = mount(ThankYouCard, {store, localVue});
    // It takes like 10 ms to process the mock data through fetch postProcess
    await new Promise((r) => setTimeout(r, 10));
    expect(wrapper.vm.isReady).toBeTruthy();
    expect(wrapper.vm.notices).toHaveLength(0);
    expect(wrapper.vm.showThankYou(mockNotices)).toBeFalsy();
  });

  // it('Check the filter function - default', async () => {
  //   axios.get.mockResolvedValue({data: mockNotices});
  //   const wrapper = mount(ThankYouCard, {store, localVue});
  //   // It takes like 10 ms to process the mock data through fetch postProcess
  //   await new Promise((r) => setTimeout(r, 10));
  //   expect(wrapper.vm.isReady).toBeTruthy();
  //   expect(wrapper.vm.notices).toHaveLength(2);
  // });
});
<commit_msg>Fixes thank you test.<commit_after>import axios from 'axios';
import {mount, shallowMount, createLocalVue} from '@vue/test-utils';
import BootstrapVue from 'bootstrap-vue';
import Vuex from 'vuex';
import notices from '../vuex/store/notices';
import ThankYouCard from '../components/index/cards/new_student/thank-you.vue';

import mockNotices from './mock_data/notices.json';

const localVue = createLocalVue();
localVue.use(BootstrapVue);
localVue.use(Vuex);

jest.mock('axios');

describe('Thank You Card', () => {
  let store;

  beforeEach(() => {
    store = new Vuex.Store({
      modules: {
        notices,
      },
      state: {
        user: {
          affiliations: {
            student: true,
          }
        }
      }
    });
  });

  it('Check the filter function - default', async () => {
    axios.get.mockResolvedValue({data: mockNotices});
    axios.put = jest.fn(() => Promise.resolve());
    const wrapper = mount(ThankYouCard, {store, localVue});
    // It takes like 10 ms to process the mock data through fetch postProcess
    await new Promise((r) => setTimeout(r, 10));
    let htmlDoc = new DOMParser().parseFromString(wrapper.html(), 'text/html');
    expect(htmlDoc.getElementsByClassName('myuw-thank-you-notices')[0]
           .getElementsByTagName('div').length).toBe(2);
    expect(wrapper.vm.isReady).toBeTruthy();
    expect(wrapper.vm.notices).toHaveLength(0);
    expect(wrapper.vm.showThankYou(mockNotices)).toBeTruthy();
  });

  it('Check the filter function - after notices read', async () => {
    mockNotices[5].is_read = true;
    mockNotices[22].is_read = true;
    axios.get.mockResolvedValue({data: mockNotices});
    axios.put = jest.fn(() => Promise.resolve());
    const wrapper = mount(ThankYouCard, {store, localVue});
    // It takes like 10 ms to process the mock data through fetch postProcess
    await new Promise((r) => setTimeout(r, 10));
    expect(wrapper.vm.isReady).toBeTruthy();
    expect(wrapper.vm.notices).toHaveLength(0);
    expect(wrapper.vm.showThankYou(mockNotices)).toBeFalsy();
  });

  // it('Check the filter function - default', async () => {
  //   axios.get.mockResolvedValue({data: mockNotices});
  //   const wrapper = mount(ThankYouCard, {store, localVue});
  //   // It takes like 10 ms to process the mock data through fetch postProcess
  //   await new Promise((r) => setTimeout(r, 10));
  //   expect(wrapper.vm.isReady).toBeTruthy();
  //   expect(wrapper.vm.notices).toHaveLength(2);
  // });
});
<|endoftext|>


<commit_before>/**
 * @file CircularLoading component
 * @author liangxiaojun(liangxiaojun@derbysoft.com)
 */

import React, {Component} from 'react';
import PropTypes from 'prop-types';

import Util from '../_vendors/Util';

export default class CircularLoading extends Component {

    static Size = {
        DEFAULT: '',
        SMALL: 'small',
        LARGE: 'large'
    };

    constructor(props, ...restArgs) {
        super(props, ...restArgs);
    }

    render() {

        const {className, style, size, theme, type, weight} = this.props;
        const border = {
            borderWidth: weight
        };

        return (

            <div className={`circular-loading circular-loading-size-${size} ${className}`}
                 type={type}
                 style={style}>

                    <div className={`${theme} circle left border-${type}`}>
                        <div className={`${type ? type : 'solid'}`}
                             style={border}>

                    <div className={`${theme} circle right border-${type}`}>
                        <div className={`${type ? type : 'solid'}`}
                             style={border}>

        );

    }
};

CircularLoading.propTypes = {

    /**
     * The CSS class name of the root element.
     */
    className: PropTypes.string,

    /**
     * Override the styles of the root element.
     */
    style: PropTypes.object,

    /**
     * Set the size of loading,can use small or large.
     */
    size: PropTypes.oneOf(Util.enumerateValue(CircularLoading.Size)),

    /**
     * Set the theme of loading,can use highlight,success,error,warning.
     */
    theme: PropTypes.string,

    /**
     * Set the CircularLoading border-width.
     */
    weight: PropTypes.number,

    /**
     * Set the border style of CircularLoading,can use solid,dotted.
     */
    type: PropTypes.string

};

CircularLoading.defaultProps = {

    className: '',
    style: null,

    size: CircularLoading.Size.DEFAULT,
    theme: '',
    width: '1',
    type: 'solid'

};<commit_msg>optimize CircularLoading code<commit_after>/**
 * @file CircularLoading component
 * @author liangxiaojun(liangxiaojun@derbysoft.com)
 */

import React, {Component} from 'react';
import PropTypes from 'prop-types';

import Util from '../_vendors/Util';

export default class CircularLoading extends Component {

    static Size = {
        DEFAULT: '',
        SMALL: 'small',
        LARGE: 'large'
    };

    constructor(props, ...restArgs) {
        super(props, ...restArgs);
    }

    render() {

        const {className, style, size, theme, type, weight} = this.props,

            loadingClassName = (size ? ` circular-loading-size-${size}` : '') + (className ? ' ' + className : ''),
            border = {
                borderWidth: weight
            };

        return (

                    <div className={`${theme} circle left border-${type}`}>
                        <div className={`${type ? type : 'solid'}`}
                             style={border}>

                    <div className={`${theme} circle right border-${type}`}>
                        <div className={`${type ? type : 'solid'}`}
                             style={border}>

        );

    }
};

CircularLoading.propTypes = {

    /**
     * The CSS class name of the root element.
     */
    className: PropTypes.string,

    /**
     * Override the styles of the root element.
     */
    style: PropTypes.object,

    /**
     * Set the size of loading,can use small or large.
     */
    size: PropTypes.oneOf(Util.enumerateValue(CircularLoading.Size)),

    /**
     * Set the theme of loading,can use highlight,success,error,warning.
     */
    theme: PropTypes.string,

    /**
     * Set the CircularLoading border-width.
     */
    weight: PropTypes.number,

    /**
     * Set the border style of CircularLoading,can use solid,dotted.
     */
    type: PropTypes.string

};

CircularLoading.defaultProps = {

    className: '',
    style: null,

    size: CircularLoading.Size.DEFAULT,
    theme: '',
    width: '1',
    type: 'solid'

};<|endoftext|>


<commit_before>/* server/db.js
 * MongoDB communicator for Montreus Chat. Optional module!
 */

 //APIs
 var mongoose = require("mongoose");

//Globals
var Schema = mongoose.Schema;
var ObjectId = mongoose.Types.ObjectId;

//Startup Routines
mongoose.connect('mongodb://localhost/chat-db', function (error) {
  if (error) {
      console.warn(error);
    }else{
      console.log("Connection to MongoDB established");
    }
});

//MongoDB Globals
var MessageSchema = new Schema({
    message: String,
    room: String,
    sent: Date
});
var Message = mongoose.model('Message', MessageSchema);
//
var findMessages = function(room) {
    return new Promise(function (resolve, decline){
        Message.find({room: room}).sort({date: -1}).exec(function(error, messages) {
            if(error != null){
                resolve(messages);
            }else{
                decline(error);
            }
        });
    });
}

var addNewMessage = function(message, room){
    return new Promise(function (resolve, decline){
        var newMessage = new Message({
            message: message,
            room: room,
            sent: new Date()
        });
        newMessage.save(function(error){
            if(!error){
                resolve();
            }else{
                console.error(error);
                decline(error);
            }
        });
    });
}

//Exporting methods
exports.find = findMessages;
exports.add = addNewMessage;
<commit_msg>Update db.js<commit_after>/* server/db.js
 * MongoDB communicator for Montreus Chat. Optional module!
 */

 //APIs
 var mongoose = require("mongoose");

//Globals
var Schema = mongoose.Schema;
var ObjectId = mongoose.Types.ObjectId;

//Startup Routines
mongoose.connect('mongodb://localhost/chat-db', function (error) {
  if (error) {
      console.warn(error);
    }else{
      console.log("Connection to MongoDB established");
    }
});

//MongoDB Globals
var MessageSchema = new Schema({
    message: String,
    room: String,
    sent: Date
});
var Message = mongoose.model('Message', MessageSchema);
//
var findMessages = function(room) {
    return new Promise(function (resolve, decline){
        Message.find({room: room}).sort({date: -1}).exec(function(error, messages) {
            if(error != null){
                resolve(messages);
            }else{
                console.error(error);
                decline(error);
            }
        });
    });
}

var addNewMessage = function(message, room){
    return new Promise(function (resolve, decline){
        var newMessage = new Message({
            message: message,
            room: room,
            sent: new Date()
        });
        newMessage.save(function(error){
            if(!error){
                resolve();
            }else{
                console.error(error);
                decline(error);
            }
        });
    });
}

//Exporting methods
exports.find = findMessages;
exports.add = addNewMessage;
<|endoftext|>


<commit_before>var expect = require('expect.js');
var rode = require('../../../../rode');

describe('rode.Object', function () {

    /**
     * Check if new instances of Object can be created
     */
    it('should allow new instances', function () {
        var object = new rode.Object({
            key: 'value'
        });
        expect(object instanceof rode.Object).to.be(true);
        expect(object.key).to.be('value');
    });

    /**
     * Check if Object can be extended
     */
    it('should be extensible', function () {
        var MyClass = rode.Object.extend({
            getName: function () {
                return this.name
            }
        });
        var myInstance = new MyClass({
            name: 'my name'
        });
        expect(myInstance instanceof MyClass).to.be(true);
        expect(myInstance.getName()).to.be('my name');
        expect(MyClass.extend).to.be.a('function');
    });

    /**
     * Check if the initialize() method is called correctly
     */
    it('should call the initialize method of the new instances', function () {
        var myObject = new rode.Object({
            initialize: function () {
                this.name = 'my name'
            }
        });
        expect(myObject instanceof rode.Object).to.be(true);
        expect(myObject.initialize).to.be.a('function');
        expect(myObject.name).to.be('my name');
    });
});<commit_msg>Added new tests to rode.Object<commit_after>var expect = require('expect.js');
var rode = require('../../../../rode');

describe('rode.Object', function () {

    /**
     * Check if new instances of Object can be created
     */
    it('should allow new instances', function () {
        var object = new rode.Object({
            key: 'value'
        });
        expect(object instanceof rode.Object).to.be(true);
        expect(object.key).to.be('value');
    });

    /**
     * Check if the initialize() method is called correctly
     */
    it('should call the initialize method of the new instances', function () {
        var myObject = new rode.Object({
            initialize: function () {
                this.name = 'my name'
            }
        });
        expect(myObject instanceof rode.Object).to.be(true);
        expect(myObject.initialize).to.be.a('function');
        expect(myObject.name).to.be('my name');
    });

    /**
     * Check if two instances of rode.Object are isolated
     */
    it('should keep isolated two instances', function () {
        var object1 = new rode.Object({
            name: 'object 1'
        });
        var object2 = new rode.Object({
            name: 'object 2'
        });
        expect(object1 instanceof rode.Object).to.be(true);
        expect(object2 instanceof rode.Object).to.be(true);
        expect(object1.name).to.be('object 1');
        expect(object2.name).to.be('object 2');
    });

    /**
     * Check if Object can be extended
     */
    it('should be extensible', function () {
        var MyClass = rode.Object.extend({
            getName: function () {
                return this.name
            }
        });
        var myInstance = new MyClass({
            name: 'my name'
        });
        expect(myInstance instanceof MyClass).to.be(true);
        expect(myInstance.getName()).to.be('my name');
        expect(MyClass.extend).to.be.a('function');
    });

    /**
     * Check if Object can be extended
     */
    it('should be extensible in multiple levels', function () {
        var MyClass = rode.Object.extend({
            getName: function () {
                return this.name
            }
        });
        var MyChildClass = MyClass.extend({
            initialize: function () {
                this.status = 'ok';
            },
            getUpperName: function () {
                return this.getName().toUpperCase();
            }
        });
        var myInstance = new MyChildClass({
            name: 'my name'
        });
        expect(myInstance instanceof MyClass).to.be(true);
        expect(myInstance.getUpperName()).to.be('MY NAME');
        expect(myInstance.status).to.be('ok');
        expect(MyChildClass.extend).to.be.a('function');
    });
});<|endoftext|>