From cd2ed9a4874519a320c7f1155598e0a26b4b6626 Mon Sep 17 00:00:00 2001 From: Laura Schlimmer Date: Mon, 17 Nov 2014 15:54:45 +0000 Subject: [PATCH 01/15] init datepicker --- .../fnordmetric-webui-datepicker.js | 360 +++++++----------- 1 file changed, 143 insertions(+), 217 deletions(-) diff --git a/fnordmetric-webui/fnordmetric-webui-datepicker.js b/fnordmetric-webui/fnordmetric-webui-datepicker.js index ab49f6ddc..af41562ec 100644 --- a/fnordmetric-webui/fnordmetric-webui-datepicker.js +++ b/fnordmetric-webui/fnordmetric-webui-datepicker.js @@ -16,84 +16,62 @@ if (FnordMetric.views === undefined) { FnordMetric.views = {}; } +/* Displays a calendar from an input for selecting time and date*/ FnordMetric.util.DatePicker = function(elem, dp_input, viewport, callback) { - var dp_widget = document.createElement("div"); - dp_widget.className = "datepicker_widget"; - elem.appendChild(dp_widget); - - var curr_day = new Date().getDay(); - var curr_date = new Date().getDate(); - var curr_month = new Date().getMonth(); - var curr_year = new Date().getFullYear(); - var m_month; - var y_year; - var isCurrMonth; - - var selected_ts; - var selected_hours; - var selected_minutes; + var currMonth; + var currYear; - var human_days = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"]; - - - function resetDatepicker() { - dp_widget.innerHTML = ""; + var dp_widget = document.createElement("div"); dp_widget.className = "datepicker_widget"; - } - + elem.appendChild(dp_widget); - function onSelect(hours, minutes, day, month, year) { - var hours = (hours.length > 0)? hours : selected_hours; - var minutes = (minutes.length > 0)? minutes : selected_minutes; - var ts = new Date(year, month, day, hours, minutes).getTime(); + var currentTimestamp = new Date( + parseInt(dp_input.getAttribute("id"), 10)); - if (ts <= Date.now() ) { - dp_input.value = - FnordMetric.util.appendLeadingZero(month+1) + - "/" + - FnordMetric.util.appendLeadingZero(day) + - "/" + year + " " + - FnordMetric.util.appendLeadingZero(hours) + - ":" + - FnordMetric.util.appendLeadingZero(minutes); + function getDaysInMonth(year, month) { + return (new Date(year, (month+1), 0).getDate()); + } - callback(ts); - dp_input.setAttribute("id", ts); - resetDatepicker(); - } + function getFirstDayOfMonth(year, month) { + return (new Date(year + "-" + (month+1) + "-01").getDay()); } - function isSelectable(day) { - if (isCurrMonth) { - return day <= curr_date; + /*checks if the day is in this month or not + day and first_day are numeric weekday description + rowNum the actual row and date the actual date + */ + function inThisMonth(day, first_day, date, daysInMonth, rowNum) { + if (rowNum > 0 && rowNum < 4) {return true} + if (rowNum == 0) { + /* the calendar has the same number of rows for each month */ + if (first_day == 0) { + return false; + } else if (day < first_day) { + return false; + } else { + return true; + } + } else if (rowNum >= 4) { + if (date <= daysInMonth) { + return true; + } else { + return false; + } } - return y_year <= curr_year && m_month <= curr_month; } + function isSelectable( - function init(month, year) { - y_year = year; - m_month = month; - - dp_widget.className = "datepicker_widget active"; - - selected_ts = new Date( - parseInt(dp_input.getAttribute("id"), 10)); - - selected_hours = + /* generate html for time input and handle input */ + function renderTimeInput() { + var currentMinutes = FnordMetric.util.appendLeadingZero( - selected_ts.getHours()); + currentTimestamp.getMinutes()); - selected_minutes = + var currentHours = FnordMetric.util.appendLeadingZero( - selected_ts.getMinutes()); - - var sltd_date = selected_ts.getDate(); - var sltd_month = selected_ts.getMonth(); - var sltd_year = selected_ts.getYear(); - var isSltdMonth = (month == sltd_month && year == curr_year); - isCurrMonth = (month == curr_month && year == curr_year); + currentTimestamp.getHours()); var input_container = document.createElement("div"); input_container.className = "input_container"; @@ -101,207 +79,155 @@ FnordMetric.util.DatePicker = function(elem, dp_input, viewport, callback) { separator.innerHTML = ":"; var hour_input = document.createElement("input"); + hour_input.placeholder = currentHours; + + var minute_input = document.createElement("input"); + minute_input.placeholder = currentMinutes; + + input_container.appendChild(hour_input); + input_container.appendChild(separator); + input_container.appendChild(minute_input); + dp_widget.appendChild(input_container); - hour_input.placeholder = selected_hours; hour_input.addEventListener('focus', function(e) { e.preventDefault(); - timeInputFocus = true; FnordMetric.util.validatedTimeInput(this, "hour"); }, false); - var minute_input = document.createElement("input"); - minute_input.placeholder = selected_minutes; - minute_input.addEventListener('focus', function() { - timeInputFocus = true; + minute_input.addEventListener('focus', function(e) { + e.preventDefault(); FnordMetric.util.validatedTimeInput(this, "minute"); }, false); + } + function resetCalendar(table) { + console.log("reset Calendar"); + table.innerHTML = ""; + FnordMetric.util.removeIfChild( + table, dp_widget); + } - input_container.appendChild(hour_input); - input_container.appendChild(separator); - input_container.appendChild(minute_input); - dp_widget.appendChild(input_container); + function renderWeekHeader(table) { + var header = document.createElement("tr"); + var dayNames = + ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"]; - var first_day = new Date(year + "-" + (month+1) + "-01").getDay(); - /* Mo = 1, ... , Su = 7 */ - first_day = (first_day === 0)? 7 : first_day-1; - var num_days = new Date(year, (month+1), 0).getDate(); - var table = document.createElement("table"); + dayNames.map(function(day) { + var cell = document.createElement("th"); + cell.innerHTML = day; + header.appendChild(cell); + }); - var month_header = document.createElement("tr"); + table.appendChild(header); + } + + function renderMonthHeader(year, month, table) { + var year = parseInt(year, 10); + var month = parseInt(month, 10); + var row = document.createElement("tr"); + var cell = document.createElement("td"); + cell.colSpan = "7"; + var month_title = document.createElement("span"); + month_title.className = "datepicker_title"; + month_title.innerHTML = + FnordMetric.util.getMonthStr(month) + " " + year; + + /* tooltip to select previous month */ var prev_ttp = FnordMetric.createButton( "#", "month_ttp", ""); + + /* tooltip to select next month */ + var next_ttp = FnordMetric.createButton( + "#", "month_ttp", ""); + prev_ttp.addEventListener('click', function(e) { e.preventDefault(); - resetDatepicker(); + resetCalendar(table); year = (month == 0)? year-1 : year; - init((month-1 +12) % 12, year); + month = (month + 11) % 12; + renderCalendar(year, month); }, false); - var next_ttp = FnordMetric.createButton( - "#", "month_ttp", ""); next_ttp.addEventListener('click', function(e) { e.preventDefault(); - resetDatepicker(); + resetCalendar(table); year = (month == 11)? year+1 : year; - init((month + 1) % 12, year); + month = (month + 1) % 12; + renderCalendar(year, month); }, false); - var month_title = document.createElement("span"); - month_title.innerHTML = - FnordMetric.util.getMonthStr(month) + " " + year; - month_title.className = "datepicker_title"; - var month_cell = document.createElement("td"); - month_cell.colSpan = "7"; - month_cell.appendChild(prev_ttp); - month_cell.appendChild(month_title); - month_cell.appendChild(next_ttp); - month_header.appendChild(month_cell); - - - var day_header = document.createElement("tr"); - human_days.map(function(day) { - var header_cell = document.createElement("th"); - header_cell.innerHTML = day; - day_header.appendChild(header_cell); - }); - var is - var day = 1; - var rows = 0; - var first_row = document.createElement("tr"); - rows++; - for (var i = 0; i < 7; i++) { - var cell = document.createElement("td"); - if (i < first_day || first_day == 0) { - cell.innerHTML = ""; - } else { - - if (isSelectable(day)) { - if (isCurrMonth && day == curr_date) { - cell.className = "highlight_border"; - } - if (isSltdMonth && day == sltd_date) { - cell.className += " highlight"; - } + cell.appendChild(prev_ttp); + cell.appendChild(month_title); + cell.appendChild(next_ttp); + row.appendChild(cell); + table.appendChild(row); + } - var link = FnordMetric.createButton( - "#", undefined, day); - link.addEventListener('click', function(e) { - e.preventDefault(); - onSelect( - hour_input.value, - minute_input.value, - this.innerText, month, year); - }, false); - - cell.appendChild(link); - } else { - cell.innerHTML = day; - } + function handleDayLinks(link, date, month, year) { + link.addEventListener('click', function(e) { + console.log("click"; + console.log(date + month + year); + }, false); + } - day++; - } - first_row.appendChild(cell); - } - table.appendChild(day_header); - table.appendChild(month_header); - table.appendChild(first_row); + function renderWeeks(year, month, calendar) { + var daysInMonth = getDaysInMonth(year, month); + var fDay = getFirstDayOfMonth(year, month); + var numRows = 6; + var date = 1; - while (rows < 6 && day <= num_days) { + /* generate the rows (that is the weeks) */ + for (var dprow = 0; dprow < numRows; dprow++) { var row = document.createElement("tr"); - rows++; - for (var i = 0; i < 7 && day <= num_days; i++) { + for (day = 0; day < 7; day ++) { var cell = document.createElement("td"); - - if (isSelectable(day)) { - if (isCurrMonth && day == curr_date) { - cell.className = "highlight_border"; - } - if (isSltdMonth && day == sltd_date) { - cell.className += " highlight"; - } - - var link = FnordMetric.createButton( - "#", undefined, day); - link.addEventListener('click', function(e) { - e.preventDefault(); - onSelect( - hour_input.value, - minute_input.value, - this.innerText, month, year); - }, false); - - cell.appendChild(link); - } else { - cell.innerHTML = day; - } - - row.appendChild(cell); - day++; - } - table.appendChild(row); - } - - if (rows < 6) { - var last_row = document.createElement("tr"); - for (var i = 0; i < 7; i++) { - var cell = document.createElement("td"); - if (day <= num_days) { - - if (isSelectable(day)) { - if (isCurrMonth && day == curr_date) { - cell.className = "highlight_border"; - } - if (isSltdMonth && day == sltd_date) { - cell.className += " highlight"; - } - + if (inThisMonth(day, fDay, date, daysInMonth, dprow)) { + if (isSelectable(date, month, year)) { var link = FnordMetric.createButton( - "#", undefined, day); - link.addEventListener('click', function(e) { - e.preventDefault(); - onSelect( - hour_input.value, - minute_input.value, - this.innerText, month, year); - }, false); - + "#", undefined, date); cell.appendChild(link); + handleDateLinks(link, date, month, year); } else { - cell.innerHTML = day; + cell.innerHTML = date; } - - day++; - } else { - cell.innerHTML = ""; + date++; } - last_row.appendChild(cell); + row.appendChild(cell); } - table.appendChild(last_row); + calendar.appendChild(row); } + } + + function renderCalendar(year, month) { + console.log("render calendar " + year + month); + var table = document.createElement("table"); + renderWeekHeader(table); + renderMonthHeader(year, month, table); + renderWeeks(year, month, table); dp_widget.appendChild(table); } - - document.addEventListener('click', function() { - resetDatepicker(); - }, false); + function init() { + var now = new Date(); + currYear = now.getFullYear(); + currMonth = now.getMonth(); + dp_widget.innerHTML = ""; + dp_widget.className += " active"; + renderTimeInput(); + renderCalendar(currYear, currMonth); + } - elem.addEventListener('click', function(e) { - e.stopPropagation(); - }); dp_input.addEventListener('focus', function(event) { - dp_widget.innerHTML = ""; - var now = new Date(); - var month = now.getMonth(); - var year = now.getFullYear(); - init(month, year) + init(); }, false); -}; + + + +} From 562796e1cb5a5c00452ebd653c91d87dbaf2d4c8 Mon Sep 17 00:00:00 2001 From: Laura Schlimmer Date: Mon, 17 Nov 2014 16:56:01 +0000 Subject: [PATCH 02/15] proper datepicker function --- .../fnordmetric-webui-datepicker.js | 177 +++++++++++++----- 1 file changed, 132 insertions(+), 45 deletions(-) diff --git a/fnordmetric-webui/fnordmetric-webui-datepicker.js b/fnordmetric-webui/fnordmetric-webui-datepicker.js index af41562ec..c35e5fc0e 100644 --- a/fnordmetric-webui/fnordmetric-webui-datepicker.js +++ b/fnordmetric-webui/fnordmetric-webui-datepicker.js @@ -18,15 +18,21 @@ if (FnordMetric.views === undefined) { /* Displays a calendar from an input for selecting time and date*/ FnordMetric.util.DatePicker = function(elem, dp_input, viewport, callback) { + /* current date */ var currMonth; var currYear; + var currDate; - var dp_widget = document.createElement("div"); - dp_widget.className = "datepicker_widget"; - elem.appendChild(dp_widget); + /* in datepicker selected date */ + var selectedTimestamp; + var selectedYear; + var selectedMonth; + var selectedDate; - var currentTimestamp = new Date( - parseInt(dp_input.getAttribute("id"), 10)); + + var dp_widget = document.createElement("div"); + dp_widget.className = "datepicker_widget"; + elem.appendChild(dp_widget); function getDaysInMonth(year, month) { @@ -61,47 +67,121 @@ FnordMetric.util.DatePicker = function(elem, dp_input, viewport, callback) { } } - function isSelectable( + function isSelectable(date, month, year) { + if (month == currMonth) { + return date <= currDate; + } + return (year <= currYear && month < currMonth); + } - /* generate html for time input and handle input */ - function renderTimeInput() { - var currentMinutes = - FnordMetric.util.appendLeadingZero( - currentTimestamp.getMinutes()); + /* if date is selectable */ + function getCellName(date, month, year) { + var name = ""; + /* date is today */ + if (date == currDate && month == currMonth && year == currYear) { + name = "highlight_border"; + } + /* date is selected date */ + if (date == selectedDate && + month == selectedMonth && + year == selectedYear) { + name += " highlight"; + } + return name; + } - var currentHours = - FnordMetric.util.appendLeadingZero( - currentTimestamp.getHours()); + function onSelect(date, month, year) { + var inputs = timeInput.singleton.getValues(); + var hours = inputs.hours; + var minutes = inputs.minutes; + var ts = new Date(year, month, date, hours, minutes).getTime(); + if (ts <= Date.now() ) { + dp_input.value = + FnordMetric.util.appendLeadingZero(month+1) + + "/" + + FnordMetric.util.appendLeadingZero(date) + + "/" + year + " " + + FnordMetric.util.appendLeadingZero(hours) + + ":" + + FnordMetric.util.appendLeadingZero(minutes); + + callback(ts); + dp_input.setAttribute("id", ts); + resetDatepicker(); + } + } - var input_container = document.createElement("div"); - input_container.className = "input_container"; - var separator = document.createElement("span"); - separator.innerHTML = ":"; - var hour_input = document.createElement("input"); - hour_input.placeholder = currentHours; + /* sets event listener for selectable dates */ + function handleDateLinks(link, date, month, year) { + link.addEventListener('click', function(e) { + e.preventDefault(); + onSelect(date, month, year); + }, false); + } - var minute_input = document.createElement("input"); - minute_input.placeholder = currentMinutes; - input_container.appendChild(hour_input); - input_container.appendChild(separator); - input_container.appendChild(minute_input); - dp_widget.appendChild(input_container); + /* generate html for time input and handle input */ + var timeInput = function() { + var selectedMinutes = + FnordMetric.util.appendLeadingZero( + selectedTimestamp.getMinutes()); + + var selectedHours = + FnordMetric.util.appendLeadingZero( + selectedTimestamp.getHours()); + + var input_container = document.createElement("div"); + input_container.className = "input_container"; + var separator = document.createElement("span"); + separator.innerHTML = ":"; + + var hour_input = document.createElement("input"); + hour_input.placeholder = selectedHours; + + var minute_input = document.createElement("input"); + minute_input.placeholder = selectedMinutes; + + function render() { + input_container.appendChild(hour_input); + input_container.appendChild(separator); + input_container.appendChild(minute_input); + dp_widget.appendChild(input_container); + + hour_input.addEventListener('focus', function(e) { + e.preventDefault(); + FnordMetric.util.validatedTimeInput(this, "hour"); + }, false); + + minute_input.addEventListener('focus', function(e) { + e.preventDefault(); + FnordMetric.util.validatedTimeInput(this, "minute"); + }, false); + } - hour_input.addEventListener('focus', function(e) { - e.preventDefault(); - FnordMetric.util.validatedTimeInput(this, "hour"); - }, false); + function getValues() { + var hours = hour_input.value.length > 0 ? + hour_input.value : selectedHours; + var minutes = minute_input.value.length > 0 ? + minute_input.value : selectedMinutes; + return { + "hours" : hours, + "minutes" : minutes + } + } - minute_input.addEventListener('focus', function(e) { - e.preventDefault(); - FnordMetric.util.validatedTimeInput(this, "minute"); - }, false); + return { + "render" : render, + "getValues" : getValues + } + } + + function resetDatepicker() { + dp_widget.innerHTML = ""; + dp_widget.className = "datepicker_widget"; } function resetCalendar(table) { - console.log("reset Calendar"); table.innerHTML = ""; FnordMetric.util.removeIfChild( table, dp_widget); @@ -165,13 +245,6 @@ FnordMetric.util.DatePicker = function(elem, dp_input, viewport, callback) { table.appendChild(row); } - function handleDayLinks(link, date, month, year) { - link.addEventListener('click', function(e) { - console.log("click"; - console.log(date + month + year); - }, false); - } - function renderWeeks(year, month, calendar) { var daysInMonth = getDaysInMonth(year, month); var fDay = getFirstDayOfMonth(year, month); @@ -187,6 +260,7 @@ FnordMetric.util.DatePicker = function(elem, dp_input, viewport, callback) { if (isSelectable(date, month, year)) { var link = FnordMetric.createButton( "#", undefined, date); + cell.className = getCellName(date, month, year); cell.appendChild(link); handleDateLinks(link, date, month, year); } else { @@ -202,7 +276,6 @@ FnordMetric.util.DatePicker = function(elem, dp_input, viewport, callback) { function renderCalendar(year, month) { - console.log("render calendar " + year + month); var table = document.createElement("table"); renderWeekHeader(table); renderMonthHeader(year, month, table); @@ -214,9 +287,18 @@ FnordMetric.util.DatePicker = function(elem, dp_input, viewport, callback) { var now = new Date(); currYear = now.getFullYear(); currMonth = now.getMonth(); + currDate = now.getDate(); + + selectedTimestamp = new Date( + parseInt(dp_input.getAttribute("id"), 10)); + selectedYear = selectedTimestamp.getFullYear(); + selectedMonth = selectedTimestamp.getMonth(); + selectedDate = selectedTimestamp.getDate(); + dp_widget.innerHTML = ""; dp_widget.className += " active"; - renderTimeInput(); + timeInput.singleton = timeInput(); + timeInput.singleton.render(); renderCalendar(currYear, currMonth); } @@ -226,8 +308,13 @@ FnordMetric.util.DatePicker = function(elem, dp_input, viewport, callback) { init(); }, false); + document.addEventListener('click', function() { + resetDatepicker(); + }, false); - + elem.addEventListener('click', function(e) { + e.stopPropagation(); + }); } From 09019d220c2ad7ebd55a6a722f6aa64dbf04f1fc Mon Sep 17 00:00:00 2001 From: Laura Schlimmer Date: Mon, 17 Nov 2014 19:40:59 +0000 Subject: [PATCH 03/15] proper time input function --- .../fnordmetric-webui-datepicker.js | 122 +++++++++--------- 1 file changed, 60 insertions(+), 62 deletions(-) diff --git a/fnordmetric-webui/fnordmetric-webui-datepicker.js b/fnordmetric-webui/fnordmetric-webui-datepicker.js index c35e5fc0e..ee4b93a84 100644 --- a/fnordmetric-webui/fnordmetric-webui-datepicker.js +++ b/fnordmetric-webui/fnordmetric-webui-datepicker.js @@ -16,6 +16,62 @@ if (FnordMetric.views === undefined) { FnordMetric.views = {}; } + /* generate html for time input and handle input */ +FnordMetric.util.timeInput = function(selectedTimestamp, elem) { + var selectedMinutes = + FnordMetric.util.appendLeadingZero( + selectedTimestamp.getMinutes()); + + var selectedHours = + FnordMetric.util.appendLeadingZero( + selectedTimestamp.getHours()); + + var input_container = document.createElement("div"); + input_container.className = "input_container"; + var separator = document.createElement("span"); + separator.innerHTML = ":"; + + var hour_input = document.createElement("input"); + hour_input.placeholder = selectedHours; + + var minute_input = document.createElement("input"); + minute_input.placeholder = selectedMinutes; + + function render() { + input_container.appendChild(hour_input); + input_container.appendChild(separator); + input_container.appendChild(minute_input); + elem.appendChild(input_container); + + hour_input.addEventListener('focus', function(e) { + e.preventDefault(); + FnordMetric.util.validatedTimeInput(this, "hour"); + }, false); + + minute_input.addEventListener('focus', function(e) { + e.preventDefault(); + FnordMetric.util.validatedTimeInput(this, "minute"); + }, false); + } + + function getValues() { + var hours = hour_input.value.length > 0 ? + hour_input.value : selectedHours; + var minutes = minute_input.value.length > 0 ? + minute_input.value : selectedMinutes; + return { + "hours" : hours, + "minutes" : minutes + } + } + + return { + "render" : render, + "getValues" : getValues + } +} + + /* Displays a calendar from an input for selecting time and date*/ FnordMetric.util.DatePicker = function(elem, dp_input, viewport, callback) { /* current date */ @@ -29,7 +85,7 @@ FnordMetric.util.DatePicker = function(elem, dp_input, viewport, callback) { var selectedMonth; var selectedDate; - + var timeInput; var dp_widget = document.createElement("div"); dp_widget.className = "datepicker_widget"; elem.appendChild(dp_widget); @@ -91,7 +147,7 @@ FnordMetric.util.DatePicker = function(elem, dp_input, viewport, callback) { } function onSelect(date, month, year) { - var inputs = timeInput.singleton.getValues(); + var inputs = timeInput.getValues(); var hours = inputs.hours; var minutes = inputs.minutes; var ts = new Date(year, month, date, hours, minutes).getTime(); @@ -120,62 +176,6 @@ FnordMetric.util.DatePicker = function(elem, dp_input, viewport, callback) { }, false); } - - /* generate html for time input and handle input */ - var timeInput = function() { - var selectedMinutes = - FnordMetric.util.appendLeadingZero( - selectedTimestamp.getMinutes()); - - var selectedHours = - FnordMetric.util.appendLeadingZero( - selectedTimestamp.getHours()); - - var input_container = document.createElement("div"); - input_container.className = "input_container"; - var separator = document.createElement("span"); - separator.innerHTML = ":"; - - var hour_input = document.createElement("input"); - hour_input.placeholder = selectedHours; - - var minute_input = document.createElement("input"); - minute_input.placeholder = selectedMinutes; - - function render() { - input_container.appendChild(hour_input); - input_container.appendChild(separator); - input_container.appendChild(minute_input); - dp_widget.appendChild(input_container); - - hour_input.addEventListener('focus', function(e) { - e.preventDefault(); - FnordMetric.util.validatedTimeInput(this, "hour"); - }, false); - - minute_input.addEventListener('focus', function(e) { - e.preventDefault(); - FnordMetric.util.validatedTimeInput(this, "minute"); - }, false); - } - - function getValues() { - var hours = hour_input.value.length > 0 ? - hour_input.value : selectedHours; - var minutes = minute_input.value.length > 0 ? - minute_input.value : selectedMinutes; - return { - "hours" : hours, - "minutes" : minutes - } - } - - return { - "render" : render, - "getValues" : getValues - } - } - function resetDatepicker() { dp_widget.innerHTML = ""; dp_widget.className = "datepicker_widget"; @@ -297,13 +297,11 @@ FnordMetric.util.DatePicker = function(elem, dp_input, viewport, callback) { dp_widget.innerHTML = ""; dp_widget.className += " active"; - timeInput.singleton = timeInput(); - timeInput.singleton.render(); + timeInput = FnordMetric.util.timeInput(selectedTimestamp, dp_widget); + timeInput.render(); renderCalendar(currYear, currMonth); } - - dp_input.addEventListener('focus', function(event) { init(); }, false); From 1c5870363bdcd0cd9bbf994e735e48261c4b48a3 Mon Sep 17 00:00:00 2001 From: Laura Schlimmer Date: Tue, 18 Nov 2014 13:20:27 +0000 Subject: [PATCH 04/15] test and refactor parseQueryString --- fnordmetric-webui/.gitignore | 1 + fnordmetric-webui/Makefile | 1 + fnordmetric-webui/fnordmetric-webui-layout.js | 1 + .../fnordmetric-webui-unittests.js | 55 +++++++++++++++++++ fnordmetric-webui/fnordmetric-webui-util.js | 41 +++++--------- 5 files changed, 73 insertions(+), 26 deletions(-) create mode 100644 fnordmetric-webui/fnordmetric-webui-unittests.js diff --git a/fnordmetric-webui/.gitignore b/fnordmetric-webui/.gitignore index 141bce8de..98c551dbd 100644 --- a/fnordmetric-webui/.gitignore +++ b/fnordmetric-webui/.gitignore @@ -1 +1,2 @@ fnordmetric-webui.js +tests/ diff --git a/fnordmetric-webui/Makefile b/fnordmetric-webui/Makefile index d64aabfad..b4ad758fd 100644 --- a/fnordmetric-webui/Makefile +++ b/fnordmetric-webui/Makefile @@ -24,6 +24,7 @@ build: fnordmetric-webui-embedpopup.js \ fnordmetric-webui-autocomplete.js \ fnordmetric-webui-datepicker.js \ + fnordmetric-webui-unittests.js \ codemirror.js \ codemirror-sql.js \ ) > fnordmetric-webui.js diff --git a/fnordmetric-webui/fnordmetric-webui-layout.js b/fnordmetric-webui/fnordmetric-webui-layout.js index 85ce30e48..db4198105 100644 --- a/fnordmetric-webui/fnordmetric-webui-layout.js +++ b/fnordmetric-webui/fnordmetric-webui-layout.js @@ -86,6 +86,7 @@ FnordMetric.WebUI = function() { }; function openUrl(raw_url, push_state) { + FnordMetric.UnitTests(); var url = FnordMetric.util.parseQueryString(raw_url); var query_params = url["query_params"]; diff --git a/fnordmetric-webui/fnordmetric-webui-unittests.js b/fnordmetric-webui/fnordmetric-webui-unittests.js new file mode 100644 index 000000000..937b6ffc1 --- /dev/null +++ b/fnordmetric-webui/fnordmetric-webui-unittests.js @@ -0,0 +1,55 @@ +/** + * This file is part of the "FnordMetric" project + * Copyright (c) 2014 Laura Schlimmer + * Copyright (c) 2014 Paul Asmuth, Google Inc. + * + * FnordMetric is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License v3.0. You should have received a + * copy of the GNU General Public License along with this program. If not, see + * . + */ +if (FnordMetric === undefined) { + FnordMetric = {}; +} + +if (FnordMetric.util === undefined) { + FnordMetric.util = {}; +} + +/* Unit Tests for FnordMetric.util functions */ +FnordMetric.UnitTests = function() { + console.log("make unit tests"); + var results = { + total : 0, + bad : 0 + } + + var testParseQueryString = function() { + function test(querystr, expected) { + var result = FnordMetric.util.parseQueryString(querystr); + result = JSON.stringify(result); + expected = JSON.stringify(expected); + if (result != expected) { + results.bad++; + console.log("Testing parseQueryString expected " + + expected + ", but was " + result); + } + } + test("metric_list?metric=/osx/load_avg_15m&view=value", + {path : "metric_list", + query_params : { + innerView : "metric", + innerViewValue : "/osx/load_avg_15m", + view : "value"}}); + + test("metric_list", {path : "metric_list", query_params : {}}); + test("query_playground?sql_query=select%20*%20from%20http_status_codes%3B", + {path : "query_playground", + query_params : { + innerView : "sql_query", + innerViewValue : decodeURIComponent("select%20*%20from%20http_status_codes%3B")}}); + test("", {path : "", query_params: {}}); + test("undefined", {path: "", query_params:{}}); + }(); + +} diff --git a/fnordmetric-webui/fnordmetric-webui-util.js b/fnordmetric-webui/fnordmetric-webui-util.js index 496b38039..11f47a1ca 100644 --- a/fnordmetric-webui/fnordmetric-webui-util.js +++ b/fnordmetric-webui/fnordmetric-webui-util.js @@ -9,27 +9,6 @@ * . */ -/** - * - * TODOS: - * - * Query Playground: - * - "embed this query" opens up a popup with html/js/ruby snippets - * - prevent reload/navigation to other page (body onunload) -* - stretch: display a [fake] loading bar - * - nice to have: represent current chart and table, view in url --> renderResultPane - * - * Metric list view: - * - write meaningful error messages - * - * - fix menuitems - * - fix resize tooltip, vertical split - * - wrong URL: redirect and display message - * - fix back and for (push and pop states) - * - */ - - if (FnordMetric === undefined) { FnordMetric = {}; } @@ -38,12 +17,18 @@ if (FnordMetric.util === undefined) { FnordMetric.util = {}; } +/** + * extracts the params from the url + * @param qstr like metric_list?metric=/osx/load_avg_15m&view=value + */ FnordMetric.util.parseQueryString = function(qstr) { var path; var query_params = {}; if (qstr.indexOf("?") >= 0) { - path = qstr.substr(0, qstr.indexOf("?")) + path = (qstr.substr(0, qstr.indexOf("?")) != "undefined")? + qstr.substr(0, qstr.indexOf("?")) : ""; + var params_str = qstr.substr(qstr.indexOf("?") + 1); var raw_params = params_str.split('&'); @@ -54,13 +39,17 @@ FnordMetric.util.parseQueryString = function(qstr) { query_params.innerViewValue = decodeURIComponent(param[1]); for (var i = 1; i < raw_params.length; i++) { - var param = raw_params[i].split('='); - query_params[decodeURIComponent(param[0])] = - decodeURIComponent(param[1]); + var param = (raw_params[i].split('=') != "undefined") ? + raw_params[i].split('=') : ""; + if (param[0] != "undefined") { + query_params[decodeURIComponent(param[0])] = + (param[1] != "undefined") ? + decodeURIComponent(param[1]) : ""; + } } } else { - path = qstr; + path = qstr != "undefined" ? qstr : ""; } return { "path": path, From dd15403f086bd483d0a488999ae177cbc4cc4ac6 Mon Sep 17 00:00:00 2001 From: Laura Schlimmer Date: Tue, 18 Nov 2014 14:08:38 +0000 Subject: [PATCH 05/15] test and revise setURLQueryString --- .../fnordmetric-webui-unittests.js | 36 +++++++++++++++ fnordmetric-webui/fnordmetric-webui-util.js | 46 ++++++++++++------- 2 files changed, 66 insertions(+), 16 deletions(-) diff --git a/fnordmetric-webui/fnordmetric-webui-unittests.js b/fnordmetric-webui/fnordmetric-webui-unittests.js index 937b6ffc1..82d7a5038 100644 --- a/fnordmetric-webui/fnordmetric-webui-unittests.js +++ b/fnordmetric-webui/fnordmetric-webui-unittests.js @@ -26,6 +26,7 @@ FnordMetric.UnitTests = function() { var testParseQueryString = function() { function test(querystr, expected) { + results++; var result = FnordMetric.util.parseQueryString(querystr); result = JSON.stringify(result); expected = JSON.stringify(expected); @@ -52,4 +53,39 @@ FnordMetric.UnitTests = function() { test("undefined", {path: "", query_params:{}}); }(); + var testSetUrlQueryString = function() { + function test(hash, query_params, encode, push_state, expected) { + results++; + FnordMetric.util.setURLQueryString(hash, query_params, encode, push_state); + var result = window.location.hash; + if (result != expected) { + results.bad++; + console.log("Testing setURLQueryString expected " + + expected + ", but was " + result); + } + } + test("metric_list", {}, false, false, "#metric_list"); + test(undefined, + {innerView : "metric", + innerViewValue : "http_status_codes"}, + false, + false, + ""); + + test("metric_list", + {innerView : undefined, + innerViewValue : "http_status_codes"}, + false, + false, + "#metric_list"); + + test("query_playground", + {innerView : "sql_query", + innerViewValue : "SELECT * FROM `http_status_codes`;"}, + true, + true, + "#query_playground?sql_query=SELECT%20*%20FROM%20%60http_status_codes%60%3B"); + test("", {}, false, false, ""); + }(); + } diff --git a/fnordmetric-webui/fnordmetric-webui-util.js b/fnordmetric-webui/fnordmetric-webui-util.js index 11f47a1ca..a7bc85852 100644 --- a/fnordmetric-webui/fnordmetric-webui-util.js +++ b/fnordmetric-webui/fnordmetric-webui-util.js @@ -57,23 +57,37 @@ FnordMetric.util.parseQueryString = function(qstr) { }; } +/** + * builds a querystring from the query_params, attachs it to the hash + * and sets the url + * @param query_params should be like query_params in parseQueryString + * @param encode (boolean) determines if innerViewValue should be URIencoded + * @param push_state (boolena) determines if the url should be + * added to the browser's history + */ FnordMetric.util.setURLQueryString = function(hash, query_params, encode, push_state) { - var path = "#"+hash+"?"; - - path += query_params.innerView + "="; - path += (encode)? - encodeURIComponent(query_params.innerViewValue) : - query_params.innerViewValue; - - for (var param in query_params) { - if (param != "innerView" && - param != "innerViewValue" && - query_params[param] != undefined && - query_params[param].length > 0) { - - path += - "&" + param + - "=" + query_params[param]; + if (hash === undefined || hash === "undefined") { + window.location.hash = ""; + return; + } + var path = "#" + hash; + + if ("innerView" in query_params && query_params.innerView != undefined) { + path += "?" + query_params.innerView + "="; + path += (encode)? + encodeURIComponent(query_params.innerViewValue) : + query_params.innerViewValue; + + for (var param in query_params) { + if (param != "innerView" && + param != "innerViewValue" && + query_params[param] != undefined && + query_params[param].length > 0) { + + path += + "&" + param + + "=" + query_params[param]; + } } } From 7d72910daf9e3a52b3b199829ba853eb7c09802f Mon Sep 17 00:00:00 2001 From: Laura Schlimmer Date: Tue, 18 Nov 2014 14:11:35 +0000 Subject: [PATCH 06/15] remove setFragmentURL which wasn't used anymore --- fnordmetric-webui/fnordmetric-webui-util.js | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/fnordmetric-webui/fnordmetric-webui-util.js b/fnordmetric-webui/fnordmetric-webui-util.js index a7bc85852..166f77704 100644 --- a/fnordmetric-webui/fnordmetric-webui-util.js +++ b/fnordmetric-webui/fnordmetric-webui-util.js @@ -97,21 +97,6 @@ FnordMetric.util.setURLQueryString = function(hash, query_params, encode, push_s window.location.hash = path; } -//REMOVE?? -FnordMetric.util.setFragmentURL = function(hash, name, value, encode, push_state) { - var path = window.location.pathname; - var value = value; - if (encode == true) { - value = encodeURIComponent(value); - } - var hash = - path + "#" + hash + "?" + name + "=" + value; - window.location = hash; - if (push_state == true) { - window.history.pushState({url: hash}, "#", hash); - } -} - FnordMetric.util.convertArrayToString = function(array) { var string = ""; From e2745daecf841e54e77b9b6e7c988c4c4276c26f Mon Sep 17 00:00:00 2001 From: Laura Schlimmer Date: Tue, 18 Nov 2014 14:16:24 +0000 Subject: [PATCH 07/15] remove useless function --- fnordmetric-webui/fnordmetric-webui-metriclist.js | 3 +-- fnordmetric-webui/fnordmetric-webui-util.js | 7 ------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/fnordmetric-webui/fnordmetric-webui-metriclist.js b/fnordmetric-webui/fnordmetric-webui-metriclist.js index 7920bc16d..0b3e6e9f9 100644 --- a/fnordmetric-webui/fnordmetric-webui-metriclist.js +++ b/fnordmetric-webui/fnordmetric-webui-metriclist.js @@ -79,8 +79,7 @@ FnordMetric.views.MetricList = function() { for (i in metrics) { table_view.addRow([ metrics[i]["key"], - FnordMetric.util.convertArrayToString( - metrics[i]["labels"]), + metrics[i]["labels"].join(", "), FnordMetric.util.parseTimestamp( metrics[i]["last_insert"]), metrics[i]["total_bytes"]]); diff --git a/fnordmetric-webui/fnordmetric-webui-util.js b/fnordmetric-webui/fnordmetric-webui-util.js index 166f77704..b9f0d7847 100644 --- a/fnordmetric-webui/fnordmetric-webui-util.js +++ b/fnordmetric-webui/fnordmetric-webui-util.js @@ -98,13 +98,6 @@ FnordMetric.util.setURLQueryString = function(hash, query_params, encode, push_s } -FnordMetric.util.convertArrayToString = function(array) { - var string = ""; - if (array.length > 0) { - string = array.join(", "); - } - return string; -} /* simple loader foreground */ FnordMetric.util.displayLoader = function(elem) { From fe59d9c0942c26bdad907b720589ff79a2726fca Mon Sep 17 00:00:00 2001 From: Laura Schlimmer Date: Tue, 18 Nov 2014 16:48:56 +0000 Subject: [PATCH 08/15] test parseTimestamp and remove unused functions --- .../fnordmetric-webui-unittests.js | 22 +++++++++++-- fnordmetric-webui/fnordmetric-webui-util.js | 31 ++++++++++++------- 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/fnordmetric-webui/fnordmetric-webui-unittests.js b/fnordmetric-webui/fnordmetric-webui-unittests.js index 82d7a5038..222390846 100644 --- a/fnordmetric-webui/fnordmetric-webui-unittests.js +++ b/fnordmetric-webui/fnordmetric-webui-unittests.js @@ -26,7 +26,7 @@ FnordMetric.UnitTests = function() { var testParseQueryString = function() { function test(querystr, expected) { - results++; + results.total++; var result = FnordMetric.util.parseQueryString(querystr); result = JSON.stringify(result); expected = JSON.stringify(expected); @@ -55,7 +55,7 @@ FnordMetric.UnitTests = function() { var testSetUrlQueryString = function() { function test(hash, query_params, encode, push_state, expected) { - results++; + results.total++; FnordMetric.util.setURLQueryString(hash, query_params, encode, push_state); var result = window.location.hash; if (result != expected) { @@ -88,4 +88,22 @@ FnordMetric.UnitTests = function() { test("", {}, false, false, ""); }(); + var testParseTimestamp = function() { + function test(timestamp, expected) { + results.total++; + var result = FnordMetric.util.parseTimestamp(timestamp); + if (result.substr(0,13) != expected) { + results.bad++; + console.log("Testing parseTimestamp expected " + + expected + ", but was " + result.substr(0,12)); + } + } + var now = Date.now(); + test(now, "0 seconds ago"); + test(now *1000, "0 seconds ago") + test(Math.floor(now / 1000), "0 seconds ago"); + }(); + + console.log(results.total + " tests, "+ results.bad + " failed."); + } diff --git a/fnordmetric-webui/fnordmetric-webui-util.js b/fnordmetric-webui/fnordmetric-webui-util.js index b9f0d7847..156288182 100644 --- a/fnordmetric-webui/fnordmetric-webui-util.js +++ b/fnordmetric-webui/fnordmetric-webui-util.js @@ -145,8 +145,6 @@ FnordMetric.util.displayErrorMessage = function(elem, msg) { } - - FnordMetric.util.renderPageHeader = function(text, elem) { var header = document.createElement("h1"); header.className = "page_header"; @@ -155,13 +153,19 @@ FnordMetric.util.renderPageHeader = function(text, elem) { elem.appendChild(header); } +/** + * creates a time description like + * '2 hours ago - Nov 8 2014 11:33:11 + * @param timestamp unix ts in seconds, milli or microseconds + */ FnordMetric.util.parseTimestamp = function(timestamp) { if (timestamp == 0) { return "0"; } + var timestamp = + FnordMetric.util.convertToMilliTS(timestamp); var time_str; - var timestamp = timestamp / 1000; var now = Date.now(); var date = new Date(timestamp); @@ -208,6 +212,18 @@ FnordMetric.util.parseTimestamp = function(timestamp) { return time_str; } +//FIXLAURA check all cases +FnordMetric.util.convertToMilliTS = function(ts) { + var length = ts.toString().length; + if (length == 16) { + return (ts/1000); + } else if (length < 13 && length >= 10) { + return (ts * 1000); + } else { + return ts; + } +} + FnordMetric.util.parseMilliTS = function(ts) { if (ts < 1000) { if (ts == 0) { @@ -233,14 +249,7 @@ FnordMetric.util.parseMilliTS = function(ts) { return (ts + (ts == 1? " hour" : " hours")); } -FnordMetric.util.humanDateToMikroTS = function(date) { - /* first version until datepicker is implemented */ - var ts; - if (date == "NOW") { - ts = Date.now(); - } - return ts; -} + FnordMetric.util.humanCountRows = function(tables) { From cf27a1d28c818f3ee2b7ed17a7c71aab6b16c7ca Mon Sep 17 00:00:00 2001 From: Laura Schlimmer Date: Tue, 18 Nov 2014 18:27:39 +0000 Subject: [PATCH 09/15] test, fix and remove unused functions --- .../fnordmetric-webui-unittests.js | 47 +++++++++++++++++++ fnordmetric-webui/fnordmetric-webui-util.js | 22 ++++++--- 2 files changed, 63 insertions(+), 6 deletions(-) diff --git a/fnordmetric-webui/fnordmetric-webui-unittests.js b/fnordmetric-webui/fnordmetric-webui-unittests.js index 222390846..90e0192af 100644 --- a/fnordmetric-webui/fnordmetric-webui-unittests.js +++ b/fnordmetric-webui/fnordmetric-webui-unittests.js @@ -104,6 +104,53 @@ FnordMetric.UnitTests = function() { test(Math.floor(now / 1000), "0 seconds ago"); }(); + var testSortMetricList = function() { + function test(metrics, column_index, order, expected) { + results.total++; + FnordMetric.util.sortMetricList(metrics, column_index, order); + var result = metrics; + if (result[0][column_index] != expected[0][column_index]) { + results.bad++; + console.log("Testing sortMetricList expected " + + expected[0][column_index] + ", but was " + result[0][column_index]); + } + } + var metric1 = [ + "/osx/load_avg_15m", "host", "5 hours ago - Nov 18 2014 11:33:11", 10693668]; + var metric2 = [ + "/osx/load_avg_5m", "", "5 hours ago - Nov 18 2014 11:33:12", 10693669]; + + test([metric1, metric2], 0, "asc", [metric1, metric2]); + test([metric1, metric2], 0, "desc", [metric2, metric1]); + test([metric1, metric1], 0, "asc", [metric1, metric1]); + test([metric1, metric2], 2, "desc", [metric2, metric1]); + test([metric1, metric2], 2, "asc", [metric1, metric2]); + test([metric1, metric2], 1, "desc", [metric1, metric2]); + + }(); + + var testSearchMetricList = function() { + function test(metrics, search_item, expected) { + results.total++; + var result = FnordMetric.util.searchMetricList(metrics, search_item); + for (var i = 0; i < result.length; i++) { + if (JSON.stringify(result[i]) != JSON.stringify(expected[i])) { + results.bad++; + console.log("Testing setURLQueryString expected " + + expected[i] + ", but was " + result[i]); + } + } + } + var m1 = {key : "/osx/load_avg_15m", labels : "", last_insert : 123}; + var m2 = {key : "/osx/load 15m", labels : "" }; + var m3 = {key : undefined, labels : ""}; + var metrics = [m1, m2, m3]; + + test(metrics, "15", [m1, m2]); + test(metrics, 15, [m1, m2]); + + }(); + console.log(results.total + " tests, "+ results.bad + " failed."); } diff --git a/fnordmetric-webui/fnordmetric-webui-util.js b/fnordmetric-webui/fnordmetric-webui-util.js index 156288182..87f02edb6 100644 --- a/fnordmetric-webui/fnordmetric-webui-util.js +++ b/fnordmetric-webui/fnordmetric-webui-util.js @@ -225,6 +225,7 @@ FnordMetric.util.convertToMilliTS = function(ts) { } FnordMetric.util.parseMilliTS = function(ts) { + var ts = FnordMetric.util.convertToMilliTS(ts); if (ts < 1000) { if (ts == 0) { return " less than 1 millisecond"; @@ -249,9 +250,6 @@ FnordMetric.util.parseMilliTS = function(ts) { return (ts + (ts == 1? " hour" : " hours")); } - - - FnordMetric.util.humanCountRows = function(tables) { var num = 0; tables.map(function(table) { @@ -260,7 +258,13 @@ FnordMetric.util.humanCountRows = function(tables) { return (num == 1? num + " row" : num + " rows") } - +/** + * sorts the metric list for a specific column + * @param metrics is an array of arrays + * @param column_index determines which 'column' + * ar array index should be sorted + * @param order can be asc or desc + */ FnordMetric.util.sortMetricList = function(metrics, column_index, order) { function compare(a, b) { if (a < b) { @@ -340,12 +344,18 @@ FnordMetric.createButton = function(href, class_name, inner_HTML) { return button; } +/** + * returns those metric objects whose key includes search_item + * @param metrics array of metric objects + */ FnordMetric.util.searchMetricList = function(metrics, search_item) { //FIXME works but seems not to be the best solution var data = []; metrics.map(function(item) { - if (item.key.indexOf(search_item) > -1) { - data.push(item); + if (item.key != undefined) { + if (item.key.indexOf(search_item) > -1) { + data.push(item); + } } }); return data; From dc82a85e7ca80d56180820c37b723945efd59af9 Mon Sep 17 00:00:00 2001 From: Laura Schlimmer Date: Wed, 19 Nov 2014 00:16:06 +0000 Subject: [PATCH 10/15] clean up and fix util functions --- fnordmetric-webui/Makefile | 1 - fnordmetric-webui/fnordmetric-webui-layout.js | 1 - .../fnordmetric-webui-metricpreviewwidget.js | 32 +++++---- .../fnordmetric-webui-unittests.js | 67 ++++++++++++++++++- fnordmetric-webui/fnordmetric-webui-util.js | 58 +++++++++++----- 5 files changed, 125 insertions(+), 34 deletions(-) diff --git a/fnordmetric-webui/Makefile b/fnordmetric-webui/Makefile index b4ad758fd..d64aabfad 100644 --- a/fnordmetric-webui/Makefile +++ b/fnordmetric-webui/Makefile @@ -24,7 +24,6 @@ build: fnordmetric-webui-embedpopup.js \ fnordmetric-webui-autocomplete.js \ fnordmetric-webui-datepicker.js \ - fnordmetric-webui-unittests.js \ codemirror.js \ codemirror-sql.js \ ) > fnordmetric-webui.js diff --git a/fnordmetric-webui/fnordmetric-webui-layout.js b/fnordmetric-webui/fnordmetric-webui-layout.js index db4198105..85ce30e48 100644 --- a/fnordmetric-webui/fnordmetric-webui-layout.js +++ b/fnordmetric-webui/fnordmetric-webui-layout.js @@ -86,7 +86,6 @@ FnordMetric.WebUI = function() { }; function openUrl(raw_url, push_state) { - FnordMetric.UnitTests(); var url = FnordMetric.util.parseQueryString(raw_url); var query_params = url["query_params"]; diff --git a/fnordmetric-webui/fnordmetric-webui-metricpreviewwidget.js b/fnordmetric-webui/fnordmetric-webui-metricpreviewwidget.js index ce5b3ea0b..0f1d7d98a 100644 --- a/fnordmetric-webui/fnordmetric-webui-metricpreviewwidget.js +++ b/fnordmetric-webui/fnordmetric-webui-metricpreviewwidget.js @@ -56,12 +56,16 @@ FnordMetric.util.MetricPreviewWidget = function(viewport, query_params) { /* checks if required url params are misssing and adds those if so */ function addRequiredURLParamsForView(value) { - if (value == "count" || value == "sum" || value == "mean") { - - var time_step = query_params.t_step; - if (time_step == undefined) { - time_step = defaults.t_step; - updateURLParams("t_step", time_step); + if (value == "count" || + value == "sum" || + value == "mean" || + value == "min" || + value == "max") { + + var time_window = query_params.t_window; + if (time_window == undefined) { + time_window = defaults.t_window; + updateURLParams("t_window", time_window); } } var group_by = query_params.by; @@ -456,10 +460,10 @@ FnordMetric.util.MetricPreviewWidget = function(viewport, query_params) { prev_timespan.addEventListener('click', function(e) { e.preventDefault(); - var end = end_time; - end_time = start_time; - var diff = end - start_time; - start_time = start_time - diff; + var end = query_params.end_time; + end_time = query_params.start_time; + var diff = end - query_params.start_time; + start_time = query_params.start_time - diff; updateURLParams("end_time", end_time); updateURLParams("start_time", start_time); updateDateTimeElems( @@ -469,11 +473,13 @@ FnordMetric.util.MetricPreviewWidget = function(viewport, query_params) { next_timespan.addEventListener('click', function(e) { e.preventDefault(); - var start = start_time; - var end = parseInt(end_time ,10) + (end_time - start); + var start = query_params.start_time; + var end = + parseInt(query_params.end_time ,10) + + (query_params.end_time - start); if (end <= now) { - start_time = end_time; + start_time = query_params.end_time; end_time = end; updateURLParams("start_time", start_time); updateURLParams("end_time", end_time); diff --git a/fnordmetric-webui/fnordmetric-webui-unittests.js b/fnordmetric-webui/fnordmetric-webui-unittests.js index 90e0192af..b25b3f9c3 100644 --- a/fnordmetric-webui/fnordmetric-webui-unittests.js +++ b/fnordmetric-webui/fnordmetric-webui-unittests.js @@ -151,6 +151,71 @@ FnordMetric.UnitTests = function() { }(); - console.log(results.total + " tests, "+ results.bad + " failed."); + var testGenerateSQLQueryFromParams = function() { + function test(params, expected) { + var result = FnordMetric.util.generateSQLQueryFromParams(params); + result = result.replace(/(\n\r|\n|\r|\s)/gm, ""); + if (result != expected) { + results.bad++; + console.log("Testing generateSQLQueryFromParams expected " + + expected + ", but was " + result); + } + } + // test XDOMAIN and WHERE clause + test( + {innerView : "metric", + innerViewValue : "status_codes", + view : "value", + by : "host", + columns : "host", + end_time : "1416263880000", + start_time : "1416260280000"}, + "DRAWLINECHARTXDOMAINFROM_TIMESTAMP(1416260280),FROM_TIMESTAMP(1416263880)" + + "AXISBOTTOMAXISLEFTLEGENDTOPRIGHTINSIDE;SELECTtimeASx,valueASy,hostASseriesFROM`status_codes`" + + "WHEREtime>FROM_TIMESTAMP(1416260280)ANDtime 0)? - ("`" + columns[0] + "`") : "'total'"; + var column; + if (columns != undefined && (columns.split(","))[0].length > 0) { + columns = columns.split(",") + column = "`" + columns[0] + "`"; + } else { + /* fallback if the metric hasn't any labels */ + column = "'total'"; + } select_expr = " SELECT " + column + " AS X, " + func + "(value) AS Y"; @@ -452,11 +469,17 @@ FnordMetric.util.generateSQLQueryFromParams = function(params) { select_expr += ", " + series + " AS series"; } + /* complete draw stm */ + draw_stm += + "\n AXIS BOTTOM\n AXIS LEFT\n" + + " LEGEND TOP RIGHT INSIDE;\n\n"; + + /* complete from_expr */ from_expr += " `" + table_ref + "`\n"; /*complete where_expr */ - if (start_time != undefined && end_time != undefined) { + if (!isNaN(start_time) && !isNaN(end_time)) { where_expr = " WHERE\n time > FROM_TIMESTAMP(" + start_time + ")\n" + " AND time < FROM_TIMESTAMP(" + end_time + ")"; @@ -468,14 +491,14 @@ FnordMetric.util.generateSQLQueryFromParams = function(params) { group_expr = " GROUP "; var hasGroupStm = false; - if (t_step != undefined) { + if (t_window != undefined) { hasGroupStm = true; group_expr += - "OVER TIMEWINDOW(time, " + Math.round(t_step / 1000) + ","; + "OVER TIMEWINDOW(time, " + Math.round(t_window / 1000) + ","; - group_expr += (t_window != undefined)? - Math.round(t_window / 1000) : Math.round(t_step / 1000); + group_expr += (t_step != undefined)? + Math.round(t_step / 1000) : Math.round(t_window / 1000); group_expr+= ")"; @@ -494,7 +517,7 @@ FnordMetric.util.generateSQLQueryFromParams = function(params) { } - query = + query = draw_stm + select_expr + from_expr + where_expr + group_expr + ";"; @@ -553,7 +576,6 @@ FnordMetric.util.validatedTimeInput = function (time_input, type) { e.preventDefault(); } } else if (input.length == 1) { - console.log(input); if (input < 2 || (input == 2 && n < 4)) { input = input * 10 + n; time_input.value += n; From ecb62296ae6c0009f1553f79c2d8bfa4671b5f2c Mon Sep 17 00:00:00 2001 From: Laura Schlimmer Date: Wed, 19 Nov 2014 12:28:43 +0000 Subject: [PATCH 11/15] submit datepicker time input --- .../fnordmetric-webui-datepicker.js | 20 ++++++++++++++----- fnordmetric-webui/fnordmetric-webui-util.js | 6 +++++- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/fnordmetric-webui/fnordmetric-webui-datepicker.js b/fnordmetric-webui/fnordmetric-webui-datepicker.js index ee4b93a84..012ce9721 100644 --- a/fnordmetric-webui/fnordmetric-webui-datepicker.js +++ b/fnordmetric-webui/fnordmetric-webui-datepicker.js @@ -17,7 +17,7 @@ if (FnordMetric.views === undefined) { } /* generate html for time input and handle input */ -FnordMetric.util.timeInput = function(selectedTimestamp, elem) { +FnordMetric.util.timeInput = function(selectedTimestamp, elem, callback) { var selectedMinutes = FnordMetric.util.appendLeadingZero( selectedTimestamp.getMinutes()); @@ -45,13 +45,14 @@ FnordMetric.util.timeInput = function(selectedTimestamp, elem) { hour_input.addEventListener('focus', function(e) { e.preventDefault(); - FnordMetric.util.validatedTimeInput(this, "hour"); + FnordMetric.util.validatedTimeInput(this, "hour", callback); }, false); minute_input.addEventListener('focus', function(e) { e.preventDefault(); - FnordMetric.util.validatedTimeInput(this, "minute"); + FnordMetric.util.validatedTimeInput(this, "minute", callback); }, false); + } function getValues() { @@ -67,7 +68,7 @@ FnordMetric.util.timeInput = function(selectedTimestamp, elem) { return { "render" : render, - "getValues" : getValues + "getValues" : getValues, } } @@ -146,7 +147,15 @@ FnordMetric.util.DatePicker = function(elem, dp_input, viewport, callback) { return name; } + function onSelect(date, month, year) { + /* fallback for time inputs */ + var date = (date == undefined) ? + selectedDate : date; + var month = (month == undefined)? + selectedMonth : month; + var year = (year == undefined) ? + selectedYear : year; var inputs = timeInput.getValues(); var hours = inputs.hours; var minutes = inputs.minutes; @@ -297,7 +306,8 @@ FnordMetric.util.DatePicker = function(elem, dp_input, viewport, callback) { dp_widget.innerHTML = ""; dp_widget.className += " active"; - timeInput = FnordMetric.util.timeInput(selectedTimestamp, dp_widget); + timeInput = FnordMetric.util.timeInput( + selectedTimestamp, dp_widget, onSelect); timeInput.render(); renderCalendar(currYear, currMonth); } diff --git a/fnordmetric-webui/fnordmetric-webui-util.js b/fnordmetric-webui/fnordmetric-webui-util.js index 351281613..9030f9ef8 100644 --- a/fnordmetric-webui/fnordmetric-webui-util.js +++ b/fnordmetric-webui/fnordmetric-webui-util.js @@ -559,10 +559,14 @@ FnordMetric.util.isNavKey = function(keycode) { } -FnordMetric.util.validatedTimeInput = function (time_input, type) { +FnordMetric.util.validatedTimeInput = function (time_input, type, callback) { var input = time_input.value; time_input.addEventListener('keydown', function(e) { + if (e.keyCode == 13) { + callback(); + return; + } if (FnordMetric.util.isNumKey(e.keyCode)) { var n = String.fromCharCode(e.keyCode); input = time_input.value; From 6db19d5729c21317dbc6b842e793b0b5477572d2 Mon Sep 17 00:00:00 2001 From: Laura Schlimmer Date: Wed, 19 Nov 2014 13:22:45 +0000 Subject: [PATCH 12/15] remove window event listeners when queryeditor view is destroyed --- .../fnordmetric-webui-queryeditor.js | 18 +- fnordmetric-webui/tests/MIT.LICENSE | 20 + fnordmetric-webui/tests/SpecRunner.html | 34 + .../tests/jasmine-standalone-2.1.2.zip | Bin 0 -> 41800 bytes .../tests/lib/jasmine-2.1.2/boot.js | 120 + .../tests/lib/jasmine-2.1.2/console.js | 190 ++ .../tests/lib/jasmine-2.1.2/jasmine-html.js | 404 +++ .../tests/lib/jasmine-2.1.2/jasmine.css | 62 + .../tests/lib/jasmine-2.1.2/jasmine.js | 2909 +++++++++++++++++ .../lib/jasmine-2.1.2/jasmine_favicon.png | Bin 0 -> 1486 bytes fnordmetric-webui/tests/spec/SpecHelper.js | 15 + fnordmetric-webui/tests/spec/utilSpec.js | 665 ++++ fnordmetric-webui/tests/src/util.js | 665 ++++ 13 files changed, 5095 insertions(+), 7 deletions(-) create mode 100644 fnordmetric-webui/tests/MIT.LICENSE create mode 100644 fnordmetric-webui/tests/SpecRunner.html create mode 100644 fnordmetric-webui/tests/jasmine-standalone-2.1.2.zip create mode 100644 fnordmetric-webui/tests/lib/jasmine-2.1.2/boot.js create mode 100644 fnordmetric-webui/tests/lib/jasmine-2.1.2/console.js create mode 100644 fnordmetric-webui/tests/lib/jasmine-2.1.2/jasmine-html.js create mode 100644 fnordmetric-webui/tests/lib/jasmine-2.1.2/jasmine.css create mode 100644 fnordmetric-webui/tests/lib/jasmine-2.1.2/jasmine.js create mode 100644 fnordmetric-webui/tests/lib/jasmine-2.1.2/jasmine_favicon.png create mode 100644 fnordmetric-webui/tests/spec/SpecHelper.js create mode 100644 fnordmetric-webui/tests/spec/utilSpec.js create mode 100644 fnordmetric-webui/tests/src/util.js diff --git a/fnordmetric-webui/fnordmetric-webui-queryeditor.js b/fnordmetric-webui/fnordmetric-webui-queryeditor.js index dd83ece0a..a5c93ed03 100644 --- a/fnordmetric-webui/fnordmetric-webui-queryeditor.js +++ b/fnordmetric-webui/fnordmetric-webui-queryeditor.js @@ -126,6 +126,12 @@ FnordMetric.views.QueryPlayground = function() { } } + function beforeUnloadEvent(e) { + e.returnValue = + "You may loose your query when leaving the page."; + } + + function render(viewport, url, query_params) { direction = "horizontal"; viewport = viewport; @@ -169,13 +175,10 @@ FnordMetric.views.QueryPlayground = function() { /* set eventListeners */ - window.onbeforeunload = function(e) { - return "You may loose your query when leaving the page."; - } + window.addEventListener( + 'beforeunload', beforeUnloadEvent, false); - window.addEventListener('resize', function() { - updateLayout() - }); + window.addEventListener('resize', updateLayout); editor_pane.addEventListener('keydown', function(e) { if ((e.ctrlKey || e.metaKey) && e.keyCode == 13) { @@ -268,7 +271,8 @@ FnordMetric.views.QueryPlayground = function() { } function destroy() { - + window.removeEventListener('beforeunload', beforeUnloadEvent, false); + window.removeEventListener('resize', updateLayout); } return { diff --git a/fnordmetric-webui/tests/MIT.LICENSE b/fnordmetric-webui/tests/MIT.LICENSE new file mode 100644 index 000000000..aff8ed47a --- /dev/null +++ b/fnordmetric-webui/tests/MIT.LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2008-2014 Pivotal Labs + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/fnordmetric-webui/tests/SpecRunner.html b/fnordmetric-webui/tests/SpecRunner.html new file mode 100644 index 000000000..25eacbf3f --- /dev/null +++ b/fnordmetric-webui/tests/SpecRunner.html @@ -0,0 +1,34 @@ + + + + + Jasmine Spec Runner v2.1.2 + + + + + + + + + + + + + + + + + + + describe("A suite is just a function", function() { + var a; + + it("and so is a spec", function() { + a = true; + + expect(a).toBe(true); + }); + }); + + diff --git a/fnordmetric-webui/tests/jasmine-standalone-2.1.2.zip b/fnordmetric-webui/tests/jasmine-standalone-2.1.2.zip new file mode 100644 index 0000000000000000000000000000000000000000..c2bab5afc90e46fd4ce5f7c8dbd5d5503abd3842 GIT binary patch literal 41800 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/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 literal 0 HcmV?d00001 diff --git a/fnordmetric-webui/tests/lib/jasmine-2.1.2/boot.js b/fnordmetric-webui/tests/lib/jasmine-2.1.2/boot.js new file mode 100644 index 000000000..164f068b0 --- /dev/null +++ b/fnordmetric-webui/tests/lib/jasmine-2.1.2/boot.js @@ -0,0 +1,120 @@ +/** + Starting with version 2.0, this file "boots" Jasmine, performing all of the necessary initialization before executing the loaded environment and all of a project's specs. This file should be loaded after `jasmine.js` and `jasmine_html.js`, but before any project source files or spec files are loaded. Thus this file can also be used to customize Jasmine for a project. + + If a project is using Jasmine via the standalone distribution, this file can be customized directly. If a project is using Jasmine via the [Ruby gem][jasmine-gem], this file can be copied into the support directory via `jasmine copy_boot_js`. Other environments (e.g., Python) will have different mechanisms. + + The location of `boot.js` can be specified and/or overridden in `jasmine.yml`. + + [jasmine-gem]: http://github.com/pivotal/jasmine-gem + */ + +(function() { + + /** + * ## Require & Instantiate + * + * Require Jasmine's core files. Specifically, this requires and attaches all of Jasmine's code to the `jasmine` reference. + */ + window.jasmine = jasmineRequire.core(jasmineRequire); + + /** + * Since this is being run in a browser and the results should populate to an HTML page, require the HTML-specific Jasmine code, injecting the same reference. + */ + jasmineRequire.html(jasmine); + + /** + * Create the Jasmine environment. This is used to run all specs in a project. + */ + var env = jasmine.getEnv(); + + /** + * ## The Global Interface + * + * Build up the functions that will be exposed as the Jasmine public interface. A project can customize, rename or alias any of these functions as desired, provided the implementation remains unchanged. + */ + var jasmineInterface = jasmineRequire.interface(jasmine, env); + + /** + * Add all of the Jasmine global/public interface to the proper global, so a project can use the public interface directly. For example, calling `describe` in specs instead of `jasmine.getEnv().describe`. + */ + if (typeof window == "undefined" && typeof exports == "object") { + extend(exports, jasmineInterface); + } else { + extend(window, jasmineInterface); + } + + /** + * ## Runner Parameters + * + * More browser specific code - wrap the query string in an object and to allow for getting/setting parameters from the runner user interface. + */ + + var queryString = new jasmine.QueryString({ + getWindowLocation: function() { return window.location; } + }); + + var catchingExceptions = queryString.getParam("catch"); + env.catchExceptions(typeof catchingExceptions === "undefined" ? true : catchingExceptions); + + /** + * ## Reporters + * The `HtmlReporter` builds all of the HTML UI for the runner page. This reporter paints the dots, stars, and x's for specs, as well as all spec names and all failures (if any). + */ + var htmlReporter = new jasmine.HtmlReporter({ + env: env, + onRaiseExceptionsClick: function() { queryString.setParam("catch", !env.catchingExceptions()); }, + getContainer: function() { return document.body; }, + createElement: function() { return document.createElement.apply(document, arguments); }, + createTextNode: function() { return document.createTextNode.apply(document, arguments); }, + timer: new jasmine.Timer() + }); + + /** + * The `jsApiReporter` also receives spec results, and is used by any environment that needs to extract the results from JavaScript. + */ + env.addReporter(jasmineInterface.jsApiReporter); + env.addReporter(htmlReporter); + + /** + * Filter which specs will be run by matching the start of the full name against the `spec` query param. + */ + var specFilter = new jasmine.HtmlSpecFilter({ + filterString: function() { return queryString.getParam("spec"); } + }); + + env.specFilter = function(spec) { + return specFilter.matches(spec.getFullName()); + }; + + /** + * Setting up timing functions to be able to be overridden. Certain browsers (Safari, IE 8, phantomjs) require this hack. + */ + window.setTimeout = window.setTimeout; + window.setInterval = window.setInterval; + window.clearTimeout = window.clearTimeout; + window.clearInterval = window.clearInterval; + + /** + * ## Execution + * + * Replace the browser window's `onload`, ensure it's called, and then run all of the loaded specs. This includes initializing the `HtmlReporter` instance and then executing the loaded Jasmine environment. All of this will happen after all of the specs are loaded. + */ + var currentWindowOnload = window.onload; + + window.onload = function() { + if (currentWindowOnload) { + currentWindowOnload(); + } + htmlReporter.initialize(); + env.execute(); + }; + + /** + * Helper function for readability above. + */ + function extend(destination, source) { + for (var property in source) destination[property] = source[property]; + return destination; + } + +}()); diff --git a/fnordmetric-webui/tests/lib/jasmine-2.1.2/console.js b/fnordmetric-webui/tests/lib/jasmine-2.1.2/console.js new file mode 100644 index 000000000..a65876e91 --- /dev/null +++ b/fnordmetric-webui/tests/lib/jasmine-2.1.2/console.js @@ -0,0 +1,190 @@ +/* +Copyright (c) 2008-2014 Pivotal Labs + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +function getJasmineRequireObj() { + if (typeof module !== 'undefined' && module.exports) { + return exports; + } else { + window.jasmineRequire = window.jasmineRequire || {}; + return window.jasmineRequire; + } +} + +getJasmineRequireObj().console = function(jRequire, j$) { + j$.ConsoleReporter = jRequire.ConsoleReporter(); +}; + +getJasmineRequireObj().ConsoleReporter = function() { + + var noopTimer = { + start: function(){}, + elapsed: function(){ return 0; } + }; + + function ConsoleReporter(options) { + var print = options.print, + showColors = options.showColors || false, + onComplete = options.onComplete || function() {}, + timer = options.timer || noopTimer, + specCount, + failureCount, + failedSpecs = [], + pendingCount, + ansi = { + green: '\x1B[32m', + red: '\x1B[31m', + yellow: '\x1B[33m', + none: '\x1B[0m' + }, + failedSuites = []; + + print('ConsoleReporter is deprecated and will be removed in a future version.'); + + this.jasmineStarted = function() { + specCount = 0; + failureCount = 0; + pendingCount = 0; + print('Started'); + printNewline(); + timer.start(); + }; + + this.jasmineDone = function() { + printNewline(); + for (var i = 0; i < failedSpecs.length; i++) { + specFailureDetails(failedSpecs[i]); + } + + if(specCount > 0) { + printNewline(); + + var specCounts = specCount + ' ' + plural('spec', specCount) + ', ' + + failureCount + ' ' + plural('failure', failureCount); + + if (pendingCount) { + specCounts += ', ' + pendingCount + ' pending ' + plural('spec', pendingCount); + } + + print(specCounts); + } else { + print('No specs found'); + } + + printNewline(); + var seconds = timer.elapsed() / 1000; + print('Finished in ' + seconds + ' ' + plural('second', seconds)); + printNewline(); + + for(i = 0; i < failedSuites.length; i++) { + suiteFailureDetails(failedSuites[i]); + } + + onComplete(failureCount === 0); + }; + + this.specDone = function(result) { + specCount++; + + if (result.status == 'pending') { + pendingCount++; + print(colored('yellow', '*')); + return; + } + + if (result.status == 'passed') { + print(colored('green', '.')); + return; + } + + if (result.status == 'failed') { + failureCount++; + failedSpecs.push(result); + print(colored('red', 'F')); + } + }; + + this.suiteDone = function(result) { + if (result.failedExpectations && result.failedExpectations.length > 0) { + failureCount++; + failedSuites.push(result); + } + }; + + return this; + + function printNewline() { + print('\n'); + } + + function colored(color, str) { + return showColors ? (ansi[color] + str + ansi.none) : str; + } + + function plural(str, count) { + return count == 1 ? str : str + 's'; + } + + function repeat(thing, times) { + var arr = []; + for (var i = 0; i < times; i++) { + arr.push(thing); + } + return arr; + } + + function indent(str, spaces) { + var lines = (str || '').split('\n'); + var newArr = []; + for (var i = 0; i < lines.length; i++) { + newArr.push(repeat(' ', spaces).join('') + lines[i]); + } + return newArr.join('\n'); + } + + function specFailureDetails(result) { + printNewline(); + print(result.fullName); + + for (var i = 0; i < result.failedExpectations.length; i++) { + var failedExpectation = result.failedExpectations[i]; + printNewline(); + print(indent(failedExpectation.message, 2)); + print(indent(failedExpectation.stack, 2)); + } + + printNewline(); + } + + function suiteFailureDetails(result) { + for (var i = 0; i < result.failedExpectations.length; i++) { + printNewline(); + print(colored('red', 'An error was thrown in an afterAll')); + printNewline(); + print(colored('red', 'AfterAll ' + result.failedExpectations[i].message)); + + } + printNewline(); + } + } + + return ConsoleReporter; +}; diff --git a/fnordmetric-webui/tests/lib/jasmine-2.1.2/jasmine-html.js b/fnordmetric-webui/tests/lib/jasmine-2.1.2/jasmine-html.js new file mode 100644 index 000000000..898108b77 --- /dev/null +++ b/fnordmetric-webui/tests/lib/jasmine-2.1.2/jasmine-html.js @@ -0,0 +1,404 @@ +/* +Copyright (c) 2008-2014 Pivotal Labs + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +jasmineRequire.html = function(j$) { + j$.ResultsNode = jasmineRequire.ResultsNode(); + j$.HtmlReporter = jasmineRequire.HtmlReporter(j$); + j$.QueryString = jasmineRequire.QueryString(); + j$.HtmlSpecFilter = jasmineRequire.HtmlSpecFilter(); +}; + +jasmineRequire.HtmlReporter = function(j$) { + + var noopTimer = { + start: function() {}, + elapsed: function() { return 0; } + }; + + function HtmlReporter(options) { + var env = options.env || {}, + getContainer = options.getContainer, + createElement = options.createElement, + createTextNode = options.createTextNode, + onRaiseExceptionsClick = options.onRaiseExceptionsClick || function() {}, + timer = options.timer || noopTimer, + results = [], + specsExecuted = 0, + failureCount = 0, + pendingSpecCount = 0, + htmlReporterMain, + symbols, + failedSuites = []; + + this.initialize = function() { + clearPrior(); + htmlReporterMain = createDom('div', {className: 'jasmine_html-reporter'}, + createDom('div', {className: 'banner'}, + createDom('a', {className: 'title', href: 'http://jasmine.github.io/', target: '_blank'}), + createDom('span', {className: 'version'}, j$.version) + ), + createDom('ul', {className: 'symbol-summary'}), + createDom('div', {className: 'alert'}), + createDom('div', {className: 'results'}, + createDom('div', {className: 'failures'}) + ) + ); + getContainer().appendChild(htmlReporterMain); + + symbols = find('.symbol-summary'); + }; + + var totalSpecsDefined; + this.jasmineStarted = function(options) { + totalSpecsDefined = options.totalSpecsDefined || 0; + timer.start(); + }; + + var summary = createDom('div', {className: 'summary'}); + + var topResults = new j$.ResultsNode({}, '', null), + currentParent = topResults; + + this.suiteStarted = function(result) { + currentParent.addChild(result, 'suite'); + currentParent = currentParent.last(); + }; + + this.suiteDone = function(result) { + if (result.status == 'failed') { + failedSuites.push(result); + } + + if (currentParent == topResults) { + return; + } + + currentParent = currentParent.parent; + }; + + this.specStarted = function(result) { + currentParent.addChild(result, 'spec'); + }; + + var failures = []; + this.specDone = function(result) { + if(noExpectations(result) && typeof console !== 'undefined' && typeof console.error !== 'undefined') { + console.error('Spec \'' + result.fullName + '\' has no expectations.'); + } + + if (result.status != 'disabled') { + specsExecuted++; + } + + symbols.appendChild(createDom('li', { + className: noExpectations(result) ? 'empty' : result.status, + id: 'spec_' + result.id, + title: result.fullName + } + )); + + if (result.status == 'failed') { + failureCount++; + + var failure = + createDom('div', {className: 'spec-detail failed'}, + createDom('div', {className: 'description'}, + createDom('a', {title: result.fullName, href: specHref(result)}, result.fullName) + ), + createDom('div', {className: 'messages'}) + ); + var messages = failure.childNodes[1]; + + for (var i = 0; i < result.failedExpectations.length; i++) { + var expectation = result.failedExpectations[i]; + messages.appendChild(createDom('div', {className: 'result-message'}, expectation.message)); + messages.appendChild(createDom('div', {className: 'stack-trace'}, expectation.stack)); + } + + failures.push(failure); + } + + if (result.status == 'pending') { + pendingSpecCount++; + } + }; + + this.jasmineDone = function() { + var banner = find('.banner'); + banner.appendChild(createDom('span', {className: 'duration'}, 'finished in ' + timer.elapsed() / 1000 + 's')); + + var alert = find('.alert'); + + alert.appendChild(createDom('span', { className: 'exceptions' }, + createDom('label', { className: 'label', 'for': 'raise-exceptions' }, 'raise exceptions'), + createDom('input', { + className: 'raise', + id: 'raise-exceptions', + type: 'checkbox' + }) + )); + var checkbox = find('#raise-exceptions'); + + checkbox.checked = !env.catchingExceptions(); + checkbox.onclick = onRaiseExceptionsClick; + + if (specsExecuted < totalSpecsDefined) { + var skippedMessage = 'Ran ' + specsExecuted + ' of ' + totalSpecsDefined + ' specs - run all'; + alert.appendChild( + createDom('span', {className: 'bar skipped'}, + createDom('a', {href: '?', title: 'Run all specs'}, skippedMessage) + ) + ); + } + var statusBarMessage = ''; + var statusBarClassName = 'bar '; + + if (totalSpecsDefined > 0) { + statusBarMessage += pluralize('spec', specsExecuted) + ', ' + pluralize('failure', failureCount); + if (pendingSpecCount) { statusBarMessage += ', ' + pluralize('pending spec', pendingSpecCount); } + statusBarClassName += (failureCount > 0) ? 'failed' : 'passed'; + } else { + statusBarClassName += 'skipped'; + statusBarMessage += 'No specs found'; + } + + alert.appendChild(createDom('span', {className: statusBarClassName}, statusBarMessage)); + + for(i = 0; i < failedSuites.length; i++) { + var failedSuite = failedSuites[i]; + for(var j = 0; j < failedSuite.failedExpectations.length; j++) { + var errorBarMessage = 'AfterAll ' + failedSuite.failedExpectations[j].message; + var errorBarClassName = 'bar errored'; + alert.appendChild(createDom('span', {className: errorBarClassName}, errorBarMessage)); + } + } + + var results = find('.results'); + results.appendChild(summary); + + summaryList(topResults, summary); + + function summaryList(resultsTree, domParent) { + var specListNode; + for (var i = 0; i < resultsTree.children.length; i++) { + var resultNode = resultsTree.children[i]; + if (resultNode.type == 'suite') { + var suiteListNode = createDom('ul', {className: 'suite', id: 'suite-' + resultNode.result.id}, + createDom('li', {className: 'suite-detail'}, + createDom('a', {href: specHref(resultNode.result)}, resultNode.result.description) + ) + ); + + summaryList(resultNode, suiteListNode); + domParent.appendChild(suiteListNode); + } + if (resultNode.type == 'spec') { + if (domParent.getAttribute('class') != 'specs') { + specListNode = createDom('ul', {className: 'specs'}); + domParent.appendChild(specListNode); + } + var specDescription = resultNode.result.description; + if(noExpectations(resultNode.result)) { + specDescription = 'SPEC HAS NO EXPECTATIONS ' + specDescription; + } + specListNode.appendChild( + createDom('li', { + className: resultNode.result.status, + id: 'spec-' + resultNode.result.id + }, + createDom('a', {href: specHref(resultNode.result)}, specDescription) + ) + ); + } + } + } + + if (failures.length) { + alert.appendChild( + createDom('span', {className: 'menu bar spec-list'}, + createDom('span', {}, 'Spec List | '), + createDom('a', {className: 'failures-menu', href: '#'}, 'Failures'))); + alert.appendChild( + createDom('span', {className: 'menu bar failure-list'}, + createDom('a', {className: 'spec-list-menu', href: '#'}, 'Spec List'), + createDom('span', {}, ' | Failures '))); + + find('.failures-menu').onclick = function() { + setMenuModeTo('failure-list'); + }; + find('.spec-list-menu').onclick = function() { + setMenuModeTo('spec-list'); + }; + + setMenuModeTo('failure-list'); + + var failureNode = find('.failures'); + for (var i = 0; i < failures.length; i++) { + failureNode.appendChild(failures[i]); + } + } + }; + + return this; + + function find(selector) { + return getContainer().querySelector('.jasmine_html-reporter ' + selector); + } + + function clearPrior() { + // return the reporter + var oldReporter = find(''); + + if(oldReporter) { + getContainer().removeChild(oldReporter); + } + } + + function createDom(type, attrs, childrenVarArgs) { + var el = createElement(type); + + for (var i = 2; i < arguments.length; i++) { + var child = arguments[i]; + + if (typeof child === 'string') { + el.appendChild(createTextNode(child)); + } else { + if (child) { + el.appendChild(child); + } + } + } + + for (var attr in attrs) { + if (attr == 'className') { + el[attr] = attrs[attr]; + } else { + el.setAttribute(attr, attrs[attr]); + } + } + + return el; + } + + function pluralize(singular, count) { + var word = (count == 1 ? singular : singular + 's'); + + return '' + count + ' ' + word; + } + + function specHref(result) { + return '?spec=' + encodeURIComponent(result.fullName); + } + + function setMenuModeTo(mode) { + htmlReporterMain.setAttribute('class', 'jasmine_html-reporter ' + mode); + } + + function noExpectations(result) { + return (result.failedExpectations.length + result.passedExpectations.length) === 0 && + result.status === 'passed'; + } + } + + return HtmlReporter; +}; + +jasmineRequire.HtmlSpecFilter = function() { + function HtmlSpecFilter(options) { + var filterString = options && options.filterString() && options.filterString().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); + var filterPattern = new RegExp(filterString); + + this.matches = function(specName) { + return filterPattern.test(specName); + }; + } + + return HtmlSpecFilter; +}; + +jasmineRequire.ResultsNode = function() { + function ResultsNode(result, type, parent) { + this.result = result; + this.type = type; + this.parent = parent; + + this.children = []; + + this.addChild = function(result, type) { + this.children.push(new ResultsNode(result, type, this)); + }; + + this.last = function() { + return this.children[this.children.length - 1]; + }; + } + + return ResultsNode; +}; + +jasmineRequire.QueryString = function() { + function QueryString(options) { + + this.setParam = function(key, value) { + var paramMap = queryStringToParamMap(); + paramMap[key] = value; + options.getWindowLocation().search = toQueryString(paramMap); + }; + + this.getParam = function(key) { + return queryStringToParamMap()[key]; + }; + + return this; + + function toQueryString(paramMap) { + var qStrPairs = []; + for (var prop in paramMap) { + qStrPairs.push(encodeURIComponent(prop) + '=' + encodeURIComponent(paramMap[prop])); + } + return '?' + qStrPairs.join('&'); + } + + function queryStringToParamMap() { + var paramStr = options.getWindowLocation().search.substring(1), + params = [], + paramMap = {}; + + if (paramStr.length > 0) { + params = paramStr.split('&'); + for (var i = 0; i < params.length; i++) { + var p = params[i].split('='); + var value = decodeURIComponent(p[1]); + if (value === 'true' || value === 'false') { + value = JSON.parse(value); + } + paramMap[decodeURIComponent(p[0])] = value; + } + } + + return paramMap; + } + + } + + return QueryString; +}; diff --git a/fnordmetric-webui/tests/lib/jasmine-2.1.2/jasmine.css b/fnordmetric-webui/tests/lib/jasmine-2.1.2/jasmine.css new file mode 100644 index 000000000..ecc5f5e7b --- /dev/null +++ b/fnordmetric-webui/tests/lib/jasmine-2.1.2/jasmine.css @@ -0,0 +1,62 @@ +body { overflow-y: scroll; } + +.jasmine_html-reporter { background-color: #eee; padding: 5px; margin: -8px; font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333; } +.jasmine_html-reporter a { text-decoration: none; } +.jasmine_html-reporter a:hover { text-decoration: underline; } +.jasmine_html-reporter p, .jasmine_html-reporter h1, .jasmine_html-reporter h2, .jasmine_html-reporter h3, .jasmine_html-reporter h4, .jasmine_html-reporter h5, .jasmine_html-reporter h6 { margin: 0; line-height: 14px; } +.jasmine_html-reporter .banner, .jasmine_html-reporter .symbol-summary, .jasmine_html-reporter .summary, .jasmine_html-reporter .result-message, .jasmine_html-reporter .spec .description, .jasmine_html-reporter .spec-detail .description, .jasmine_html-reporter .alert .bar, .jasmine_html-reporter .stack-trace { padding-left: 9px; padding-right: 9px; } +.jasmine_html-reporter .banner { position: relative; } +.jasmine_html-reporter .banner .title { background: url('data:image/png;base64,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') no-repeat; background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgoKPHN2ZwogICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiCiAgIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyIKICAgeG1sbnM6c3ZnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgdmVyc2lvbj0iMS4xIgogICB3aWR0aD0iNjgxLjk2MjUyIgogICBoZWlnaHQ9IjE4Ny41IgogICBpZD0ic3ZnMiIKICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PG1ldGFkYXRhCiAgICAgaWQ9Im1ldGFkYXRhOCI+PHJkZjpSREY+PGNjOldvcmsKICAgICAgICAgcmRmOmFib3V0PSIiPjxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PjxkYzp0eXBlCiAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz48L2NjOldvcms+PC9yZGY6UkRGPjwvbWV0YWRhdGE+PGRlZnMKICAgICBpZD0iZGVmczYiPjxjbGlwUGF0aAogICAgICAgaWQ9ImNsaXBQYXRoMTgiPjxwYXRoCiAgICAgICAgIGQ9Ik0gMCwxNTAwIDAsMCBsIDU0NTUuNzQsMCAwLDE1MDAgTCAwLDE1MDAgeiIKICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgaWQ9InBhdGgyMCIgLz48L2NsaXBQYXRoPjwvZGVmcz48ZwogICAgIHRyYW5zZm9ybT0ibWF0cml4KDEuMjUsMCwwLC0xLjI1LDAsMTg3LjUpIgogICAgIGlkPSJnMTAiPjxnCiAgICAgICB0cmFuc2Zvcm09InNjYWxlKDAuMSwwLjEpIgogICAgICAgaWQ9ImcxMiI+PGcKICAgICAgICAgaWQ9ImcxNCI+PGcKICAgICAgICAgICBjbGlwLXBhdGg9InVybCgjY2xpcFBhdGgxOCkiCiAgICAgICAgICAgaWQ9ImcxNiI+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gMTU0NCw1OTkuNDM0IGMgMC45MiwtNDAuMzUyIDI1LjY4LC04MS42MDIgNzEuNTMsLTgxLjYwMiAyNy41MSwwIDQ3LjY4LDEyLjgzMiA2MS40NCwzNS43NTQgMTIuODMsMjIuOTMgMTIuODMsNTYuODUyIDEyLjgzLDgyLjUyNyBsIDAsMzI5LjE4NCAtNzEuNTIsMCAwLDEwNC41NDMgMjY2LjgzLDAgMCwtMTA0LjU0MyAtNzAuNiwwIDAsLTM0NC43NyBjIDAsLTU4LjY5MSAtMy42OCwtMTA0LjUzMSAtNDQuOTMsLTE1Mi4yMTggLTM2LjY4LC00Mi4xOCAtOTYuMjgsLTY2LjAyIC0xNTMuMTQsLTY2LjAyIC0xMTcuMzcsMCAtMjA3LjI0LDc3Ljk0MSAtMjAyLjY0LDE5Ny4xNDUgbCAxMzAuMiwwIgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMjIiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDIzMDEuNCw2NjIuNjk1IGMgMCw4MC43MDMgLTY2Ljk0LDE0NS44MTMgLTE0Ny42MywxNDUuODEzIC04My40NCwwIC0xNDcuNjMsLTY4Ljc4MSAtMTQ3LjYzLC0xNTEuMzAxIDAsLTc5Ljc4NSA2Ni45NCwtMTQ1LjgwMSAxNDUuOCwtMTQ1LjgwMSA4NC4zNSwwIDE0OS40Niw2Ny44NTIgMTQ5LjQ2LDE1MS4yODkgeiBtIC0xLjgzLC0xODEuNTQ3IGMgLTM1Ljc3LC01NC4wOTcgLTkzLjUzLC03OC44NTkgLTE1Ny43MiwtNzguODU5IC0xNDAuMywwIC0yNTEuMjQsMTE2LjQ0OSAtMjUxLjI0LDI1NC45MTggMCwxNDIuMTI5IDExMy43LDI2MC40MSAyNTYuNzQsMjYwLjQxIDYzLjI3LDAgMTE4LjI5LC0yOS4zMzYgMTUyLjIyLC04Mi41MjMgbCAwLDY5LjY4NyAxNzUuMTQsMCAwLC0xMDQuNTI3IC02MS40NCwwIDAsLTI4MC41OTggNjEuNDQsMCAwLC0xMDQuNTI3IC0xNzUuMTQsMCAwLDY2LjAxOSIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDI0IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSAyNjIyLjMzLDU1Ny4yNTggYyAzLjY3LC00NC4wMTYgMzMuMDEsLTczLjM0OCA3OC44NiwtNzMuMzQ4IDMzLjkzLDAgNjYuOTMsMjMuODI0IDY2LjkzLDYwLjUwNCAwLDQ4LjYwNiAtNDUuODQsNTYuODU2IC04My40NCw2Ni45NDEgLTg1LjI4LDIyLjAwNCAtMTc4LjgxLDQ4LjYwNiAtMTc4LjgxLDE1NS44NzkgMCw5My41MzYgNzguODYsMTQ3LjYzMyAxNjUuOTgsMTQ3LjYzMyA0NCwwIDgzLjQzLC05LjE3NiAxMTAuOTQsLTQ0LjAwOCBsIDAsMzMuOTIyIDgyLjUzLDAgMCwtMTMyLjk2NSAtMTA4LjIxLDAgYyAtMS44MywzNC44NTYgLTI4LjQyLDU3Ljc3NCAtNjMuMjYsNTcuNzc0IC0zMC4yNiwwIC02Mi4zNSwtMTcuNDIyIC02Mi4zNSwtNTEuMzQ4IDAsLTQ1Ljg0NyA0NC45MywtNTUuOTMgODAuNjksLTY0LjE4IDg4LjAyLC0yMC4xNzUgMTgyLjQ3LC00Ny42OTUgMTgyLjQ3LC0xNTcuNzM0IDAsLTk5LjAyNyAtODMuNDQsLTE1NC4wMzkgLTE3NS4xMywtMTU0LjAzOSAtNDkuNTMsMCAtOTQuNDYsMTUuNTgyIC0xMjYuNTUsNTMuMTggbCAwLC00MC4zNCAtODUuMjcsMCAwLDE0Mi4xMjkgMTE0LjYyLDAiCiAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgaWQ9InBhdGgyNiIKICAgICAgICAgICAgIHN0eWxlPSJmaWxsOiM4YTQxODI7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiIC8+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gMjk4OC4xOCw4MDAuMjU0IC02My4yNiwwIDAsMTA0LjUyNyAxNjUuMDUsMCAwLC03My4zNTUgYyAzMS4xOCw1MS4zNDcgNzguODYsODUuMjc3IDE0MS4yMSw4NS4yNzcgNjcuODUsMCAxMjQuNzEsLTQxLjI1OCAxNTIuMjEsLTEwMi42OTkgMjYuNiw2Mi4zNTEgOTIuNjIsMTAyLjY5OSAxNjAuNDcsMTAyLjY5OSA1My4xOSwwIDEwNS40NiwtMjIgMTQxLjIxLC02Mi4zNTEgMzguNTIsLTQ0LjkzOCAzOC41MiwtOTMuNTMyIDM4LjUyLC0xNDkuNDU3IGwgMCwtMTg1LjIzOSA2My4yNywwIDAsLTEwNC41MjcgLTIzOC40MiwwIDAsMTA0LjUyNyA2My4yOCwwIDAsMTU3LjcxNSBjIDAsMzIuMTAyIDAsNjAuNTI3IC0xNC42Nyw4OC45NTcgLTE4LjM0LDI2LjU4MiAtNDguNjEsNDAuMzQ0IC03OS43Nyw0MC4zNDQgLTMwLjI2LDAgLTYzLjI4LC0xMi44NDQgLTgyLjUzLC0zNi42NzIgLTIyLjkzLC0yOS4zNTUgLTIyLjkzLC01Ni44NjMgLTIyLjkzLC05Mi42MjkgbCAwLC0xNTcuNzE1IDYzLjI3LDAgMCwtMTA0LjUyNyAtMjM4LjQxLDAgMCwxMDQuNTI3IDYzLjI4LDAgMCwxNTAuMzgzIGMgMCwyOS4zNDggMCw2Ni4wMjMgLTE0LjY3LDkxLjY5OSAtMTUuNTksMjkuMzM2IC00Ny42OSw0NC45MzQgLTgwLjcsNDQuOTM0IC0zMS4xOCwwIC01Ny43NywtMTEuMDA4IC03Ny45NCwtMzUuNzc0IC0yNC43NywtMzAuMjUzIC0yNi42LC02Mi4zNDMgLTI2LjYsLTk5Ljk0MSBsIDAsLTE1MS4zMDEgNjMuMjcsMCAwLC0xMDQuNTI3IC0yMzguNCwwIDAsMTA0LjUyNyA2My4yNiwwIDAsMjgwLjU5OCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDI4IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSAzOTk4LjY2LDk1MS41NDcgLTExMS44NywwIDAsMTE4LjI5MyAxMTEuODcsMCAwLC0xMTguMjkzIHogbSAwLC00MzEuODkxIDYzLjI3LDAgMCwtMTA0LjUyNyAtMjM5LjMzLDAgMCwxMDQuNTI3IDY0LjE5LDAgMCwyODAuNTk4IC02My4yNywwIDAsMTA0LjUyNyAxNzUuMTQsMCAwLC0zODUuMTI1IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMzAiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDQxNTkuMTIsODAwLjI1NCAtNjMuMjcsMCAwLDEwNC41MjcgMTc1LjE0LDAgMCwtNjkuNjg3IGMgMjkuMzUsNTQuMTAxIDg0LjM2LDgwLjY5OSAxNDQuODcsODAuNjk5IDUzLjE5LDAgMTA1LjQ1LC0yMi4wMTYgMTQxLjIyLC02MC41MjcgNDAuMzQsLTQ0LjkzNCA0MS4yNiwtODguMDMyIDQxLjI2LC0xNDMuOTU3IGwgMCwtMTkxLjY1MyA2My4yNywwIDAsLTEwNC41MjcgLTIzOC40LDAgMCwxMDQuNTI3IDYzLjI2LDAgMCwxNTguNjM3IGMgMCwzMC4yNjIgMCw2MS40MzQgLTE5LjI2LDg4LjAzNSAtMjAuMTcsMjYuNTgyIC01My4xOCwzOS40MTQgLTg2LjE5LDM5LjQxNCAtMzMuOTMsMCAtNjguNzcsLTEzLjc1IC04OC45NCwtNDEuMjUgLTIxLjA5LC0yNy41IC0yMS4wOSwtNjkuNjg3IC0yMS4wOSwtMTAyLjcwNyBsIDAsLTE0Mi4xMjkgNjMuMjYsMCAwLC0xMDQuNTI3IC0yMzguNCwwIDAsMTA0LjUyNyA2My4yNywwIDAsMjgwLjU5OCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDMyIgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA1MDgyLjQ4LDcwMy45NjUgYyAtMTkuMjQsNzAuNjA1IC04MS42LDExNS41NDcgLTE1NC4wNCwxMTUuNTQ3IC02Ni4wNCwwIC0xMjkuMywtNTEuMzQ4IC0xNDMuMDUsLTExNS41NDcgbCAyOTcuMDksMCB6IG0gODUuMjcsLTE0NC44ODMgYyAtMzguNTEsLTkzLjUyMyAtMTI5LjI3LC0xNTYuNzkzIC0yMzEuMDUsLTE1Ni43OTMgLTE0My4wNywwIC0yNTcuNjgsMTExLjg3MSAtMjU3LjY4LDI1NS44MzYgMCwxNDQuODgzIDEwOS4xMiwyNjEuMzI4IDI1NC45MSwyNjEuMzI4IDY3Ljg3LDAgMTM1LjcyLC0zMC4yNTggMTgzLjM5LC03OC44NjMgNDguNjIsLTUxLjM0NCA2OC43OSwtMTEzLjY5NSA2OC43OSwtMTgzLjM4MyBsIC0zLjY3LC0zOS40MzQgLTM5Ni4xMywwIGMgMTQuNjcsLTY3Ljg2MyA3Ny4wMywtMTE3LjM2MyAxNDYuNzIsLTExNy4zNjMgNDguNTksMCA5MC43NiwxOC4zMjggMTE4LjI4LDU4LjY3MiBsIDExNi40NCwwIgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMzQiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDY5MC44OTUsODUwLjcwMyA5MC43NSwwIDIyLjU0MywzMS4wMzUgMCwyNDMuMTIyIC0xMzUuODI5LDAgMCwtMjQzLjE0MSAyMi41MzYsLTMxLjAxNiIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDM2IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA2MzIuMzk1LDc0Mi4yNTggMjguMDM5LDg2LjMwNCAtMjIuNTUxLDMxLjA0IC0yMzEuMjIzLDc1LjEyOCAtNDEuOTc2LC0xMjkuMTgzIDIzMS4yNTcsLTc1LjEzNyAzNi40NTQsMTEuODQ4IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMzgiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDcxNy40NDksNjUzLjEwNSAtNzMuNDEsNTMuMzYgLTM2LjQ4OCwtMTEuODc1IC0xNDIuOTAzLC0xOTYuNjkyIDEwOS44ODMsLTc5LjgyOCAxNDIuOTE4LDE5Ni43MDMgMCwzOC4zMzIiCiAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgaWQ9InBhdGg0MCIKICAgICAgICAgICAgIHN0eWxlPSJmaWxsOiM4YTQxODI7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiIC8+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gODI4LjUyLDcwNi40NjUgLTczLjQyNiwtNTMuMzQgMC4wMTEsLTM4LjM1OSBMIDg5OC4wMDQsNDE4LjA3IDEwMDcuOSw0OTcuODk4IDg2NC45NzMsNjk0LjYwOSA4MjguNTIsNzA2LjQ2NSIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDQyIgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA4MTIuMDg2LDgyOC41ODYgMjguMDU1LC04Ni4zMiAzNi40ODQsLTExLjgzNiAyMzEuMjI1LDc1LjExNyAtNDEuOTcsMTI5LjE4MyAtMjMxLjIzOSwtNzUuMTQgLTIyLjU1NSwtMzEuMDA0IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoNDQiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDczNi4zMDEsMTMzNS44OCBjIC0zMjMuMDQ3LDAgLTU4NS44NzUsLTI2Mi43OCAtNTg1Ljg3NSwtNTg1Ljc4MiAwLC0zMjMuMTE4IDI2Mi44MjgsLTU4NS45NzcgNTg1Ljg3NSwtNTg1Ljk3NyAzMjMuMDE5LDAgNTg1LjgwOSwyNjIuODU5IDU4NS44MDksNTg1Ljk3NyAwLDMyMy4wMDIgLTI2Mi43OSw1ODUuNzgyIC01ODUuODA5LDU4NS43ODIgbCAwLDAgeiBtIDAsLTExOC42MSBjIDI1Ny45NzIsMCA0NjcuMTg5LC0yMDkuMTMgNDY3LjE4OSwtNDY3LjE3MiAwLC0yNTguMTI5IC0yMDkuMjE3LC00NjcuMzQ4IC00NjcuMTg5LC00NjcuMzQ4IC0yNTguMDc0LDAgLTQ2Ny4yNTQsMjA5LjIxOSAtNDY3LjI1NCw0NjcuMzQ4IDAsMjU4LjA0MiAyMDkuMTgsNDY3LjE3MiA0NjcuMjU0LDQ2Ny4xNzIiCiAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgaWQ9InBhdGg0NiIKICAgICAgICAgICAgIHN0eWxlPSJmaWxsOiM4YTQxODI7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiIC8+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gMTA5MS4xMyw2MTkuODgzIC0xNzUuNzcxLDU3LjEyMSAxMS42MjksMzUuODA4IDE3NS43NjIsLTU3LjEyMSAtMTEuNjIsLTM1LjgwOCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDQ4IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0iTSA4NjYuOTU3LDkwMi4wNzQgODM2LjUsOTI0LjE5OSA5NDUuMTIxLDEwNzMuNzMgOTc1LjU4NiwxMDUxLjYxIDg2Ni45NTcsOTAyLjA3NCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDUwIgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0iTSA2MDcuNDY1LDkwMy40NDUgNDk4Ljg1NSwxMDUyLjk3IDUyOS4zMiwxMDc1LjEgNjM3LjkzLDkyNS41NjYgNjA3LjQ2NSw5MDMuNDQ1IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoNTIiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDM4MC42ODgsNjIyLjEyOSAtMTEuNjI2LDM1LjgwMSAxNzUuNzU4LDU3LjA5IDExLjYyMSwtMzUuODAxIC0xNzUuNzUzLC01Ny4wOSIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDU0IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA3MTYuMjg5LDM3Ni41OSAzNy42NDA2LDAgMCwxODQuODE2IC0zNy42NDA2LDAgMCwtMTg0LjgxNiB6IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoNTYiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjwvZz48L2c+PC9nPjwvZz48L3N2Zz4=') no-repeat, none; -moz-background-size: 100%; -o-background-size: 100%; -webkit-background-size: 100%; background-size: 100%; display: block; float: left; width: 90px; height: 25px; } +.jasmine_html-reporter .banner .version { margin-left: 14px; position: relative; top: 6px; } +.jasmine_html-reporter .banner .duration { position: absolute; right: 14px; top: 6px; } +.jasmine_html-reporter #jasmine_content { position: fixed; right: 100%; } +.jasmine_html-reporter .version { color: #aaa; } +.jasmine_html-reporter .banner { margin-top: 14px; } +.jasmine_html-reporter .duration { color: #aaa; float: right; } +.jasmine_html-reporter .symbol-summary { overflow: hidden; *zoom: 1; margin: 14px 0; } +.jasmine_html-reporter .symbol-summary li { display: inline-block; height: 8px; width: 14px; font-size: 16px; } +.jasmine_html-reporter .symbol-summary li.passed { font-size: 14px; } +.jasmine_html-reporter .symbol-summary li.passed:before { color: #007069; content: "\02022"; } +.jasmine_html-reporter .symbol-summary li.failed { line-height: 9px; } +.jasmine_html-reporter .symbol-summary li.failed:before { color: #ca3a11; content: "\d7"; font-weight: bold; margin-left: -1px; } +.jasmine_html-reporter .symbol-summary li.disabled { font-size: 14px; } +.jasmine_html-reporter .symbol-summary li.disabled:before { color: #bababa; content: "\02022"; } +.jasmine_html-reporter .symbol-summary li.pending { line-height: 17px; } +.jasmine_html-reporter .symbol-summary li.pending:before { color: #ba9d37; content: "*"; } +.jasmine_html-reporter .symbol-summary li.empty { font-size: 14px; } +.jasmine_html-reporter .symbol-summary li.empty:before { color: #ba9d37; content: "\02022"; } +.jasmine_html-reporter .exceptions { color: #fff; float: right; margin-top: 5px; margin-right: 5px; } +.jasmine_html-reporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; } +.jasmine_html-reporter .bar.failed { background-color: #ca3a11; } +.jasmine_html-reporter .bar.passed { background-color: #007069; } +.jasmine_html-reporter .bar.skipped { background-color: #bababa; } +.jasmine_html-reporter .bar.errored { background-color: #ca3a11; } +.jasmine_html-reporter .bar.menu { background-color: #fff; color: #aaa; } +.jasmine_html-reporter .bar.menu a { color: #333; } +.jasmine_html-reporter .bar a { color: white; } +.jasmine_html-reporter.spec-list .bar.menu.failure-list, .jasmine_html-reporter.spec-list .results .failures { display: none; } +.jasmine_html-reporter.failure-list .bar.menu.spec-list, .jasmine_html-reporter.failure-list .summary { display: none; } +.jasmine_html-reporter .running-alert { background-color: #666; } +.jasmine_html-reporter .results { margin-top: 14px; } +.jasmine_html-reporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; } +.jasmine_html-reporter.showDetails .summaryMenuItem:hover { text-decoration: underline; } +.jasmine_html-reporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; } +.jasmine_html-reporter.showDetails .summary { display: none; } +.jasmine_html-reporter.showDetails #details { display: block; } +.jasmine_html-reporter .summaryMenuItem { font-weight: bold; text-decoration: underline; } +.jasmine_html-reporter .summary { margin-top: 14px; } +.jasmine_html-reporter .summary ul { list-style-type: none; margin-left: 14px; padding-top: 0; padding-left: 0; } +.jasmine_html-reporter .summary ul.suite { margin-top: 7px; margin-bottom: 7px; } +.jasmine_html-reporter .summary li.passed a { color: #007069; } +.jasmine_html-reporter .summary li.failed a { color: #ca3a11; } +.jasmine_html-reporter .summary li.empty a { color: #ba9d37; } +.jasmine_html-reporter .summary li.pending a { color: #ba9d37; } +.jasmine_html-reporter .description + .suite { margin-top: 0; } +.jasmine_html-reporter .suite { margin-top: 14px; } +.jasmine_html-reporter .suite a { color: #333; } +.jasmine_html-reporter .failures .spec-detail { margin-bottom: 28px; } +.jasmine_html-reporter .failures .spec-detail .description { background-color: #ca3a11; } +.jasmine_html-reporter .failures .spec-detail .description a { color: white; } +.jasmine_html-reporter .result-message { padding-top: 14px; color: #333; white-space: pre; } +.jasmine_html-reporter .result-message span.result { display: block; } +.jasmine_html-reporter .stack-trace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666; border: 1px solid #ddd; background: white; white-space: pre; } diff --git a/fnordmetric-webui/tests/lib/jasmine-2.1.2/jasmine.js b/fnordmetric-webui/tests/lib/jasmine-2.1.2/jasmine.js new file mode 100644 index 000000000..e4c574fc5 --- /dev/null +++ b/fnordmetric-webui/tests/lib/jasmine-2.1.2/jasmine.js @@ -0,0 +1,2909 @@ +/* +Copyright (c) 2008-2014 Pivotal Labs + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +getJasmineRequireObj = (function (jasmineGlobal) { + var jasmineRequire; + + if (typeof module !== 'undefined' && module.exports) { + jasmineGlobal = global; + jasmineRequire = exports; + } else { + jasmineRequire = jasmineGlobal.jasmineRequire = jasmineGlobal.jasmineRequire || {}; + } + + function getJasmineRequire() { + return jasmineRequire; + } + + getJasmineRequire().core = function(jRequire) { + var j$ = {}; + + jRequire.base(j$, jasmineGlobal); + j$.util = jRequire.util(); + j$.Any = jRequire.Any(); + j$.CallTracker = jRequire.CallTracker(); + j$.MockDate = jRequire.MockDate(); + j$.Clock = jRequire.Clock(); + j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler(); + j$.Env = jRequire.Env(j$); + j$.ExceptionFormatter = jRequire.ExceptionFormatter(); + j$.Expectation = jRequire.Expectation(); + j$.buildExpectationResult = jRequire.buildExpectationResult(); + j$.JsApiReporter = jRequire.JsApiReporter(); + j$.matchersUtil = jRequire.matchersUtil(j$); + j$.ObjectContaining = jRequire.ObjectContaining(j$); + j$.pp = jRequire.pp(j$); + j$.QueueRunner = jRequire.QueueRunner(j$); + j$.ReportDispatcher = jRequire.ReportDispatcher(); + j$.Spec = jRequire.Spec(j$); + j$.SpyRegistry = jRequire.SpyRegistry(j$); + j$.SpyStrategy = jRequire.SpyStrategy(); + j$.Suite = jRequire.Suite(); + j$.Timer = jRequire.Timer(); + j$.version = jRequire.version(); + + j$.matchers = jRequire.requireMatchers(jRequire, j$); + + return j$; + }; + + return getJasmineRequire; +})(this); + +getJasmineRequireObj().requireMatchers = function(jRequire, j$) { + var availableMatchers = [ + 'toBe', + 'toBeCloseTo', + 'toBeDefined', + 'toBeFalsy', + 'toBeGreaterThan', + 'toBeLessThan', + 'toBeNaN', + 'toBeNull', + 'toBeTruthy', + 'toBeUndefined', + 'toContain', + 'toEqual', + 'toHaveBeenCalled', + 'toHaveBeenCalledWith', + 'toMatch', + 'toThrow', + 'toThrowError' + ], + matchers = {}; + + for (var i = 0; i < availableMatchers.length; i++) { + var name = availableMatchers[i]; + matchers[name] = jRequire[name](j$); + } + + return matchers; +}; + +getJasmineRequireObj().base = function(j$, jasmineGlobal) { + j$.unimplementedMethod_ = function() { + throw new Error('unimplemented method'); + }; + + j$.MAX_PRETTY_PRINT_DEPTH = 40; + j$.MAX_PRETTY_PRINT_ARRAY_LENGTH = 100; + j$.DEFAULT_TIMEOUT_INTERVAL = 5000; + + j$.getGlobal = function() { + return jasmineGlobal; + }; + + j$.getEnv = function(options) { + var env = j$.currentEnv_ = j$.currentEnv_ || new j$.Env(options); + //jasmine. singletons in here (setTimeout blah blah). + return env; + }; + + j$.isArray_ = function(value) { + return j$.isA_('Array', value); + }; + + j$.isString_ = function(value) { + return j$.isA_('String', value); + }; + + j$.isNumber_ = function(value) { + return j$.isA_('Number', value); + }; + + j$.isA_ = function(typeName, value) { + return Object.prototype.toString.apply(value) === '[object ' + typeName + ']'; + }; + + j$.isDomNode = function(obj) { + return obj.nodeType > 0; + }; + + j$.any = function(clazz) { + return new j$.Any(clazz); + }; + + j$.objectContaining = function(sample) { + return new j$.ObjectContaining(sample); + }; + + j$.createSpy = function(name, originalFn) { + + var spyStrategy = new j$.SpyStrategy({ + name: name, + fn: originalFn, + getSpy: function() { return spy; } + }), + callTracker = new j$.CallTracker(), + spy = function() { + var callData = { + object: this, + args: Array.prototype.slice.apply(arguments) + }; + + callTracker.track(callData); + var returnValue = spyStrategy.exec.apply(this, arguments); + callData.returnValue = returnValue; + + return returnValue; + }; + + for (var prop in originalFn) { + if (prop === 'and' || prop === 'calls') { + throw new Error('Jasmine spies would overwrite the \'and\' and \'calls\' properties on the object being spied upon'); + } + + spy[prop] = originalFn[prop]; + } + + spy.and = spyStrategy; + spy.calls = callTracker; + + return spy; + }; + + j$.isSpy = function(putativeSpy) { + if (!putativeSpy) { + return false; + } + return putativeSpy.and instanceof j$.SpyStrategy && + putativeSpy.calls instanceof j$.CallTracker; + }; + + j$.createSpyObj = function(baseName, methodNames) { + if (!j$.isArray_(methodNames) || methodNames.length === 0) { + throw 'createSpyObj requires a non-empty array of method names to create spies for'; + } + var obj = {}; + for (var i = 0; i < methodNames.length; i++) { + obj[methodNames[i]] = j$.createSpy(baseName + '.' + methodNames[i]); + } + return obj; + }; +}; + +getJasmineRequireObj().util = function() { + + var util = {}; + + util.inherit = function(childClass, parentClass) { + var Subclass = function() { + }; + Subclass.prototype = parentClass.prototype; + childClass.prototype = new Subclass(); + }; + + util.htmlEscape = function(str) { + if (!str) { + return str; + } + return str.replace(/&/g, '&') + .replace(//g, '>'); + }; + + util.argsToArray = function(args) { + var arrayOfArgs = []; + for (var i = 0; i < args.length; i++) { + arrayOfArgs.push(args[i]); + } + return arrayOfArgs; + }; + + util.isUndefined = function(obj) { + return obj === void 0; + }; + + util.arrayContains = function(array, search) { + var i = array.length; + while (i--) { + if (array[i] === search) { + return true; + } + } + return false; + }; + + util.clone = function(obj) { + if (Object.prototype.toString.apply(obj) === '[object Array]') { + return obj.slice(); + } + + var cloned = {}; + for (var prop in obj) { + if (obj.hasOwnProperty(prop)) { + cloned[prop] = obj[prop]; + } + } + + return cloned; + }; + + return util; +}; + +getJasmineRequireObj().Spec = function(j$) { + function Spec(attrs) { + this.expectationFactory = attrs.expectationFactory; + this.resultCallback = attrs.resultCallback || function() {}; + this.id = attrs.id; + this.description = attrs.description || ''; + this.queueableFn = attrs.queueableFn; + this.beforeAndAfterFns = attrs.beforeAndAfterFns || function() { return {befores: [], afters: []}; }; + this.userContext = attrs.userContext || function() { return {}; }; + this.onStart = attrs.onStart || function() {}; + this.getSpecName = attrs.getSpecName || function() { return ''; }; + this.expectationResultFactory = attrs.expectationResultFactory || function() { }; + this.queueRunnerFactory = attrs.queueRunnerFactory || function() {}; + this.catchingExceptions = attrs.catchingExceptions || function() { return true; }; + + if (!this.queueableFn.fn) { + this.pend(); + } + + this.result = { + id: this.id, + description: this.description, + fullName: this.getFullName(), + failedExpectations: [], + passedExpectations: [] + }; + } + + Spec.prototype.addExpectationResult = function(passed, data) { + var expectationResult = this.expectationResultFactory(data); + if (passed) { + this.result.passedExpectations.push(expectationResult); + } else { + this.result.failedExpectations.push(expectationResult); + } + }; + + Spec.prototype.expect = function(actual) { + return this.expectationFactory(actual, this); + }; + + Spec.prototype.execute = function(onComplete) { + var self = this; + + this.onStart(this); + + if (this.markedPending || this.disabled) { + complete(); + return; + } + + var fns = this.beforeAndAfterFns(); + var allFns = fns.befores.concat(this.queueableFn).concat(fns.afters); + + this.queueRunnerFactory({ + queueableFns: allFns, + onException: function() { self.onException.apply(self, arguments); }, + onComplete: complete, + userContext: this.userContext() + }); + + function complete() { + self.result.status = self.status(); + self.resultCallback(self.result); + + if (onComplete) { + onComplete(); + } + } + }; + + Spec.prototype.onException = function onException(e) { + if (Spec.isPendingSpecException(e)) { + this.pend(); + return; + } + + this.addExpectationResult(false, { + matcherName: '', + passed: false, + expected: '', + actual: '', + error: e + }); + }; + + Spec.prototype.disable = function() { + this.disabled = true; + }; + + Spec.prototype.pend = function() { + this.markedPending = true; + }; + + Spec.prototype.status = function() { + if (this.disabled) { + return 'disabled'; + } + + if (this.markedPending) { + return 'pending'; + } + + if (this.result.failedExpectations.length > 0) { + return 'failed'; + } else { + return 'passed'; + } + }; + + Spec.prototype.isExecutable = function() { + return !this.disabled && !this.markedPending; + }; + + Spec.prototype.getFullName = function() { + return this.getSpecName(this); + }; + + Spec.pendingSpecExceptionMessage = '=> marked Pending'; + + Spec.isPendingSpecException = function(e) { + return !!(e && e.toString && e.toString().indexOf(Spec.pendingSpecExceptionMessage) !== -1); + }; + + return Spec; +}; + +if (typeof window == void 0 && typeof exports == 'object') { + exports.Spec = jasmineRequire.Spec; +} + +getJasmineRequireObj().Env = function(j$) { + function Env(options) { + options = options || {}; + + var self = this; + var global = options.global || j$.getGlobal(); + + var totalSpecsDefined = 0; + + var catchExceptions = true; + + var realSetTimeout = j$.getGlobal().setTimeout; + var realClearTimeout = j$.getGlobal().clearTimeout; + this.clock = new j$.Clock(global, new j$.DelayedFunctionScheduler(), new j$.MockDate(global)); + + var runnableLookupTable = {}; + var runnableResources = {}; + + var currentSpec = null; + var currentlyExecutingSuites = []; + var currentDeclarationSuite = null; + + var currentSuite = function() { + return currentlyExecutingSuites[currentlyExecutingSuites.length - 1]; + }; + + var currentRunnable = function() { + return currentSpec || currentSuite(); + }; + + var reporter = new j$.ReportDispatcher([ + 'jasmineStarted', + 'jasmineDone', + 'suiteStarted', + 'suiteDone', + 'specStarted', + 'specDone' + ]); + + this.specFilter = function() { + return true; + }; + + this.addCustomEqualityTester = function(tester) { + if(!currentRunnable()) { + throw new Error('Custom Equalities must be added in a before function or a spec'); + } + runnableResources[currentRunnable().id].customEqualityTesters.push(tester); + }; + + this.addMatchers = function(matchersToAdd) { + if(!currentRunnable()) { + throw new Error('Matchers must be added in a before function or a spec'); + } + var customMatchers = runnableResources[currentRunnable().id].customMatchers; + for (var matcherName in matchersToAdd) { + customMatchers[matcherName] = matchersToAdd[matcherName]; + } + }; + + j$.Expectation.addCoreMatchers(j$.matchers); + + var nextSpecId = 0; + var getNextSpecId = function() { + return 'spec' + nextSpecId++; + }; + + var nextSuiteId = 0; + var getNextSuiteId = function() { + return 'suite' + nextSuiteId++; + }; + + var expectationFactory = function(actual, spec) { + return j$.Expectation.Factory({ + util: j$.matchersUtil, + customEqualityTesters: runnableResources[spec.id].customEqualityTesters, + customMatchers: runnableResources[spec.id].customMatchers, + actual: actual, + addExpectationResult: addExpectationResult + }); + + function addExpectationResult(passed, result) { + return spec.addExpectationResult(passed, result); + } + }; + + var defaultResourcesForRunnable = function(id, parentRunnableId) { + var resources = {spies: [], customEqualityTesters: [], customMatchers: {}}; + + if(runnableResources[parentRunnableId]){ + resources.customEqualityTesters = j$.util.clone(runnableResources[parentRunnableId].customEqualityTesters); + resources.customMatchers = j$.util.clone(runnableResources[parentRunnableId].customMatchers); + } + + runnableResources[id] = resources; + }; + + var clearResourcesForRunnable = function(id) { + spyRegistry.clearSpies(); + delete runnableResources[id]; + }; + + var beforeAndAfterFns = function(suite, runnablesExplictlySet) { + return function() { + var befores = [], + afters = [], + beforeAlls = [], + afterAlls = []; + + while(suite) { + befores = befores.concat(suite.beforeFns); + afters = afters.concat(suite.afterFns); + + if (runnablesExplictlySet()) { + beforeAlls = beforeAlls.concat(suite.beforeAllFns); + afterAlls = afterAlls.concat(suite.afterAllFns); + } + + suite = suite.parentSuite; + } + return { + befores: beforeAlls.reverse().concat(befores.reverse()), + afters: afters.concat(afterAlls) + }; + }; + }; + + var getSpecName = function(spec, suite) { + return suite.getFullName() + ' ' + spec.description; + }; + + // TODO: we may just be able to pass in the fn instead of wrapping here + var buildExpectationResult = j$.buildExpectationResult, + exceptionFormatter = new j$.ExceptionFormatter(), + expectationResultFactory = function(attrs) { + attrs.messageFormatter = exceptionFormatter.message; + attrs.stackFormatter = exceptionFormatter.stack; + + return buildExpectationResult(attrs); + }; + + // TODO: fix this naming, and here's where the value comes in + this.catchExceptions = function(value) { + catchExceptions = !!value; + return catchExceptions; + }; + + this.catchingExceptions = function() { + return catchExceptions; + }; + + var maximumSpecCallbackDepth = 20; + var currentSpecCallbackDepth = 0; + + function clearStack(fn) { + currentSpecCallbackDepth++; + if (currentSpecCallbackDepth >= maximumSpecCallbackDepth) { + currentSpecCallbackDepth = 0; + realSetTimeout(fn, 0); + } else { + fn(); + } + } + + var catchException = function(e) { + return j$.Spec.isPendingSpecException(e) || catchExceptions; + }; + + var queueRunnerFactory = function(options) { + options.catchException = catchException; + options.clearStack = options.clearStack || clearStack; + options.timer = {setTimeout: realSetTimeout, clearTimeout: realClearTimeout}; + options.fail = self.fail; + + new j$.QueueRunner(options).execute(); + }; + + var topSuite = new j$.Suite({ + env: this, + id: getNextSuiteId(), + description: 'Jasmine__TopLevel__Suite', + queueRunner: queueRunnerFactory, + onStart: function(suite) { + reporter.suiteStarted(suite.result); + }, + resultCallback: function(attrs) { + reporter.suiteDone(attrs); + } + }); + runnableLookupTable[topSuite.id] = topSuite; + defaultResourcesForRunnable(topSuite.id); + currentDeclarationSuite = topSuite; + + this.topSuite = function() { + return topSuite; + }; + + this.execute = function(runnablesToRun) { + if(runnablesToRun) { + runnablesExplictlySet = true; + } else if (focusedRunnables.length) { + runnablesExplictlySet = true; + runnablesToRun = focusedRunnables; + } else { + runnablesToRun = [topSuite.id]; + } + + var allFns = []; + for(var i = 0; i < runnablesToRun.length; i++) { + var runnable = runnableLookupTable[runnablesToRun[i]]; + allFns.push((function(runnable) { return { fn: function(done) { runnable.execute(done); } }; })(runnable)); + } + + reporter.jasmineStarted({ + totalSpecsDefined: totalSpecsDefined + }); + + queueRunnerFactory({queueableFns: allFns, onComplete: reporter.jasmineDone}); + }; + + this.addReporter = function(reporterToAdd) { + reporter.addReporter(reporterToAdd); + }; + + var spyRegistry = new j$.SpyRegistry({currentSpies: function() { + if(!currentRunnable()) { + throw new Error('Spies must be created in a before function or a spec'); + } + return runnableResources[currentRunnable().id].spies; + }}); + + this.spyOn = function() { + return spyRegistry.spyOn.apply(spyRegistry, arguments); + }; + + var suiteFactory = function(description) { + var suite = new j$.Suite({ + env: self, + id: getNextSuiteId(), + description: description, + parentSuite: currentDeclarationSuite, + queueRunner: queueRunnerFactory, + onStart: suiteStarted, + expectationFactory: expectationFactory, + expectationResultFactory: expectationResultFactory, + resultCallback: function(attrs) { + if (!suite.disabled) { + clearResourcesForRunnable(suite.id); + currentlyExecutingSuites.pop(); + } + reporter.suiteDone(attrs); + } + }); + + runnableLookupTable[suite.id] = suite; + return suite; + + function suiteStarted(suite) { + currentlyExecutingSuites.push(suite); + defaultResourcesForRunnable(suite.id, suite.parentSuite.id); + reporter.suiteStarted(suite.result); + } + }; + + this.describe = function(description, specDefinitions) { + var suite = suiteFactory(description); + addSpecsToSuite(suite, specDefinitions); + return suite; + }; + + this.xdescribe = function(description, specDefinitions) { + var suite = this.describe(description, specDefinitions); + suite.disable(); + return suite; + }; + + var focusedRunnables = []; + + this.fdescribe = function(description, specDefinitions) { + var suite = suiteFactory(description); + suite.isFocused = true; + + focusedRunnables.push(suite.id); + unfocusAncestor(); + addSpecsToSuite(suite, specDefinitions); + + return suite; + }; + + function addSpecsToSuite(suite, specDefinitions) { + var parentSuite = currentDeclarationSuite; + parentSuite.addChild(suite); + currentDeclarationSuite = suite; + + var declarationError = null; + try { + specDefinitions.call(suite); + } catch (e) { + declarationError = e; + } + + if (declarationError) { + self.it('encountered a declaration exception', function() { + throw declarationError; + }); + } + + currentDeclarationSuite = parentSuite; + } + + function findFocusedAncestor(suite) { + while (suite) { + if (suite.isFocused) { + return suite.id; + } + suite = suite.parentSuite; + } + + return null; + } + + function unfocusAncestor() { + var focusedAncestor = findFocusedAncestor(currentDeclarationSuite); + if (focusedAncestor) { + for (var i = 0; i < focusedRunnables.length; i++) { + if (focusedRunnables[i] === focusedAncestor) { + focusedRunnables.splice(i, 1); + break; + } + } + } + } + + var runnablesExplictlySet = false; + + var runnablesExplictlySetGetter = function(){ + return runnablesExplictlySet; + }; + + var specFactory = function(description, fn, suite, timeout) { + totalSpecsDefined++; + var spec = new j$.Spec({ + id: getNextSpecId(), + beforeAndAfterFns: beforeAndAfterFns(suite, runnablesExplictlySetGetter), + expectationFactory: expectationFactory, + resultCallback: specResultCallback, + getSpecName: function(spec) { + return getSpecName(spec, suite); + }, + onStart: specStarted, + description: description, + expectationResultFactory: expectationResultFactory, + queueRunnerFactory: queueRunnerFactory, + userContext: function() { return suite.clonedSharedUserContext(); }, + queueableFn: { + fn: fn, + timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } + } + }); + + runnableLookupTable[spec.id] = spec; + + if (!self.specFilter(spec)) { + spec.disable(); + } + + return spec; + + function specResultCallback(result) { + clearResourcesForRunnable(spec.id); + currentSpec = null; + reporter.specDone(result); + } + + function specStarted(spec) { + currentSpec = spec; + defaultResourcesForRunnable(spec.id, suite.id); + reporter.specStarted(spec.result); + } + }; + + this.it = function(description, fn, timeout) { + var spec = specFactory(description, fn, currentDeclarationSuite, timeout); + currentDeclarationSuite.addChild(spec); + return spec; + }; + + this.xit = function() { + var spec = this.it.apply(this, arguments); + spec.pend(); + return spec; + }; + + this.fit = function(){ + var spec = this.it.apply(this, arguments); + + focusedRunnables.push(spec.id); + unfocusAncestor(); + return spec; + }; + + this.expect = function(actual) { + if (!currentRunnable()) { + throw new Error('\'expect\' was used when there was no current spec, this could be because an asynchronous test timed out'); + } + + return currentRunnable().expect(actual); + }; + + this.beforeEach = function(beforeEachFunction, timeout) { + currentDeclarationSuite.beforeEach({ + fn: beforeEachFunction, + timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } + }); + }; + + this.beforeAll = function(beforeAllFunction, timeout) { + currentDeclarationSuite.beforeAll({ + fn: beforeAllFunction, + timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } + }); + }; + + this.afterEach = function(afterEachFunction, timeout) { + currentDeclarationSuite.afterEach({ + fn: afterEachFunction, + timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } + }); + }; + + this.afterAll = function(afterAllFunction, timeout) { + currentDeclarationSuite.afterAll({ + fn: afterAllFunction, + timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } + }); + }; + + this.pending = function() { + throw j$.Spec.pendingSpecExceptionMessage; + }; + + this.fail = function(error) { + var message = 'Failed'; + if (error) { + message += ': '; + message += error.message || error; + } + + currentRunnable().addExpectationResult(false, { + matcherName: '', + passed: false, + expected: '', + actual: '', + message: message + }); + }; + } + + return Env; +}; + +getJasmineRequireObj().JsApiReporter = function() { + + var noopTimer = { + start: function(){}, + elapsed: function(){ return 0; } + }; + + function JsApiReporter(options) { + var timer = options.timer || noopTimer, + status = 'loaded'; + + this.started = false; + this.finished = false; + + this.jasmineStarted = function() { + this.started = true; + status = 'started'; + timer.start(); + }; + + var executionTime; + + this.jasmineDone = function() { + this.finished = true; + executionTime = timer.elapsed(); + status = 'done'; + }; + + this.status = function() { + return status; + }; + + var suites = [], + suites_hash = {}; + + this.suiteStarted = function(result) { + suites_hash[result.id] = result; + }; + + this.suiteDone = function(result) { + storeSuite(result); + }; + + this.suiteResults = function(index, length) { + return suites.slice(index, index + length); + }; + + function storeSuite(result) { + suites.push(result); + suites_hash[result.id] = result; + } + + this.suites = function() { + return suites_hash; + }; + + var specs = []; + + this.specDone = function(result) { + specs.push(result); + }; + + this.specResults = function(index, length) { + return specs.slice(index, index + length); + }; + + this.specs = function() { + return specs; + }; + + this.executionTime = function() { + return executionTime; + }; + + } + + return JsApiReporter; +}; + +getJasmineRequireObj().Any = function() { + + function Any(expectedObject) { + this.expectedObject = expectedObject; + } + + Any.prototype.jasmineMatches = function(other) { + if (this.expectedObject == String) { + return typeof other == 'string' || other instanceof String; + } + + if (this.expectedObject == Number) { + return typeof other == 'number' || other instanceof Number; + } + + if (this.expectedObject == Function) { + return typeof other == 'function' || other instanceof Function; + } + + if (this.expectedObject == Object) { + return typeof other == 'object'; + } + + if (this.expectedObject == Boolean) { + return typeof other == 'boolean'; + } + + return other instanceof this.expectedObject; + }; + + Any.prototype.jasmineToString = function() { + return ''; + }; + + return Any; +}; + +getJasmineRequireObj().CallTracker = function() { + + function CallTracker() { + var calls = []; + + this.track = function(context) { + calls.push(context); + }; + + this.any = function() { + return !!calls.length; + }; + + this.count = function() { + return calls.length; + }; + + this.argsFor = function(index) { + var call = calls[index]; + return call ? call.args : []; + }; + + this.all = function() { + return calls; + }; + + this.allArgs = function() { + var callArgs = []; + for(var i = 0; i < calls.length; i++){ + callArgs.push(calls[i].args); + } + + return callArgs; + }; + + this.first = function() { + return calls[0]; + }; + + this.mostRecent = function() { + return calls[calls.length - 1]; + }; + + this.reset = function() { + calls = []; + }; + } + + return CallTracker; +}; + +getJasmineRequireObj().Clock = function() { + function Clock(global, delayedFunctionScheduler, mockDate) { + var self = this, + realTimingFunctions = { + setTimeout: global.setTimeout, + clearTimeout: global.clearTimeout, + setInterval: global.setInterval, + clearInterval: global.clearInterval + }, + fakeTimingFunctions = { + setTimeout: setTimeout, + clearTimeout: clearTimeout, + setInterval: setInterval, + clearInterval: clearInterval + }, + installed = false, + timer; + + + self.install = function() { + replace(global, fakeTimingFunctions); + timer = fakeTimingFunctions; + installed = true; + + return self; + }; + + self.uninstall = function() { + delayedFunctionScheduler.reset(); + mockDate.uninstall(); + replace(global, realTimingFunctions); + + timer = realTimingFunctions; + installed = false; + }; + + self.mockDate = function(initialDate) { + mockDate.install(initialDate); + }; + + self.setTimeout = function(fn, delay, params) { + if (legacyIE()) { + if (arguments.length > 2) { + throw new Error('IE < 9 cannot support extra params to setTimeout without a polyfill'); + } + return timer.setTimeout(fn, delay); + } + return Function.prototype.apply.apply(timer.setTimeout, [global, arguments]); + }; + + self.setInterval = function(fn, delay, params) { + if (legacyIE()) { + if (arguments.length > 2) { + throw new Error('IE < 9 cannot support extra params to setInterval without a polyfill'); + } + return timer.setInterval(fn, delay); + } + return Function.prototype.apply.apply(timer.setInterval, [global, arguments]); + }; + + self.clearTimeout = function(id) { + return Function.prototype.call.apply(timer.clearTimeout, [global, id]); + }; + + self.clearInterval = function(id) { + return Function.prototype.call.apply(timer.clearInterval, [global, id]); + }; + + self.tick = function(millis) { + if (installed) { + mockDate.tick(millis); + delayedFunctionScheduler.tick(millis); + } else { + throw new Error('Mock clock is not installed, use jasmine.clock().install()'); + } + }; + + return self; + + function legacyIE() { + //if these methods are polyfilled, apply will be present + return !(realTimingFunctions.setTimeout || realTimingFunctions.setInterval).apply; + } + + function replace(dest, source) { + for (var prop in source) { + dest[prop] = source[prop]; + } + } + + function setTimeout(fn, delay) { + return delayedFunctionScheduler.scheduleFunction(fn, delay, argSlice(arguments, 2)); + } + + function clearTimeout(id) { + return delayedFunctionScheduler.removeFunctionWithId(id); + } + + function setInterval(fn, interval) { + return delayedFunctionScheduler.scheduleFunction(fn, interval, argSlice(arguments, 2), true); + } + + function clearInterval(id) { + return delayedFunctionScheduler.removeFunctionWithId(id); + } + + function argSlice(argsObj, n) { + return Array.prototype.slice.call(argsObj, n); + } + } + + return Clock; +}; + +getJasmineRequireObj().DelayedFunctionScheduler = function() { + function DelayedFunctionScheduler() { + var self = this; + var scheduledLookup = []; + var scheduledFunctions = {}; + var currentTime = 0; + var delayedFnCount = 0; + + self.tick = function(millis) { + millis = millis || 0; + var endTime = currentTime + millis; + + runScheduledFunctions(endTime); + currentTime = endTime; + }; + + self.scheduleFunction = function(funcToCall, millis, params, recurring, timeoutKey, runAtMillis) { + var f; + if (typeof(funcToCall) === 'string') { + /* jshint evil: true */ + f = function() { return eval(funcToCall); }; + /* jshint evil: false */ + } else { + f = funcToCall; + } + + millis = millis || 0; + timeoutKey = timeoutKey || ++delayedFnCount; + runAtMillis = runAtMillis || (currentTime + millis); + + var funcToSchedule = { + runAtMillis: runAtMillis, + funcToCall: f, + recurring: recurring, + params: params, + timeoutKey: timeoutKey, + millis: millis + }; + + if (runAtMillis in scheduledFunctions) { + scheduledFunctions[runAtMillis].push(funcToSchedule); + } else { + scheduledFunctions[runAtMillis] = [funcToSchedule]; + scheduledLookup.push(runAtMillis); + scheduledLookup.sort(function (a, b) { + return a - b; + }); + } + + return timeoutKey; + }; + + self.removeFunctionWithId = function(timeoutKey) { + for (var runAtMillis in scheduledFunctions) { + var funcs = scheduledFunctions[runAtMillis]; + var i = indexOfFirstToPass(funcs, function (func) { + return func.timeoutKey === timeoutKey; + }); + + if (i > -1) { + if (funcs.length === 1) { + delete scheduledFunctions[runAtMillis]; + deleteFromLookup(runAtMillis); + } else { + funcs.splice(i, 1); + } + + // intervals get rescheduled when executed, so there's never more + // than a single scheduled function with a given timeoutKey + break; + } + } + }; + + self.reset = function() { + currentTime = 0; + scheduledLookup = []; + scheduledFunctions = {}; + delayedFnCount = 0; + }; + + return self; + + function indexOfFirstToPass(array, testFn) { + var index = -1; + + for (var i = 0; i < array.length; ++i) { + if (testFn(array[i])) { + index = i; + break; + } + } + + return index; + } + + function deleteFromLookup(key) { + var value = Number(key); + var i = indexOfFirstToPass(scheduledLookup, function (millis) { + return millis === value; + }); + + if (i > -1) { + scheduledLookup.splice(i, 1); + } + } + + function reschedule(scheduledFn) { + self.scheduleFunction(scheduledFn.funcToCall, + scheduledFn.millis, + scheduledFn.params, + true, + scheduledFn.timeoutKey, + scheduledFn.runAtMillis + scheduledFn.millis); + } + + function runScheduledFunctions(endTime) { + if (scheduledLookup.length === 0 || scheduledLookup[0] > endTime) { + return; + } + + do { + currentTime = scheduledLookup.shift(); + + var funcsToRun = scheduledFunctions[currentTime]; + delete scheduledFunctions[currentTime]; + + for (var i = 0; i < funcsToRun.length; ++i) { + var funcToRun = funcsToRun[i]; + + if (funcToRun.recurring) { + reschedule(funcToRun); + } + + funcToRun.funcToCall.apply(null, funcToRun.params || []); + } + } while (scheduledLookup.length > 0 && + // checking first if we're out of time prevents setTimeout(0) + // scheduled in a funcToRun from forcing an extra iteration + currentTime !== endTime && + scheduledLookup[0] <= endTime); + } + } + + return DelayedFunctionScheduler; +}; + +getJasmineRequireObj().ExceptionFormatter = function() { + function ExceptionFormatter() { + this.message = function(error) { + var message = ''; + + if (error.name && error.message) { + message += error.name + ': ' + error.message; + } else { + message += error.toString() + ' thrown'; + } + + if (error.fileName || error.sourceURL) { + message += ' in ' + (error.fileName || error.sourceURL); + } + + if (error.line || error.lineNumber) { + message += ' (line ' + (error.line || error.lineNumber) + ')'; + } + + return message; + }; + + this.stack = function(error) { + return error ? error.stack : null; + }; + } + + return ExceptionFormatter; +}; + +getJasmineRequireObj().Expectation = function() { + + function Expectation(options) { + this.util = options.util || { buildFailureMessage: function() {} }; + this.customEqualityTesters = options.customEqualityTesters || []; + this.actual = options.actual; + this.addExpectationResult = options.addExpectationResult || function(){}; + this.isNot = options.isNot; + + var customMatchers = options.customMatchers || {}; + for (var matcherName in customMatchers) { + this[matcherName] = Expectation.prototype.wrapCompare(matcherName, customMatchers[matcherName]); + } + } + + Expectation.prototype.wrapCompare = function(name, matcherFactory) { + return function() { + var args = Array.prototype.slice.call(arguments, 0), + expected = args.slice(0), + message = ''; + + args.unshift(this.actual); + + var matcher = matcherFactory(this.util, this.customEqualityTesters), + matcherCompare = matcher.compare; + + function defaultNegativeCompare() { + var result = matcher.compare.apply(null, args); + result.pass = !result.pass; + return result; + } + + if (this.isNot) { + matcherCompare = matcher.negativeCompare || defaultNegativeCompare; + } + + var result = matcherCompare.apply(null, args); + + if (!result.pass) { + if (!result.message) { + args.unshift(this.isNot); + args.unshift(name); + message = this.util.buildFailureMessage.apply(null, args); + } else { + if (Object.prototype.toString.apply(result.message) === '[object Function]') { + message = result.message(); + } else { + message = result.message; + } + } + } + + if (expected.length == 1) { + expected = expected[0]; + } + + // TODO: how many of these params are needed? + this.addExpectationResult( + result.pass, + { + matcherName: name, + passed: result.pass, + message: message, + actual: this.actual, + expected: expected // TODO: this may need to be arrayified/sliced + } + ); + }; + }; + + Expectation.addCoreMatchers = function(matchers) { + var prototype = Expectation.prototype; + for (var matcherName in matchers) { + var matcher = matchers[matcherName]; + prototype[matcherName] = prototype.wrapCompare(matcherName, matcher); + } + }; + + Expectation.Factory = function(options) { + options = options || {}; + + var expect = new Expectation(options); + + // TODO: this would be nice as its own Object - NegativeExpectation + // TODO: copy instead of mutate options + options.isNot = true; + expect.not = new Expectation(options); + + return expect; + }; + + return Expectation; +}; + +//TODO: expectation result may make more sense as a presentation of an expectation. +getJasmineRequireObj().buildExpectationResult = function() { + function buildExpectationResult(options) { + var messageFormatter = options.messageFormatter || function() {}, + stackFormatter = options.stackFormatter || function() {}; + + return { + matcherName: options.matcherName, + expected: options.expected, + actual: options.actual, + message: message(), + stack: stack(), + passed: options.passed + }; + + function message() { + if (options.passed) { + return 'Passed.'; + } else if (options.message) { + return options.message; + } else if (options.error) { + return messageFormatter(options.error); + } + return ''; + } + + function stack() { + if (options.passed) { + return ''; + } + + var error = options.error; + if (!error) { + try { + throw new Error(message()); + } catch (e) { + error = e; + } + } + return stackFormatter(error); + } + } + + return buildExpectationResult; +}; + +getJasmineRequireObj().MockDate = function() { + function MockDate(global) { + var self = this; + var currentTime = 0; + + if (!global || !global.Date) { + self.install = function() {}; + self.tick = function() {}; + self.uninstall = function() {}; + return self; + } + + var GlobalDate = global.Date; + + self.install = function(mockDate) { + if (mockDate instanceof GlobalDate) { + currentTime = mockDate.getTime(); + } else { + currentTime = new GlobalDate().getTime(); + } + + global.Date = FakeDate; + }; + + self.tick = function(millis) { + millis = millis || 0; + currentTime = currentTime + millis; + }; + + self.uninstall = function() { + currentTime = 0; + global.Date = GlobalDate; + }; + + createDateProperties(); + + return self; + + function FakeDate() { + switch(arguments.length) { + case 0: + return new GlobalDate(currentTime); + case 1: + return new GlobalDate(arguments[0]); + case 2: + return new GlobalDate(arguments[0], arguments[1]); + case 3: + return new GlobalDate(arguments[0], arguments[1], arguments[2]); + case 4: + return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3]); + case 5: + return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3], + arguments[4]); + case 6: + return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3], + arguments[4], arguments[5]); + case 7: + return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3], + arguments[4], arguments[5], arguments[6]); + } + } + + function createDateProperties() { + FakeDate.prototype = GlobalDate.prototype; + + FakeDate.now = function() { + if (GlobalDate.now) { + return currentTime; + } else { + throw new Error('Browser does not support Date.now()'); + } + }; + + FakeDate.toSource = GlobalDate.toSource; + FakeDate.toString = GlobalDate.toString; + FakeDate.parse = GlobalDate.parse; + FakeDate.UTC = GlobalDate.UTC; + } + } + + return MockDate; +}; + +getJasmineRequireObj().ObjectContaining = function(j$) { + + function ObjectContaining(sample) { + this.sample = sample; + } + + ObjectContaining.prototype.jasmineMatches = function(other, mismatchKeys, mismatchValues) { + if (typeof(this.sample) !== 'object') { throw new Error('You must provide an object to objectContaining, not \''+this.sample+'\'.'); } + + mismatchKeys = mismatchKeys || []; + mismatchValues = mismatchValues || []; + + var hasKey = function(obj, keyName) { + return obj !== null && !j$.util.isUndefined(obj[keyName]); + }; + + for (var property in this.sample) { + if (!hasKey(other, property) && hasKey(this.sample, property)) { + mismatchKeys.push('expected has key \'' + property + '\', but missing from actual.'); + } + else if (!j$.matchersUtil.equals(other[property], this.sample[property])) { + mismatchValues.push('\'' + property + '\' was \'' + (other[property] ? j$.util.htmlEscape(other[property].toString()) : other[property]) + '\' in actual, but was \'' + (this.sample[property] ? j$.util.htmlEscape(this.sample[property].toString()) : this.sample[property]) + '\' in expected.'); + } + } + + return (mismatchKeys.length === 0 && mismatchValues.length === 0); + }; + + ObjectContaining.prototype.jasmineToString = function() { + return ''; + }; + + return ObjectContaining; +}; + +getJasmineRequireObj().pp = function(j$) { + + function PrettyPrinter() { + this.ppNestLevel_ = 0; + this.seen = []; + } + + PrettyPrinter.prototype.format = function(value) { + this.ppNestLevel_++; + try { + if (j$.util.isUndefined(value)) { + this.emitScalar('undefined'); + } else if (value === null) { + this.emitScalar('null'); + } else if (value === 0 && 1/value === -Infinity) { + this.emitScalar('-0'); + } else if (value === j$.getGlobal()) { + this.emitScalar(''); + } else if (value.jasmineToString) { + this.emitScalar(value.jasmineToString()); + } else if (typeof value === 'string') { + this.emitString(value); + } else if (j$.isSpy(value)) { + this.emitScalar('spy on ' + value.and.identity()); + } else if (value instanceof RegExp) { + this.emitScalar(value.toString()); + } else if (typeof value === 'function') { + this.emitScalar('Function'); + } else if (typeof value.nodeType === 'number') { + this.emitScalar('HTMLNode'); + } else if (value instanceof Date) { + this.emitScalar('Date(' + value + ')'); + } else if (j$.util.arrayContains(this.seen, value)) { + this.emitScalar(''); + } else if (j$.isArray_(value) || j$.isA_('Object', value)) { + this.seen.push(value); + if (j$.isArray_(value)) { + this.emitArray(value); + } else { + this.emitObject(value); + } + this.seen.pop(); + } else { + this.emitScalar(value.toString()); + } + } finally { + this.ppNestLevel_--; + } + }; + + PrettyPrinter.prototype.iterateObject = function(obj, fn) { + for (var property in obj) { + if (!Object.prototype.hasOwnProperty.call(obj, property)) { continue; } + fn(property, obj.__lookupGetter__ ? (!j$.util.isUndefined(obj.__lookupGetter__(property)) && + obj.__lookupGetter__(property) !== null) : false); + } + }; + + PrettyPrinter.prototype.emitArray = j$.unimplementedMethod_; + PrettyPrinter.prototype.emitObject = j$.unimplementedMethod_; + PrettyPrinter.prototype.emitScalar = j$.unimplementedMethod_; + PrettyPrinter.prototype.emitString = j$.unimplementedMethod_; + + function StringPrettyPrinter() { + PrettyPrinter.call(this); + + this.string = ''; + } + + j$.util.inherit(StringPrettyPrinter, PrettyPrinter); + + StringPrettyPrinter.prototype.emitScalar = function(value) { + this.append(value); + }; + + StringPrettyPrinter.prototype.emitString = function(value) { + this.append('\'' + value + '\''); + }; + + StringPrettyPrinter.prototype.emitArray = function(array) { + if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) { + this.append('Array'); + return; + } + var length = Math.min(array.length, j$.MAX_PRETTY_PRINT_ARRAY_LENGTH); + this.append('[ '); + for (var i = 0; i < length; i++) { + if (i > 0) { + this.append(', '); + } + this.format(array[i]); + } + if(array.length > length){ + this.append(', ...'); + } + this.append(' ]'); + }; + + StringPrettyPrinter.prototype.emitObject = function(obj) { + if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) { + this.append('Object'); + return; + } + + var self = this; + this.append('{ '); + var first = true; + + this.iterateObject(obj, function(property, isGetter) { + if (first) { + first = false; + } else { + self.append(', '); + } + + self.append(property); + self.append(': '); + if (isGetter) { + self.append(''); + } else { + self.format(obj[property]); + } + }); + + this.append(' }'); + }; + + StringPrettyPrinter.prototype.append = function(value) { + this.string += value; + }; + + return function(value) { + var stringPrettyPrinter = new StringPrettyPrinter(); + stringPrettyPrinter.format(value); + return stringPrettyPrinter.string; + }; +}; + +getJasmineRequireObj().QueueRunner = function(j$) { + + function once(fn) { + var called = false; + return function() { + if (!called) { + called = true; + fn(); + } + }; + } + + function QueueRunner(attrs) { + this.queueableFns = attrs.queueableFns || []; + this.onComplete = attrs.onComplete || function() {}; + this.clearStack = attrs.clearStack || function(fn) {fn();}; + this.onException = attrs.onException || function() {}; + this.catchException = attrs.catchException || function() { return true; }; + this.userContext = attrs.userContext || {}; + this.timer = attrs.timeout || {setTimeout: setTimeout, clearTimeout: clearTimeout}; + this.fail = attrs.fail || function() {}; + } + + QueueRunner.prototype.execute = function() { + this.run(this.queueableFns, 0); + }; + + QueueRunner.prototype.run = function(queueableFns, recursiveIndex) { + var length = queueableFns.length, + self = this, + iterativeIndex; + + + for(iterativeIndex = recursiveIndex; iterativeIndex < length; iterativeIndex++) { + var queueableFn = queueableFns[iterativeIndex]; + if (queueableFn.fn.length > 0) { + return attemptAsync(queueableFn); + } else { + attemptSync(queueableFn); + } + } + + var runnerDone = iterativeIndex >= length; + + if (runnerDone) { + this.clearStack(this.onComplete); + } + + function attemptSync(queueableFn) { + try { + queueableFn.fn.call(self.userContext); + } catch (e) { + handleException(e, queueableFn); + } + } + + function attemptAsync(queueableFn) { + var clearTimeout = function () { + Function.prototype.apply.apply(self.timer.clearTimeout, [j$.getGlobal(), [timeoutId]]); + }, + next = once(function () { + clearTimeout(timeoutId); + self.run(queueableFns, iterativeIndex + 1); + }), + timeoutId; + + next.fail = function() { + self.fail.apply(null, arguments); + next(); + }; + + if (queueableFn.timeout) { + timeoutId = Function.prototype.apply.apply(self.timer.setTimeout, [j$.getGlobal(), [function() { + var error = new Error('Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.'); + onException(error, queueableFn); + next(); + }, queueableFn.timeout()]]); + } + + try { + queueableFn.fn.call(self.userContext, next); + } catch (e) { + handleException(e, queueableFn); + next(); + } + } + + function onException(e, queueableFn) { + self.onException(e); + } + + function handleException(e, queueableFn) { + onException(e, queueableFn); + if (!self.catchException(e)) { + //TODO: set a var when we catch an exception and + //use a finally block to close the loop in a nice way.. + throw e; + } + } + }; + + return QueueRunner; +}; + +getJasmineRequireObj().ReportDispatcher = function() { + function ReportDispatcher(methods) { + + var dispatchedMethods = methods || []; + + for (var i = 0; i < dispatchedMethods.length; i++) { + var method = dispatchedMethods[i]; + this[method] = (function(m) { + return function() { + dispatch(m, arguments); + }; + }(method)); + } + + var reporters = []; + + this.addReporter = function(reporter) { + reporters.push(reporter); + }; + + return this; + + function dispatch(method, args) { + for (var i = 0; i < reporters.length; i++) { + var reporter = reporters[i]; + if (reporter[method]) { + reporter[method].apply(reporter, args); + } + } + } + } + + return ReportDispatcher; +}; + + +getJasmineRequireObj().SpyRegistry = function(j$) { + + function SpyRegistry(options) { + options = options || {}; + var currentSpies = options.currentSpies || function() { return []; }; + + this.spyOn = function(obj, methodName) { + if (j$.util.isUndefined(obj)) { + throw new Error('spyOn could not find an object to spy upon for ' + methodName + '()'); + } + + if (j$.util.isUndefined(obj[methodName])) { + throw new Error(methodName + '() method does not exist'); + } + + if (obj[methodName] && j$.isSpy(obj[methodName])) { + //TODO?: should this return the current spy? Downside: may cause user confusion about spy state + throw new Error(methodName + ' has already been spied upon'); + } + + var spy = j$.createSpy(methodName, obj[methodName]); + + currentSpies().push({ + spy: spy, + baseObj: obj, + methodName: methodName, + originalValue: obj[methodName] + }); + + obj[methodName] = spy; + + return spy; + }; + + this.clearSpies = function() { + var spies = currentSpies(); + for (var i = 0; i < spies.length; i++) { + var spyEntry = spies[i]; + spyEntry.baseObj[spyEntry.methodName] = spyEntry.originalValue; + } + }; + } + + return SpyRegistry; +}; + +getJasmineRequireObj().SpyStrategy = function() { + + function SpyStrategy(options) { + options = options || {}; + + var identity = options.name || 'unknown', + originalFn = options.fn || function() {}, + getSpy = options.getSpy || function() {}, + plan = function() {}; + + this.identity = function() { + return identity; + }; + + this.exec = function() { + return plan.apply(this, arguments); + }; + + this.callThrough = function() { + plan = originalFn; + return getSpy(); + }; + + this.returnValue = function(value) { + plan = function() { + return value; + }; + return getSpy(); + }; + + this.returnValues = function() { + var values = Array.prototype.slice.call(arguments); + plan = function () { + return values.shift(); + }; + return getSpy(); + }; + + this.throwError = function(something) { + var error = (something instanceof Error) ? something : new Error(something); + plan = function() { + throw error; + }; + return getSpy(); + }; + + this.callFake = function(fn) { + plan = fn; + return getSpy(); + }; + + this.stub = function(fn) { + plan = function() {}; + return getSpy(); + }; + } + + return SpyStrategy; +}; + +getJasmineRequireObj().Suite = function() { + function Suite(attrs) { + this.env = attrs.env; + this.id = attrs.id; + this.parentSuite = attrs.parentSuite; + this.description = attrs.description; + this.onStart = attrs.onStart || function() {}; + this.resultCallback = attrs.resultCallback || function() {}; + this.clearStack = attrs.clearStack || function(fn) {fn();}; + this.expectationFactory = attrs.expectationFactory; + this.expectationResultFactory = attrs.expectationResultFactory; + + this.beforeFns = []; + this.afterFns = []; + this.beforeAllFns = []; + this.afterAllFns = []; + this.queueRunner = attrs.queueRunner || function() {}; + this.disabled = false; + + this.children = []; + + this.result = { + id: this.id, + description: this.description, + fullName: this.getFullName(), + failedExpectations: [] + }; + } + + Suite.prototype.expect = function(actual) { + return this.expectationFactory(actual, this); + }; + + Suite.prototype.getFullName = function() { + var fullName = this.description; + for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { + if (parentSuite.parentSuite) { + fullName = parentSuite.description + ' ' + fullName; + } + } + return fullName; + }; + + Suite.prototype.disable = function() { + this.disabled = true; + }; + + Suite.prototype.beforeEach = function(fn) { + this.beforeFns.unshift(fn); + }; + + Suite.prototype.beforeAll = function(fn) { + this.beforeAllFns.push(fn); + }; + + Suite.prototype.afterEach = function(fn) { + this.afterFns.unshift(fn); + }; + + Suite.prototype.afterAll = function(fn) { + this.afterAllFns.push(fn); + }; + + Suite.prototype.addChild = function(child) { + this.children.push(child); + }; + + Suite.prototype.status = function() { + if (this.disabled) { + return 'disabled'; + } + + if (this.result.failedExpectations.length > 0) { + return 'failed'; + } else { + return 'finished'; + } + }; + + Suite.prototype.execute = function(onComplete) { + var self = this; + + this.onStart(this); + + if (this.disabled) { + complete(); + return; + } + + var allFns = []; + + for (var i = 0; i < this.children.length; i++) { + allFns.push(wrapChildAsAsync(this.children[i])); + } + + if (this.isExecutable()) { + allFns = this.beforeAllFns.concat(allFns); + allFns = allFns.concat(this.afterAllFns); + } + + this.queueRunner({ + queueableFns: allFns, + onComplete: complete, + userContext: this.sharedUserContext(), + onException: function() { self.onException.apply(self, arguments); } + }); + + function complete() { + self.result.status = self.status(); + self.resultCallback(self.result); + + if (onComplete) { + onComplete(); + } + } + + function wrapChildAsAsync(child) { + return { fn: function(done) { child.execute(done); } }; + } + }; + + Suite.prototype.isExecutable = function() { + var foundActive = false; + for(var i = 0; i < this.children.length; i++) { + if(this.children[i].isExecutable()) { + foundActive = true; + break; + } + } + return foundActive; + }; + + Suite.prototype.sharedUserContext = function() { + if (!this.sharedContext) { + this.sharedContext = this.parentSuite ? clone(this.parentSuite.sharedUserContext()) : {}; + } + + return this.sharedContext; + }; + + Suite.prototype.clonedSharedUserContext = function() { + return clone(this.sharedUserContext()); + }; + + Suite.prototype.onException = function() { + if(isAfterAll(this.children)) { + var data = { + matcherName: '', + passed: false, + expected: '', + actual: '', + error: arguments[0] + }; + this.result.failedExpectations.push(this.expectationResultFactory(data)); + } else { + for (var i = 0; i < this.children.length; i++) { + var child = this.children[i]; + child.onException.apply(child, arguments); + } + } + }; + + Suite.prototype.addExpectationResult = function () { + if(isAfterAll(this.children) && isFailure(arguments)){ + var data = arguments[1]; + this.result.failedExpectations.push(this.expectationResultFactory(data)); + } else { + for (var i = 0; i < this.children.length; i++) { + var child = this.children[i]; + child.addExpectationResult.apply(child, arguments); + } + } + }; + + function isAfterAll(children) { + return children && children[0].result.status; + } + + function isFailure(args) { + return !args[0]; + } + + function clone(obj) { + var clonedObj = {}; + for (var prop in obj) { + if (obj.hasOwnProperty(prop)) { + clonedObj[prop] = obj[prop]; + } + } + + return clonedObj; + } + + return Suite; +}; + +if (typeof window == void 0 && typeof exports == 'object') { + exports.Suite = jasmineRequire.Suite; +} + +getJasmineRequireObj().Timer = function() { + var defaultNow = (function(Date) { + return function() { return new Date().getTime(); }; + })(Date); + + function Timer(options) { + options = options || {}; + + var now = options.now || defaultNow, + startTime; + + this.start = function() { + startTime = now(); + }; + + this.elapsed = function() { + return now() - startTime; + }; + } + + return Timer; +}; + +getJasmineRequireObj().matchersUtil = function(j$) { + // TODO: what to do about jasmine.pp not being inject? move to JSON.stringify? gut PrettyPrinter? + + return { + equals: function(a, b, customTesters) { + customTesters = customTesters || []; + + return eq(a, b, [], [], customTesters); + }, + + contains: function(haystack, needle, customTesters) { + customTesters = customTesters || []; + + if ((Object.prototype.toString.apply(haystack) === '[object Array]') || + (!!haystack && !haystack.indexOf)) + { + for (var i = 0; i < haystack.length; i++) { + if (eq(haystack[i], needle, [], [], customTesters)) { + return true; + } + } + return false; + } + + return !!haystack && haystack.indexOf(needle) >= 0; + }, + + buildFailureMessage: function() { + var args = Array.prototype.slice.call(arguments, 0), + matcherName = args[0], + isNot = args[1], + actual = args[2], + expected = args.slice(3), + englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); + + var message = 'Expected ' + + j$.pp(actual) + + (isNot ? ' not ' : ' ') + + englishyPredicate; + + if (expected.length > 0) { + for (var i = 0; i < expected.length; i++) { + if (i > 0) { + message += ','; + } + message += ' ' + j$.pp(expected[i]); + } + } + + return message + '.'; + } + }; + + // Equality function lovingly adapted from isEqual in + // [Underscore](http://underscorejs.org) + function eq(a, b, aStack, bStack, customTesters) { + var result = true; + + for (var i = 0; i < customTesters.length; i++) { + var customTesterResult = customTesters[i](a, b); + if (!j$.util.isUndefined(customTesterResult)) { + return customTesterResult; + } + } + + if (a instanceof j$.Any) { + result = a.jasmineMatches(b); + if (result) { + return true; + } + } + + if (b instanceof j$.Any) { + result = b.jasmineMatches(a); + if (result) { + return true; + } + } + + if (b instanceof j$.ObjectContaining) { + result = b.jasmineMatches(a); + if (result) { + return true; + } + } + + if (a instanceof Error && b instanceof Error) { + return a.message == b.message; + } + + // Identical objects are equal. `0 === -0`, but they aren't identical. + // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). + if (a === b) { return a !== 0 || 1 / a == 1 / b; } + // A strict comparison is necessary because `null == undefined`. + if (a === null || b === null) { return a === b; } + var className = Object.prototype.toString.call(a); + if (className != Object.prototype.toString.call(b)) { return false; } + switch (className) { + // Strings, numbers, dates, and booleans are compared by value. + case '[object String]': + // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is + // equivalent to `new String("5")`. + return a == String(b); + case '[object Number]': + // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for + // other numeric values. + return a != +a ? b != +b : (a === 0 ? 1 / a == 1 / b : a == +b); + case '[object Date]': + case '[object Boolean]': + // Coerce dates and booleans to numeric primitive values. Dates are compared by their + // millisecond representations. Note that invalid dates with millisecond representations + // of `NaN` are not equivalent. + return +a == +b; + // RegExps are compared by their source patterns and flags. + case '[object RegExp]': + return a.source == b.source && + a.global == b.global && + a.multiline == b.multiline && + a.ignoreCase == b.ignoreCase; + } + if (typeof a != 'object' || typeof b != 'object') { return false; } + // Assume equality for cyclic structures. The algorithm for detecting cyclic + // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. + var length = aStack.length; + while (length--) { + // Linear search. Performance is inversely proportional to the number of + // unique nested structures. + if (aStack[length] == a) { return bStack[length] == b; } + } + // Add the first object to the stack of traversed objects. + aStack.push(a); + bStack.push(b); + var size = 0; + // Recursively compare objects and arrays. + if (className == '[object Array]') { + // Compare array lengths to determine if a deep comparison is necessary. + size = a.length; + result = size == b.length; + if (result) { + // Deep compare the contents, ignoring non-numeric properties. + while (size--) { + if (!(result = eq(a[size], b[size], aStack, bStack, customTesters))) { break; } + } + } + } else { + // Objects with different constructors are not equivalent, but `Object`s + // from different frames are. + var aCtor = a.constructor, bCtor = b.constructor; + if (aCtor !== bCtor && !(isFunction(aCtor) && (aCtor instanceof aCtor) && + isFunction(bCtor) && (bCtor instanceof bCtor))) { + return false; + } + // Deep compare objects. + for (var key in a) { + if (has(a, key)) { + // Count the expected number of properties. + size++; + // Deep compare each member. + if (!(result = has(b, key) && eq(a[key], b[key], aStack, bStack, customTesters))) { break; } + } + } + // Ensure that both objects contain the same number of properties. + if (result) { + for (key in b) { + if (has(b, key) && !(size--)) { break; } + } + result = !size; + } + } + // Remove the first object from the stack of traversed objects. + aStack.pop(); + bStack.pop(); + + return result; + + function has(obj, key) { + return obj.hasOwnProperty(key); + } + + function isFunction(obj) { + return typeof obj === 'function'; + } + } +}; + +getJasmineRequireObj().toBe = function() { + function toBe() { + return { + compare: function(actual, expected) { + return { + pass: actual === expected + }; + } + }; + } + + return toBe; +}; + +getJasmineRequireObj().toBeCloseTo = function() { + + function toBeCloseTo() { + return { + compare: function(actual, expected, precision) { + if (precision !== 0) { + precision = precision || 2; + } + + return { + pass: Math.abs(expected - actual) < (Math.pow(10, -precision) / 2) + }; + } + }; + } + + return toBeCloseTo; +}; + +getJasmineRequireObj().toBeDefined = function() { + function toBeDefined() { + return { + compare: function(actual) { + return { + pass: (void 0 !== actual) + }; + } + }; + } + + return toBeDefined; +}; + +getJasmineRequireObj().toBeFalsy = function() { + function toBeFalsy() { + return { + compare: function(actual) { + return { + pass: !!!actual + }; + } + }; + } + + return toBeFalsy; +}; + +getJasmineRequireObj().toBeGreaterThan = function() { + + function toBeGreaterThan() { + return { + compare: function(actual, expected) { + return { + pass: actual > expected + }; + } + }; + } + + return toBeGreaterThan; +}; + + +getJasmineRequireObj().toBeLessThan = function() { + function toBeLessThan() { + return { + + compare: function(actual, expected) { + return { + pass: actual < expected + }; + } + }; + } + + return toBeLessThan; +}; +getJasmineRequireObj().toBeNaN = function(j$) { + + function toBeNaN() { + return { + compare: function(actual) { + var result = { + pass: (actual !== actual) + }; + + if (result.pass) { + result.message = 'Expected actual not to be NaN.'; + } else { + result.message = function() { return 'Expected ' + j$.pp(actual) + ' to be NaN.'; }; + } + + return result; + } + }; + } + + return toBeNaN; +}; + +getJasmineRequireObj().toBeNull = function() { + + function toBeNull() { + return { + compare: function(actual) { + return { + pass: actual === null + }; + } + }; + } + + return toBeNull; +}; + +getJasmineRequireObj().toBeTruthy = function() { + + function toBeTruthy() { + return { + compare: function(actual) { + return { + pass: !!actual + }; + } + }; + } + + return toBeTruthy; +}; + +getJasmineRequireObj().toBeUndefined = function() { + + function toBeUndefined() { + return { + compare: function(actual) { + return { + pass: void 0 === actual + }; + } + }; + } + + return toBeUndefined; +}; + +getJasmineRequireObj().toContain = function() { + function toContain(util, customEqualityTesters) { + customEqualityTesters = customEqualityTesters || []; + + return { + compare: function(actual, expected) { + + return { + pass: util.contains(actual, expected, customEqualityTesters) + }; + } + }; + } + + return toContain; +}; + +getJasmineRequireObj().toEqual = function() { + + function toEqual(util, customEqualityTesters) { + customEqualityTesters = customEqualityTesters || []; + + return { + compare: function(actual, expected) { + var result = { + pass: false + }; + + result.pass = util.equals(actual, expected, customEqualityTesters); + + return result; + } + }; + } + + return toEqual; +}; + +getJasmineRequireObj().toHaveBeenCalled = function(j$) { + + function toHaveBeenCalled() { + return { + compare: function(actual) { + var result = {}; + + if (!j$.isSpy(actual)) { + throw new Error('Expected a spy, but got ' + j$.pp(actual) + '.'); + } + + if (arguments.length > 1) { + throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith'); + } + + result.pass = actual.calls.any(); + + result.message = result.pass ? + 'Expected spy ' + actual.and.identity() + ' not to have been called.' : + 'Expected spy ' + actual.and.identity() + ' to have been called.'; + + return result; + } + }; + } + + return toHaveBeenCalled; +}; + +getJasmineRequireObj().toHaveBeenCalledWith = function(j$) { + + function toHaveBeenCalledWith(util, customEqualityTesters) { + return { + compare: function() { + var args = Array.prototype.slice.call(arguments, 0), + actual = args[0], + expectedArgs = args.slice(1), + result = { pass: false }; + + if (!j$.isSpy(actual)) { + throw new Error('Expected a spy, but got ' + j$.pp(actual) + '.'); + } + + if (!actual.calls.any()) { + result.message = function() { return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + j$.pp(expectedArgs) + ' but it was never called.'; }; + return result; + } + + if (util.contains(actual.calls.allArgs(), expectedArgs, customEqualityTesters)) { + result.pass = true; + result.message = function() { return 'Expected spy ' + actual.and.identity() + ' not to have been called with ' + j$.pp(expectedArgs) + ' but it was.'; }; + } else { + result.message = function() { return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + j$.pp(expectedArgs) + ' but actual calls were ' + j$.pp(actual.calls.allArgs()).replace(/^\[ | \]$/g, '') + '.'; }; + } + + return result; + } + }; + } + + return toHaveBeenCalledWith; +}; + +getJasmineRequireObj().toMatch = function() { + + function toMatch() { + return { + compare: function(actual, expected) { + var regexp = new RegExp(expected); + + return { + pass: regexp.test(actual) + }; + } + }; + } + + return toMatch; +}; + +getJasmineRequireObj().toThrow = function(j$) { + + function toThrow(util) { + return { + compare: function(actual, expected) { + var result = { pass: false }, + threw = false, + thrown; + + if (typeof actual != 'function') { + throw new Error('Actual is not a Function'); + } + + try { + actual(); + } catch (e) { + threw = true; + thrown = e; + } + + if (!threw) { + result.message = 'Expected function to throw an exception.'; + return result; + } + + if (arguments.length == 1) { + result.pass = true; + result.message = function() { return 'Expected function not to throw, but it threw ' + j$.pp(thrown) + '.'; }; + + return result; + } + + if (util.equals(thrown, expected)) { + result.pass = true; + result.message = function() { return 'Expected function not to throw ' + j$.pp(expected) + '.'; }; + } else { + result.message = function() { return 'Expected function to throw ' + j$.pp(expected) + ', but it threw ' + j$.pp(thrown) + '.'; }; + } + + return result; + } + }; + } + + return toThrow; +}; + +getJasmineRequireObj().toThrowError = function(j$) { + function toThrowError (util) { + return { + compare: function(actual) { + var threw = false, + pass = {pass: true}, + fail = {pass: false}, + thrown; + + if (typeof actual != 'function') { + throw new Error('Actual is not a Function'); + } + + var errorMatcher = getMatcher.apply(null, arguments); + + try { + actual(); + } catch (e) { + threw = true; + thrown = e; + } + + if (!threw) { + fail.message = 'Expected function to throw an Error.'; + return fail; + } + + if (!(thrown instanceof Error)) { + fail.message = function() { return 'Expected function to throw an Error, but it threw ' + j$.pp(thrown) + '.'; }; + return fail; + } + + if (errorMatcher.hasNoSpecifics()) { + pass.message = 'Expected function not to throw an Error, but it threw ' + fnNameFor(thrown) + '.'; + return pass; + } + + if (errorMatcher.matches(thrown)) { + pass.message = function() { + return 'Expected function not to throw ' + errorMatcher.errorTypeDescription + errorMatcher.messageDescription() + '.'; + }; + return pass; + } else { + fail.message = function() { + return 'Expected function to throw ' + errorMatcher.errorTypeDescription + errorMatcher.messageDescription() + + ', but it threw ' + errorMatcher.thrownDescription(thrown) + '.'; + }; + return fail; + } + } + }; + + function getMatcher() { + var expected = null, + errorType = null; + + if (arguments.length == 2) { + expected = arguments[1]; + if (isAnErrorType(expected)) { + errorType = expected; + expected = null; + } + } else if (arguments.length > 2) { + errorType = arguments[1]; + expected = arguments[2]; + if (!isAnErrorType(errorType)) { + throw new Error('Expected error type is not an Error.'); + } + } + + if (expected && !isStringOrRegExp(expected)) { + if (errorType) { + throw new Error('Expected error message is not a string or RegExp.'); + } else { + throw new Error('Expected is not an Error, string, or RegExp.'); + } + } + + function messageMatch(message) { + if (typeof expected == 'string') { + return expected == message; + } else { + return expected.test(message); + } + } + + return { + errorTypeDescription: errorType ? fnNameFor(errorType) : 'an exception', + thrownDescription: function(thrown) { + var thrownName = errorType ? fnNameFor(thrown.constructor) : 'an exception', + thrownMessage = ''; + + if (expected) { + thrownMessage = ' with message ' + j$.pp(thrown.message); + } + + return thrownName + thrownMessage; + }, + messageDescription: function() { + if (expected === null) { + return ''; + } else if (expected instanceof RegExp) { + return ' with a message matching ' + j$.pp(expected); + } else { + return ' with message ' + j$.pp(expected); + } + }, + hasNoSpecifics: function() { + return expected === null && errorType === null; + }, + matches: function(error) { + return (errorType === null || error.constructor === errorType) && + (expected === null || messageMatch(error.message)); + } + }; + } + + function fnNameFor(func) { + return func.name || func.toString().match(/^\s*function\s*(\w*)\s*\(/)[1]; + } + + function isStringOrRegExp(potential) { + return potential instanceof RegExp || (typeof potential == 'string'); + } + + function isAnErrorType(type) { + if (typeof type !== 'function') { + return false; + } + + var Surrogate = function() {}; + Surrogate.prototype = type.prototype; + return (new Surrogate()) instanceof Error; + } + } + + return toThrowError; +}; + +getJasmineRequireObj().interface = function(jasmine, env) { + var jasmineInterface = { + describe: function(description, specDefinitions) { + return env.describe(description, specDefinitions); + }, + + xdescribe: function(description, specDefinitions) { + return env.xdescribe(description, specDefinitions); + }, + + fdescribe: function(description, specDefinitions) { + return env.fdescribe(description, specDefinitions); + }, + + it: function(desc, func) { + return env.it(desc, func); + }, + + xit: function(desc, func) { + return env.xit(desc, func); + }, + + fit: function(desc, func) { + return env.fit(desc, func); + }, + + beforeEach: function(beforeEachFunction) { + return env.beforeEach(beforeEachFunction); + }, + + afterEach: function(afterEachFunction) { + return env.afterEach(afterEachFunction); + }, + + beforeAll: function(beforeAllFunction) { + return env.beforeAll(beforeAllFunction); + }, + + afterAll: function(afterAllFunction) { + return env.afterAll(afterAllFunction); + }, + + expect: function(actual) { + return env.expect(actual); + }, + + pending: function() { + return env.pending(); + }, + + fail: function() { + return env.fail.apply(env, arguments); + }, + + spyOn: function(obj, methodName) { + return env.spyOn(obj, methodName); + }, + + jsApiReporter: new jasmine.JsApiReporter({ + timer: new jasmine.Timer() + }), + + jasmine: jasmine + }; + + jasmine.addCustomEqualityTester = function(tester) { + env.addCustomEqualityTester(tester); + }; + + jasmine.addMatchers = function(matchers) { + return env.addMatchers(matchers); + }; + + jasmine.clock = function() { + return env.clock; + }; + + return jasmineInterface; +}; + +getJasmineRequireObj().version = function() { + return '2.1.2'; +}; diff --git a/fnordmetric-webui/tests/lib/jasmine-2.1.2/jasmine_favicon.png b/fnordmetric-webui/tests/lib/jasmine-2.1.2/jasmine_favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..3b84583be4b9d5ae9cd5cae07b2dbaa5ebb0ad1c GIT binary patch literal 1486 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabRA=0VDb;}332@o1PuQh8X8uGu4-^- zn3*=SA+%wV=cI;&hMB$%-Lc(MLmN8%IwwUpbcA;F2Q;)twND9bn-tpC9oR4-vb8I; zp+Bg#FSKP+P(yD-%f!%zo}iZgh=%Ua=KkP@iNVbiLYsSn8~VeW`+}P$1~&ACHcbd_ z=nZb_32o{NZkZ6&1k^hrxT!a&r8lIdAIJ@9nh?|iRMsET+#A%`AKKg-(%2K)*cZ|~ zA-J&*$PEUH08MM`4Q=iVY3vVfhN$TUscGs5sR8P31X|G_+SnTcv<0XVC=Rr!u|K4# zHyCJU6UYRR;%2BtKv^I=q#2|DECn_k@wXU|=@ zeD&J(8#iy=zH|5fgNKiwJbm`!<*V0k-oF3v@zdvTKYsrD^Y>rGdNDU(R>|^oaSW-5 z%f0Y2x+hSk{p0&HA;E60Oq1S3r7U;(X3MH#?W9nxXzk)srlC4P;cZYe&v*G}mgi%< ze(bTl{@&_)cX8b-kvX%ff9D22e*M~7edm|;|Jb8m2JBzkzKiSBKR0n*?XSyM6fxZY zJ4bb;vGKRiKP(ztq3sJx9iq$Re`-EEQU0KE$sdyqayzp6H-7x+oGKZ_6;hQ_Y|p&y z7o`N7g@ z8Yl1HKL;mFo4BdxljF1}KbfMR6nNdLyPnEap1MqBdI?{g$L58$P8+wftLX5R70)7n`#pwItnbma$w{%`M(`wpsu2rJ60rdGCc> z+~lm)viMYM`=uXwK_$`#_UtRZMO=2-IV)l2vc~)doyPvg-RmxtACQ#v{7}5$&isV+ zPws~n?@yNGy#H1=vTI?;CAl9~9#VgqJ+7`4y~R+Wyj0U+){?lm9r6mtf0gl_VmUW^ z%eje%?jM%^eLuUg#ybXaQM{yQy&8UgiQ`HQ+=iTpjfd0iJ@ ssyFi&dF4%9dEry;pNN)Q>$jg_dr3r;PHA*CFc&d+y85}Sb4q9e0J812W&i*H literal 0 HcmV?d00001 diff --git a/fnordmetric-webui/tests/spec/SpecHelper.js b/fnordmetric-webui/tests/spec/SpecHelper.js new file mode 100644 index 000000000..578b3e862 --- /dev/null +++ b/fnordmetric-webui/tests/spec/SpecHelper.js @@ -0,0 +1,15 @@ +beforeEach(function () { + jasmine.addMatchers({ + toBePlaying: function () { + return { + compare: function (actual, expected) { + var player = actual; + + return { + pass: player.currentlyPlayingSong === expected && player.isPlaying + } + } + }; + } + }); +}); diff --git a/fnordmetric-webui/tests/spec/utilSpec.js b/fnordmetric-webui/tests/spec/utilSpec.js new file mode 100644 index 000000000..496b38039 --- /dev/null +++ b/fnordmetric-webui/tests/spec/utilSpec.js @@ -0,0 +1,665 @@ +/** + * This file is part of the "FnordMetric" project + * Copyright (c) 2014 Laura Schlimmer + * Copyright (c) 2014 Paul Asmuth, Google Inc. + * + * FnordMetric is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License v3.0. You should have received a + * copy of the GNU General Public License along with this program. If not, see + * . + */ + +/** + * + * TODOS: + * + * Query Playground: + * - "embed this query" opens up a popup with html/js/ruby snippets + * - prevent reload/navigation to other page (body onunload) +* - stretch: display a [fake] loading bar + * - nice to have: represent current chart and table, view in url --> renderResultPane + * + * Metric list view: + * - write meaningful error messages + * + * - fix menuitems + * - fix resize tooltip, vertical split + * - wrong URL: redirect and display message + * - fix back and for (push and pop states) + * + */ + + +if (FnordMetric === undefined) { + FnordMetric = {}; +} + +if (FnordMetric.util === undefined) { + FnordMetric.util = {}; +} + +FnordMetric.util.parseQueryString = function(qstr) { + var path; + var query_params = {}; + + if (qstr.indexOf("?") >= 0) { + path = qstr.substr(0, qstr.indexOf("?")) + + var params_str = qstr.substr(qstr.indexOf("?") + 1); + var raw_params = params_str.split('&'); + + /* set first param which defines view's view (metric, search ...) */ + var param = raw_params[0].split('='); + query_params.innerView = decodeURIComponent(param[0]); + query_params.innerViewValue = decodeURIComponent(param[1]); + + for (var i = 1; i < raw_params.length; i++) { + var param = raw_params[i].split('='); + query_params[decodeURIComponent(param[0])] = + decodeURIComponent(param[1]); + } + + } else { + path = qstr; + } + return { + "path": path, + "query_params": query_params + }; +} + +FnordMetric.util.setURLQueryString = function(hash, query_params, encode, push_state) { + var path = "#"+hash+"?"; + + path += query_params.innerView + "="; + path += (encode)? + encodeURIComponent(query_params.innerViewValue) : + query_params.innerViewValue; + + for (var param in query_params) { + if (param != "innerView" && + param != "innerViewValue" && + query_params[param] != undefined && + query_params[param].length > 0) { + + path += + "&" + param + + "=" + query_params[param]; + } + } + + if (push_state) { + window.history.pushState({url:path}, "#", path); + } + window.location.hash = path; +} + +//REMOVE?? +FnordMetric.util.setFragmentURL = function(hash, name, value, encode, push_state) { + var path = window.location.pathname; + var value = value; + if (encode == true) { + value = encodeURIComponent(value); + } + var hash = + path + "#" + hash + "?" + name + "=" + value; + window.location = hash; + if (push_state == true) { + window.history.pushState({url: hash}, "#", hash); + } +} + + +FnordMetric.util.convertArrayToString = function(array) { + var string = ""; + if (array.length > 0) { + string = array.join(", "); + } + return string; +} + +/* simple loader foreground */ +FnordMetric.util.displayLoader = function(elem) { + elem.innerHTML = "
"; +} + +/* + * loader foreground if loader can't be + * destroyed with resetting the innerHTML +*/ +FnordMetric.util.Loader = function() { + var loader = document.createElement("div"); + loader.className = "load_foreground"; + loader.innerHTML = + ""; + on_click = null; + + function onClick(on_click_new) { + on_click = on_click_new; + } + + function display(elem) { + elem.appendChild(loader); + if (on_click != null) { + loader.onclick = on_click; + } + } + + function destroy(elem) { + //FIXME + loader = elem.querySelector(".load_foreground"); + elem.removeChild(loader); + } + + return { + "display" : display, + "destroy" : destroy, + "onClick" : onClick + } +} + + +FnordMetric.util.displayErrorMessage = function(elem, msg) { + elem.innerHTML = "
" + msg + "
"; // XSS! +} + + + + +FnordMetric.util.renderPageHeader = function(text, elem) { + var header = document.createElement("h1"); + header.className = "page_header"; + header.innerHTML = text; + + elem.appendChild(header); +} + +FnordMetric.util.parseTimestamp = function(timestamp) { + if (timestamp == 0) { + return "0"; + } + + var time_str; + var timestamp = timestamp / 1000; + var now = Date.now(); + var date = new Date(timestamp); + + var offset = Math.floor( + (now - timestamp) / 1000); + if (offset < 60) { + var label = (offset == 1)? " second ago" : " seconds ago"; + time_str = offset + label; + } else if (offset < 3600) { + var time = Math.floor(offset / 60); + var label = (time == 1)? " minute ago" : " minutes ago"; + time_str = time + label; + } else if (offset < 86400) { + var time = Math.floor(offset / 3600); + var label = (time == 1)? " hour ago" : " hours ago"; + time_str = time + label; + } else { + var time = Math.floor(offset / 86400); + var label = (time == 1)? " day ago" : " days ago"; + time_str = time + label; + } + + var months = ["Jan", "Feb", "Mar", "Apr", "May", + "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] + + var minutes = date.getMinutes(); + if (minutes < 10) { + minutes = "0" + minutes; + } + + var seconds = date.getSeconds(); + if (seconds < 10) { + seconds = "0" + seconds; + } + + time_str += + " - " + months[date.getMonth()] + + " " + date.getDate() + + " " + date.getFullYear() + + " " + date.getHours() + + ":" + minutes + + ":" + seconds + + return time_str; +} + +FnordMetric.util.parseMilliTS = function(ts) { + if (ts < 1000) { + if (ts == 0) { + return " less than 1 millisecond"; + } + if (ts == 1) { + return " 1 millisecond"; + } + return ts + " milliseconds"; + } + + if (ts < 60000) { + ts = ts / 1000; + return (ts + (ts == 1? " second" : " seconds")); + } + + if (ts < 3600000){ + ts = ts / 60000; + return (ts + (ts == 1? " minute" : " minutes")); + } + + ts = ts / 3600000; + return (ts + (ts == 1? " hour" : " hours")); +} + +FnordMetric.util.humanDateToMikroTS = function(date) { + /* first version until datepicker is implemented */ + var ts; + if (date == "NOW") { + ts = Date.now(); + } + return ts; +} + + +FnordMetric.util.humanCountRows = function(tables) { + var num = 0; + tables.map(function(table) { + num += table.rows.length; + }); + return (num == 1? num + " row" : num + " rows") +} + + +FnordMetric.util.sortMetricList = function(metrics, column_index, order) { + function compare(a, b) { + if (a < b) { + if (order == "asc") { + return -1; + } else { + return 1; + } + } + if (a > b) { + if (order == "asc") { + return 1; + } else { + return -1; + } + } + return 0; + } + + var sorted_metrics = metrics; + column_index = parseInt(column_index); + switch (column_index) { + case 0: + sorted_metrics.sort(function(a, b) { + return (compare( + a[column_index].toLowerCase(), + b[column_index].toLowerCase())); + }); + break; + case 2: + sorted_metrics.sort(function(a, b) { + return (compare( + a[column_index].toLowerCase(), + b[column_index].toLowerCase())); + }); + break; + case 3: + sorted_metrics.sort(function(a, b) { + return (compare( + a[column_index], b[column_index])); + }); + break; + default: + break; + } +} + +FnordMetric.util.getHorizontalEditorHeight = function( + editor_height, result_height) { + var default_height = (window.innerHeight - 49); + editor_height = Math.max(editor_height, default_height); + var height = Math.max(editor_height, result_height); + return height; +} + +FnordMetric.util.getHorizontalEditorWidth = + function(editor_width) { + //returns the percental editor width + var wdn_width = window.innerWidth; + if (editor_width > 0) { + editor_width = wdn_width / editor_width; + } + var width = Math.max(35, Math.min(50, editor_width)); + return width; +} + + +FnordMetric.createButton = function(href, class_name, inner_HTML) { + var button = document.createElement("a"); + button.href = "#"; + if (class_name !== undefined) { + button.className = class_name; + } + if (inner_HTML !== undefined) { + button.innerHTML = inner_HTML; + } + return button; +} + +FnordMetric.util.searchMetricList = function(metrics, search_item) { + //FIXME works but seems not to be the best solution + var data = []; + metrics.map(function(item) { + if (item.key.indexOf(search_item) > -1) { + data.push(item); + } + }); + return data; +} + +FnordMetric.util.htmlEscape = function(str) { + return str; +} + + +/* returns all words that includes filter */ +FnordMetric.util.filterStringArray = function(strings, filter, limit) { + //FIXME ? + var data = []; + strings.map(function(string) { + if (string.indexOf(filter) > -1 && limit > 0) { + data.push(string); + limit--; + } + }); + return data; +} + +FnordMetric.util.toMilliSeconds = function(timestr) { + var time = timestr.split(/([a-z])/); + var conversion = { + "s" : 1000, + "m" : 60000, + "h" : 3600000, + "d" : 86400000 + } + var seconds = time[0] * conversion[time[1]]; + return parseInt(seconds, 10); +} + +FnordMetric.util.milliSecondsToTimeString = function(seconds) { + if (seconds < 60000) { + return (seconds / 1000) + "s"; + } + if (seconds < 3600000) { + return (seconds / 60000) + "m"; + } + return (seconds / 3600000) + "h"; +} + + +/* in singleMetricView */ +FnordMetric.util.generateSQLQueryFromParams = function(params) { + //FIX html escape + var table_ref = params.innerViewValue + var view = params.view; + /* column for rollups */ + var columns = params.columns.split(","); + var start_time = Math.round(params.start_time / 1000); + var end_time = Math.round(params.end_time / 1000); + var t_step = params.t_step; + var t_window = params.t_window; + var by = params.by; + + var query; + var draw_stm = + "DRAW LINECHART\n XDOMAIN\n FROM_TIMESTAMP(" + + start_time + "),\n FROM_TIMESTAMP(" + end_time + ")" + + "\n AXIS BOTTOM\n AXIS LEFT;\n\n"; + var select_expr = "SELECT\n time AS x,\n "; + var from_expr = "\n FROM\n"; + var where_expr = ""; + var group_expr = ""; + var hasAggregation = false; + + + /* complete select_expr */ + if (view == "value") { + select_expr += "value as y "; + } else if (view == "rollup_sum" || view == "rollup_count" || view == "rollup_mean") { + draw_stm = "DRAW BARCHART\n AXIS BOTTOM\n AXIS LEFT;"; + var func = (view.split("_"))[1]; + + /* if the metric hasn't any labels total is selected */ + var column = (columns[0].length > 0)? + ("`" + columns[0] + "`") : "'total'"; + + select_expr = + " SELECT " + column + " AS X, " + func + "(value) AS Y"; + + hasAggregation = true; + } else { + select_expr += + view.toLowerCase() + "(value) AS Y"; + hasAggregation = true; + } + if (by != undefined && by.length > 0) { + var series = by.replace(/,/g, " + ',' + "); + select_expr += ", " + series + " AS series"; + } + + /* complete from_expr */ + from_expr += " `" + table_ref + "`\n"; + + /*complete where_expr */ + if (start_time != undefined && end_time != undefined) { + where_expr = + " WHERE\n time > FROM_TIMESTAMP(" + start_time + ")\n" + + " AND time < FROM_TIMESTAMP(" + end_time + ")"; + } + + + /*complete group_expr if an aggregate function is selected */ + if (hasAggregation) { + group_expr = " GROUP "; + var hasGroupStm = false; + + if (t_step != undefined) { + hasGroupStm = true; + + group_expr += + "OVER TIMEWINDOW(time, " + Math.round(t_step / 1000) + ","; + + group_expr += (t_window != undefined)? + Math.round(t_window / 1000) : Math.round(t_step / 1000); + + group_expr+= ")"; + + } + + if (by != undefined && by.length > 0) { + hasGroupStm = true; + + group_expr += " BY " + by; + } + + /* aggregate function without group_by statement */ + if (!hasGroupStm) { + group_expr = ""; + } + } + + + query = + draw_stm + select_expr + from_expr + + where_expr + group_expr + ";"; + + return query; +} + + +FnordMetric.util.getMonthStr = function(index) { + var months = [ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December"]; + + return months[index]; +} + +FnordMetric.util.isNumKey = function(keycode) { + return ( + (keycode >= 48 && keycode <= 57) || (keycode >= 96 && keycode <= 105)); +} + +/* tab, arrow-left, arrow-right, deletekeys */ +FnordMetric.util.isNavKey = function(keycode) { + return ( + keycode == 8 || + keycode == 9 || + keycode == 37 || + keycode == 39 || + keycode == 46); +} + + +FnordMetric.util.validatedTimeInput = function (time_input, type) { + var input = time_input.value; + + time_input.addEventListener('keydown', function(e) { + if (FnordMetric.util.isNumKey(e.keyCode)) { + var n = String.fromCharCode(e.keyCode); + input = time_input.value; + + if (type == "hour") { + if (input.length == 0) { + if (n >= 0 && n <= 2) { + input = n; + time_input.value = n; + } else{ + e.preventDefault(); + } + } else if (input.length == 1) { + console.log(input); + if (input < 2 || (input == 2 && n < 4)) { + input = input * 10 + n; + time_input.value += n; + } else { + e.preventDefault(); + } + } else { + e.preventDefault(); + } + + } else if (type == "minute") { + if (input.length == 0) { + if (n >= 0 && n <= 5) { + input = n; + time_input.value = n; + } else { + e.preventDefault(); + } + } else if (input.length == 1) { + input = input * 10 + n; + time_input.value += n; + } else { + e.preventDefault(); + } + } else { + e.preventDefault(); + } + } + + if (!FnordMetric.util.isNavKey(e.keyCode)) { + e.preventDefault(); + } + }, false); + +} + +FnordMetric.util.appendLeadingZero = function (num) { + var num = num; + if (typeof num == 'string') { + return (num.length > 1)? num : "0" + num; + } + return (num > 9)? num : "0" + num; +} + + +/* returns mm/dd/yyyy hh:mm */ +FnordMetric.util.getDateTimeString = function(timestamp) { + var timestamp = timestamp == undefined? + new Date() : new Date(parseInt(timestamp, 10)); + + var month = timestamp.getMonth(); + month = FnordMetric.util.appendLeadingZero(month +1); + var day = FnordMetric.util.appendLeadingZero(timestamp.getDate()); + var hours = FnordMetric.util.appendLeadingZero(timestamp.getHours()); + var minutes = FnordMetric.util.appendLeadingZero(timestamp.getMinutes()); + return ( + month + "/" + day + "/" + + timestamp.getFullYear() + " " + hours + + ":" + minutes); +} + +FnordMetric.util.makeLowerCaseUnderscore = function(string) { + return (string.toLowerCase().replace(/ /g,"_")); +} + +FnordMetric.util.reverseLowerCaseUnderscore = function(string) { + var str = string[0].toUpperCase(); + for (var i = 1; i < string.length; i++) { + if (string[i] == "_") { + str += " " + string[i+1].toUpperCase(); + i++; + } else { + str += string[i]; + } + } + return str; +} + +FnordMetric.util.removeFromString = function(start, end, str) { + var length = str.length; + if (end >= length) { + return ""; + } + + var res = str.substr(0, length - start); + res += str.substr(end, length-1); + return res; +} + +FnordMetric.util.renderFlyout = function(text, elem, left) { + var flyout = document.createElement("div"); + flyout.className = "hover_tooltip"; + flyout.innerHTML = text; + flyout.style.top = "85px"; + flyout.style.left = left + "px"; + elem.appendChild(flyout); + return flyout; +} + + +FnordMetric.util.removeIfChild = function(child_n, parent_n) { + if (typeof child_n == 'object' && child_n.parentNode == parent_n) { + parent_n.removeChild(child_n); + } +} + + + + diff --git a/fnordmetric-webui/tests/src/util.js b/fnordmetric-webui/tests/src/util.js new file mode 100644 index 000000000..496b38039 --- /dev/null +++ b/fnordmetric-webui/tests/src/util.js @@ -0,0 +1,665 @@ +/** + * This file is part of the "FnordMetric" project + * Copyright (c) 2014 Laura Schlimmer + * Copyright (c) 2014 Paul Asmuth, Google Inc. + * + * FnordMetric is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License v3.0. You should have received a + * copy of the GNU General Public License along with this program. If not, see + * . + */ + +/** + * + * TODOS: + * + * Query Playground: + * - "embed this query" opens up a popup with html/js/ruby snippets + * - prevent reload/navigation to other page (body onunload) +* - stretch: display a [fake] loading bar + * - nice to have: represent current chart and table, view in url --> renderResultPane + * + * Metric list view: + * - write meaningful error messages + * + * - fix menuitems + * - fix resize tooltip, vertical split + * - wrong URL: redirect and display message + * - fix back and for (push and pop states) + * + */ + + +if (FnordMetric === undefined) { + FnordMetric = {}; +} + +if (FnordMetric.util === undefined) { + FnordMetric.util = {}; +} + +FnordMetric.util.parseQueryString = function(qstr) { + var path; + var query_params = {}; + + if (qstr.indexOf("?") >= 0) { + path = qstr.substr(0, qstr.indexOf("?")) + + var params_str = qstr.substr(qstr.indexOf("?") + 1); + var raw_params = params_str.split('&'); + + /* set first param which defines view's view (metric, search ...) */ + var param = raw_params[0].split('='); + query_params.innerView = decodeURIComponent(param[0]); + query_params.innerViewValue = decodeURIComponent(param[1]); + + for (var i = 1; i < raw_params.length; i++) { + var param = raw_params[i].split('='); + query_params[decodeURIComponent(param[0])] = + decodeURIComponent(param[1]); + } + + } else { + path = qstr; + } + return { + "path": path, + "query_params": query_params + }; +} + +FnordMetric.util.setURLQueryString = function(hash, query_params, encode, push_state) { + var path = "#"+hash+"?"; + + path += query_params.innerView + "="; + path += (encode)? + encodeURIComponent(query_params.innerViewValue) : + query_params.innerViewValue; + + for (var param in query_params) { + if (param != "innerView" && + param != "innerViewValue" && + query_params[param] != undefined && + query_params[param].length > 0) { + + path += + "&" + param + + "=" + query_params[param]; + } + } + + if (push_state) { + window.history.pushState({url:path}, "#", path); + } + window.location.hash = path; +} + +//REMOVE?? +FnordMetric.util.setFragmentURL = function(hash, name, value, encode, push_state) { + var path = window.location.pathname; + var value = value; + if (encode == true) { + value = encodeURIComponent(value); + } + var hash = + path + "#" + hash + "?" + name + "=" + value; + window.location = hash; + if (push_state == true) { + window.history.pushState({url: hash}, "#", hash); + } +} + + +FnordMetric.util.convertArrayToString = function(array) { + var string = ""; + if (array.length > 0) { + string = array.join(", "); + } + return string; +} + +/* simple loader foreground */ +FnordMetric.util.displayLoader = function(elem) { + elem.innerHTML = "
"; +} + +/* + * loader foreground if loader can't be + * destroyed with resetting the innerHTML +*/ +FnordMetric.util.Loader = function() { + var loader = document.createElement("div"); + loader.className = "load_foreground"; + loader.innerHTML = + ""; + on_click = null; + + function onClick(on_click_new) { + on_click = on_click_new; + } + + function display(elem) { + elem.appendChild(loader); + if (on_click != null) { + loader.onclick = on_click; + } + } + + function destroy(elem) { + //FIXME + loader = elem.querySelector(".load_foreground"); + elem.removeChild(loader); + } + + return { + "display" : display, + "destroy" : destroy, + "onClick" : onClick + } +} + + +FnordMetric.util.displayErrorMessage = function(elem, msg) { + elem.innerHTML = "
" + msg + "
"; // XSS! +} + + + + +FnordMetric.util.renderPageHeader = function(text, elem) { + var header = document.createElement("h1"); + header.className = "page_header"; + header.innerHTML = text; + + elem.appendChild(header); +} + +FnordMetric.util.parseTimestamp = function(timestamp) { + if (timestamp == 0) { + return "0"; + } + + var time_str; + var timestamp = timestamp / 1000; + var now = Date.now(); + var date = new Date(timestamp); + + var offset = Math.floor( + (now - timestamp) / 1000); + if (offset < 60) { + var label = (offset == 1)? " second ago" : " seconds ago"; + time_str = offset + label; + } else if (offset < 3600) { + var time = Math.floor(offset / 60); + var label = (time == 1)? " minute ago" : " minutes ago"; + time_str = time + label; + } else if (offset < 86400) { + var time = Math.floor(offset / 3600); + var label = (time == 1)? " hour ago" : " hours ago"; + time_str = time + label; + } else { + var time = Math.floor(offset / 86400); + var label = (time == 1)? " day ago" : " days ago"; + time_str = time + label; + } + + var months = ["Jan", "Feb", "Mar", "Apr", "May", + "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] + + var minutes = date.getMinutes(); + if (minutes < 10) { + minutes = "0" + minutes; + } + + var seconds = date.getSeconds(); + if (seconds < 10) { + seconds = "0" + seconds; + } + + time_str += + " - " + months[date.getMonth()] + + " " + date.getDate() + + " " + date.getFullYear() + + " " + date.getHours() + + ":" + minutes + + ":" + seconds + + return time_str; +} + +FnordMetric.util.parseMilliTS = function(ts) { + if (ts < 1000) { + if (ts == 0) { + return " less than 1 millisecond"; + } + if (ts == 1) { + return " 1 millisecond"; + } + return ts + " milliseconds"; + } + + if (ts < 60000) { + ts = ts / 1000; + return (ts + (ts == 1? " second" : " seconds")); + } + + if (ts < 3600000){ + ts = ts / 60000; + return (ts + (ts == 1? " minute" : " minutes")); + } + + ts = ts / 3600000; + return (ts + (ts == 1? " hour" : " hours")); +} + +FnordMetric.util.humanDateToMikroTS = function(date) { + /* first version until datepicker is implemented */ + var ts; + if (date == "NOW") { + ts = Date.now(); + } + return ts; +} + + +FnordMetric.util.humanCountRows = function(tables) { + var num = 0; + tables.map(function(table) { + num += table.rows.length; + }); + return (num == 1? num + " row" : num + " rows") +} + + +FnordMetric.util.sortMetricList = function(metrics, column_index, order) { + function compare(a, b) { + if (a < b) { + if (order == "asc") { + return -1; + } else { + return 1; + } + } + if (a > b) { + if (order == "asc") { + return 1; + } else { + return -1; + } + } + return 0; + } + + var sorted_metrics = metrics; + column_index = parseInt(column_index); + switch (column_index) { + case 0: + sorted_metrics.sort(function(a, b) { + return (compare( + a[column_index].toLowerCase(), + b[column_index].toLowerCase())); + }); + break; + case 2: + sorted_metrics.sort(function(a, b) { + return (compare( + a[column_index].toLowerCase(), + b[column_index].toLowerCase())); + }); + break; + case 3: + sorted_metrics.sort(function(a, b) { + return (compare( + a[column_index], b[column_index])); + }); + break; + default: + break; + } +} + +FnordMetric.util.getHorizontalEditorHeight = function( + editor_height, result_height) { + var default_height = (window.innerHeight - 49); + editor_height = Math.max(editor_height, default_height); + var height = Math.max(editor_height, result_height); + return height; +} + +FnordMetric.util.getHorizontalEditorWidth = + function(editor_width) { + //returns the percental editor width + var wdn_width = window.innerWidth; + if (editor_width > 0) { + editor_width = wdn_width / editor_width; + } + var width = Math.max(35, Math.min(50, editor_width)); + return width; +} + + +FnordMetric.createButton = function(href, class_name, inner_HTML) { + var button = document.createElement("a"); + button.href = "#"; + if (class_name !== undefined) { + button.className = class_name; + } + if (inner_HTML !== undefined) { + button.innerHTML = inner_HTML; + } + return button; +} + +FnordMetric.util.searchMetricList = function(metrics, search_item) { + //FIXME works but seems not to be the best solution + var data = []; + metrics.map(function(item) { + if (item.key.indexOf(search_item) > -1) { + data.push(item); + } + }); + return data; +} + +FnordMetric.util.htmlEscape = function(str) { + return str; +} + + +/* returns all words that includes filter */ +FnordMetric.util.filterStringArray = function(strings, filter, limit) { + //FIXME ? + var data = []; + strings.map(function(string) { + if (string.indexOf(filter) > -1 && limit > 0) { + data.push(string); + limit--; + } + }); + return data; +} + +FnordMetric.util.toMilliSeconds = function(timestr) { + var time = timestr.split(/([a-z])/); + var conversion = { + "s" : 1000, + "m" : 60000, + "h" : 3600000, + "d" : 86400000 + } + var seconds = time[0] * conversion[time[1]]; + return parseInt(seconds, 10); +} + +FnordMetric.util.milliSecondsToTimeString = function(seconds) { + if (seconds < 60000) { + return (seconds / 1000) + "s"; + } + if (seconds < 3600000) { + return (seconds / 60000) + "m"; + } + return (seconds / 3600000) + "h"; +} + + +/* in singleMetricView */ +FnordMetric.util.generateSQLQueryFromParams = function(params) { + //FIX html escape + var table_ref = params.innerViewValue + var view = params.view; + /* column for rollups */ + var columns = params.columns.split(","); + var start_time = Math.round(params.start_time / 1000); + var end_time = Math.round(params.end_time / 1000); + var t_step = params.t_step; + var t_window = params.t_window; + var by = params.by; + + var query; + var draw_stm = + "DRAW LINECHART\n XDOMAIN\n FROM_TIMESTAMP(" + + start_time + "),\n FROM_TIMESTAMP(" + end_time + ")" + + "\n AXIS BOTTOM\n AXIS LEFT;\n\n"; + var select_expr = "SELECT\n time AS x,\n "; + var from_expr = "\n FROM\n"; + var where_expr = ""; + var group_expr = ""; + var hasAggregation = false; + + + /* complete select_expr */ + if (view == "value") { + select_expr += "value as y "; + } else if (view == "rollup_sum" || view == "rollup_count" || view == "rollup_mean") { + draw_stm = "DRAW BARCHART\n AXIS BOTTOM\n AXIS LEFT;"; + var func = (view.split("_"))[1]; + + /* if the metric hasn't any labels total is selected */ + var column = (columns[0].length > 0)? + ("`" + columns[0] + "`") : "'total'"; + + select_expr = + " SELECT " + column + " AS X, " + func + "(value) AS Y"; + + hasAggregation = true; + } else { + select_expr += + view.toLowerCase() + "(value) AS Y"; + hasAggregation = true; + } + if (by != undefined && by.length > 0) { + var series = by.replace(/,/g, " + ',' + "); + select_expr += ", " + series + " AS series"; + } + + /* complete from_expr */ + from_expr += " `" + table_ref + "`\n"; + + /*complete where_expr */ + if (start_time != undefined && end_time != undefined) { + where_expr = + " WHERE\n time > FROM_TIMESTAMP(" + start_time + ")\n" + + " AND time < FROM_TIMESTAMP(" + end_time + ")"; + } + + + /*complete group_expr if an aggregate function is selected */ + if (hasAggregation) { + group_expr = " GROUP "; + var hasGroupStm = false; + + if (t_step != undefined) { + hasGroupStm = true; + + group_expr += + "OVER TIMEWINDOW(time, " + Math.round(t_step / 1000) + ","; + + group_expr += (t_window != undefined)? + Math.round(t_window / 1000) : Math.round(t_step / 1000); + + group_expr+= ")"; + + } + + if (by != undefined && by.length > 0) { + hasGroupStm = true; + + group_expr += " BY " + by; + } + + /* aggregate function without group_by statement */ + if (!hasGroupStm) { + group_expr = ""; + } + } + + + query = + draw_stm + select_expr + from_expr + + where_expr + group_expr + ";"; + + return query; +} + + +FnordMetric.util.getMonthStr = function(index) { + var months = [ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December"]; + + return months[index]; +} + +FnordMetric.util.isNumKey = function(keycode) { + return ( + (keycode >= 48 && keycode <= 57) || (keycode >= 96 && keycode <= 105)); +} + +/* tab, arrow-left, arrow-right, deletekeys */ +FnordMetric.util.isNavKey = function(keycode) { + return ( + keycode == 8 || + keycode == 9 || + keycode == 37 || + keycode == 39 || + keycode == 46); +} + + +FnordMetric.util.validatedTimeInput = function (time_input, type) { + var input = time_input.value; + + time_input.addEventListener('keydown', function(e) { + if (FnordMetric.util.isNumKey(e.keyCode)) { + var n = String.fromCharCode(e.keyCode); + input = time_input.value; + + if (type == "hour") { + if (input.length == 0) { + if (n >= 0 && n <= 2) { + input = n; + time_input.value = n; + } else{ + e.preventDefault(); + } + } else if (input.length == 1) { + console.log(input); + if (input < 2 || (input == 2 && n < 4)) { + input = input * 10 + n; + time_input.value += n; + } else { + e.preventDefault(); + } + } else { + e.preventDefault(); + } + + } else if (type == "minute") { + if (input.length == 0) { + if (n >= 0 && n <= 5) { + input = n; + time_input.value = n; + } else { + e.preventDefault(); + } + } else if (input.length == 1) { + input = input * 10 + n; + time_input.value += n; + } else { + e.preventDefault(); + } + } else { + e.preventDefault(); + } + } + + if (!FnordMetric.util.isNavKey(e.keyCode)) { + e.preventDefault(); + } + }, false); + +} + +FnordMetric.util.appendLeadingZero = function (num) { + var num = num; + if (typeof num == 'string') { + return (num.length > 1)? num : "0" + num; + } + return (num > 9)? num : "0" + num; +} + + +/* returns mm/dd/yyyy hh:mm */ +FnordMetric.util.getDateTimeString = function(timestamp) { + var timestamp = timestamp == undefined? + new Date() : new Date(parseInt(timestamp, 10)); + + var month = timestamp.getMonth(); + month = FnordMetric.util.appendLeadingZero(month +1); + var day = FnordMetric.util.appendLeadingZero(timestamp.getDate()); + var hours = FnordMetric.util.appendLeadingZero(timestamp.getHours()); + var minutes = FnordMetric.util.appendLeadingZero(timestamp.getMinutes()); + return ( + month + "/" + day + "/" + + timestamp.getFullYear() + " " + hours + + ":" + minutes); +} + +FnordMetric.util.makeLowerCaseUnderscore = function(string) { + return (string.toLowerCase().replace(/ /g,"_")); +} + +FnordMetric.util.reverseLowerCaseUnderscore = function(string) { + var str = string[0].toUpperCase(); + for (var i = 1; i < string.length; i++) { + if (string[i] == "_") { + str += " " + string[i+1].toUpperCase(); + i++; + } else { + str += string[i]; + } + } + return str; +} + +FnordMetric.util.removeFromString = function(start, end, str) { + var length = str.length; + if (end >= length) { + return ""; + } + + var res = str.substr(0, length - start); + res += str.substr(end, length-1); + return res; +} + +FnordMetric.util.renderFlyout = function(text, elem, left) { + var flyout = document.createElement("div"); + flyout.className = "hover_tooltip"; + flyout.innerHTML = text; + flyout.style.top = "85px"; + flyout.style.left = left + "px"; + elem.appendChild(flyout); + return flyout; +} + + +FnordMetric.util.removeIfChild = function(child_n, parent_n) { + if (typeof child_n == 'object' && child_n.parentNode == parent_n) { + parent_n.removeChild(child_n); + } +} + + + + From a45bbdc1073a8fd3194ce8d9ffcce7f2db9c3423 Mon Sep 17 00:00:00 2001 From: Laura Schlimmer Date: Wed, 19 Nov 2014 13:24:01 +0000 Subject: [PATCH 13/15] update gitignore --- fnordmetric-webui/.gitignore | 1 + fnordmetric-webui/tests/MIT.LICENSE | 20 - fnordmetric-webui/tests/SpecRunner.html | 34 - .../tests/jasmine-standalone-2.1.2.zip | Bin 41800 -> 0 bytes .../tests/lib/jasmine-2.1.2/boot.js | 120 - .../tests/lib/jasmine-2.1.2/console.js | 190 -- .../tests/lib/jasmine-2.1.2/jasmine-html.js | 404 --- .../tests/lib/jasmine-2.1.2/jasmine.css | 62 - .../tests/lib/jasmine-2.1.2/jasmine.js | 2909 ----------------- .../lib/jasmine-2.1.2/jasmine_favicon.png | Bin 1486 -> 0 bytes fnordmetric-webui/tests/spec/SpecHelper.js | 15 - fnordmetric-webui/tests/spec/utilSpec.js | 665 ---- fnordmetric-webui/tests/src/util.js | 665 ---- 13 files changed, 1 insertion(+), 5084 deletions(-) delete mode 100644 fnordmetric-webui/tests/MIT.LICENSE delete mode 100644 fnordmetric-webui/tests/SpecRunner.html delete mode 100644 fnordmetric-webui/tests/jasmine-standalone-2.1.2.zip delete mode 100644 fnordmetric-webui/tests/lib/jasmine-2.1.2/boot.js delete mode 100644 fnordmetric-webui/tests/lib/jasmine-2.1.2/console.js delete mode 100644 fnordmetric-webui/tests/lib/jasmine-2.1.2/jasmine-html.js delete mode 100644 fnordmetric-webui/tests/lib/jasmine-2.1.2/jasmine.css delete mode 100644 fnordmetric-webui/tests/lib/jasmine-2.1.2/jasmine.js delete mode 100644 fnordmetric-webui/tests/lib/jasmine-2.1.2/jasmine_favicon.png delete mode 100644 fnordmetric-webui/tests/spec/SpecHelper.js delete mode 100644 fnordmetric-webui/tests/spec/utilSpec.js delete mode 100644 fnordmetric-webui/tests/src/util.js diff --git a/fnordmetric-webui/.gitignore b/fnordmetric-webui/.gitignore index 141bce8de..98c551dbd 100644 --- a/fnordmetric-webui/.gitignore +++ b/fnordmetric-webui/.gitignore @@ -1 +1,2 @@ fnordmetric-webui.js +tests/ diff --git a/fnordmetric-webui/tests/MIT.LICENSE b/fnordmetric-webui/tests/MIT.LICENSE deleted file mode 100644 index aff8ed47a..000000000 --- a/fnordmetric-webui/tests/MIT.LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -Copyright (c) 2008-2014 Pivotal Labs - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/fnordmetric-webui/tests/SpecRunner.html b/fnordmetric-webui/tests/SpecRunner.html deleted file mode 100644 index 25eacbf3f..000000000 --- a/fnordmetric-webui/tests/SpecRunner.html +++ /dev/null @@ -1,34 +0,0 @@ - - - - - Jasmine Spec Runner v2.1.2 - - - - - - - - - - - - - - - - - - - describe("A suite is just a function", function() { - var a; - - it("and so is a spec", function() { - a = true; - - expect(a).toBe(true); - }); - }); - - diff --git a/fnordmetric-webui/tests/jasmine-standalone-2.1.2.zip b/fnordmetric-webui/tests/jasmine-standalone-2.1.2.zip deleted file mode 100644 index c2bab5afc90e46fd4ce5f7c8dbd5d5503abd3842..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41800 zcmaHyQ>-vb6rGQ4+qP}nwr$(CZQHi-AKSKV_4c7n)1;kbl0C^huB`06<{JfRU=S1l z2nYy(FgFKLfd6Ab|5wXOs?f{`39Vom*eZ_<@fo9FZO8a z!z(PuN+&7Fiy!4Q5@Ue(=D9q=o}mhaOpA93eAA3F@P^|2LW z!b`%^E)JF4DUNOQ@~=WeU6#de27T(DrV z5La2gcLX&*^yc!)Y<^6(2ojGLx@C0l9~^lxh&z*pyl4bM&zgrr2F;`dd4>vGtr+Q*#s3;MIpnsb@qwf#mdoz5~5$hisL&n>}+$y!`nA}CFc^|{bj^Y=qpx?DA`7q3{X*&dYE#~ z9K@--`=$@2Ri>^}3_wbDYzAro9dN&odgPHkyHi@K1||%wnhsjz0Uc>JPGb58g5Ek7 z78qEUDX2g~3{;~OceKl_s+du$kU^_4v`QOJqQGZKqmU{fRcf?RMua#KODigloI#@J zJd}z+=_=RZB}P#c)V~9+M4^a^B*J#3FX)r~%Qc~=2BlU!MuA6Xp~TQcc@7#<&=g$f z3eBmRly)E&EHI1&w7YGi#)?l6zfY$m^9af{38Ie7rz}gkZ0j;bl?{|o3l&OJD)6a5 zW*0K&xpwqYgj5w@YeyeUR9FWS-s?F|L8%Cx8|H`QC-VnXkOqg4Cn%!z0s;Ua1pxq1 z_}`KZ|Gy+l`k!QNERE=`44rK)?M!Kz=osmk{?|$0%+Sr!*xruL!Or{&=HB`2B~!kz~B-79@6m#9t7qO8w*P8vCy zBrLNJcBgSiSs0t^hl^a4-6h2B8Jwj>yj333CL3jQ6?dJ1u&I^0rI5d(mA=JPNkQ0N zWNKtSEUzH#HzMqI{`7Z0EOKxLSt#2(2>Ww5`zt7$42%R+{ zuwo2g>x=^F10#b?1hFUuQ-3gWK+5ZhNd)}{U}9uuXliV3aCCNfdVGF>f`*EWj*ya; zn3|lPprWLzuCTJUxVpT;#>mRd&d}7>+T7mY;^gM)?C$XN`1<^R0SWW>5fc^X8S5SI zAtlEt%Pr3}*E!ogLx)L^RhL&c z!Qt`wJif1oC)I2AyM2H64@+nE{C>ZmPjC7oUX1JpYLkyHjxd!GQ@!_0UE~U7KSy88 zgoAcZy0Hzf+S7B`1VWrbXV2et~#bM7DJX=lbge%FmXzq^;-?v6Xu ziJZ+<{%2tPem|R9zGvw#f9u71VAuLvebHCT{OpdpH}zBMD2&^G+~TgKjSubf{4`iP zg*VXBxJ6UP{#Jb4ioXR;llaN7PTWnO?0(}di-Fg31^15V9y&&~-#!o}rQUcfj9&Zyq2+vPislf0g2U z3X(2@ytS8ga6FPjyCy-{PnJ`to-- zjx{s%eAEitt`(8O7vXk!EOJ7p@N9B87+)SNr{PxexqiEQkj?zH##fqs z9FfoYy0zXM!`ti?EHhg>yJ%SL{&LfXVgbJBeMEQZM}G5mdFrC;SFt~#d!ESZcpfO< z=0~JyU$y5OdP|C=$XU9uGN3bhbbWT#I7?Xo{pY0M5EuTRg5aP40JHz?|F^T^{QsPl z&dPbm*VbK|J?Zr9rM}}fd8ufV>87OH+&#CwcwMOoPuoe_>CFVSUN}-64LQmrIR!VC z`_DJOTrXg#H$CN=gxR-kPKHJgAhb%Tuu6df0RGqE?7_v27r(bpL#?-Y<}x`P{%`F^ z9?$Q){PN>`#)T6@T^_tVn%g~Z?P+Skh%rm9YN4pB;_T(Z^~H-XgXZc$&6ThI%F0VG zR~P2A{;JlBvlcA*%1Uhx-GYHv7fvjB;rJz2&^2sV!`Ty%A^PIu@y0-}_^|t`*uR>+fJDab5+2B1+b;-vFP%~!jrLAOYO<4 zF(-Z&!WcSrc1&IPaDVh@;bg+gz}avxC_|rC0PtbEimrTkf%C66v}R-F!lR{Qy^9$K z@%dr}i530ZLrs1i%7rImwpx@I4|xc}1G*NIyE0hsHh}$nPlM#AuQx+4EhRwoO#KP6 z3q(0!!vQKyGM<0gTv~|m^la(D;}fl1K@4xGmZ)7C#d)P_&A`edTA61m>=lQe5vz!jifpLw|}ED4$dw9ug|wb z`36^Y4j%ru?aky&@}>KR`1^N9?d7fa$R17)N4>(aqu2l2*0n*~_Re4X=WF*jxt5#z z@8R3w?7V`Dqvkb_*T1X%$)tL7%m4d%G`aVuzPf(i?r*=ttLx3?4nrUwp32Rl;MHy3 z?rpX?cX)gc;PF-6TmCJsf2YdrE$$7{8C{^g`fMwF+&f;MXNRvQub;!m&!HvwHh+7A ze|>Ljk5`1Ad(Q1O{_7l7SC!j;IR5fH`=(sJwE6~p;8)?^+vom!Yq+<*pR=xy)8o?w z9KV0F^U1~jtGb+f6E(u?+r|5CL+!=nYvXloE!P6rr>(}?r!n}HuZ=0J)*bN>YAn#fP`q3u;=G7eqN|RQj98nOJ z=^8Xbu7hV|!w*UWBk!lk>2DSvZ`)Is{*OsRCywmJ@F%`{p@)y-r|`4qGFM;LBu=## zzvcbv;n{^@w`O;zvWh*}(=@+!rQH4A(YnX|6i9da{8}9DF$T z@~yu;$?pd_)TZ#BD!eSW;BRels~jNfUky2mmUh~&&dQM}t%R!zjlx|z2Jr0Wq5p5Q z*IToT;35*Y*7qeR$vf9wCk~O#eE4LeBISSUF6OJW(qM$#ejioE`XW&BiU zb6Sc98fYQlIpET1Gd zqW9qiO@vL!|jX3SXdlH-j=(PX57nkx3x zh5E~w+Bc|bLq&Y~$vC+gQ&d)W@WWwO&D%@QSj9{{Jy>$nzGbgQOaCqW>y*`(`jO+u ziHBcnkHzyNQD#Fx{7lZe*BQQgFwi!8F#C(TD{tXr2p~-DR8s$NcWmiU=lj9C5ufkY zX9IJ-{2BJpbUGCGM2u#vb4ML-RuW>^!9n@^S#uSu(2papXK#=>&$+RMFW ztm@Fcs?EhQ&E%k!Qh#lTHSarVAt4&DO($WS&{gyo7%^bX@ zG@ua@89jyK9n1T49Gzy~u>g4O3P@R*a-;j?LJ`5T6$$@kzV6JK0k&3xu>~Ig03cEG zeX82>;CThVhe4lmzm`EHQ(d}7u7m6#smY4=va}Ts-J0BZF!aZqnf}N!1ppN2Zt>`= z4_^lz*zJsgk19LmUqyUXy%)BUuQ7BT1vRUIpgC~$7Vy$%b-|6kZrF$hAIS5ZDvC@~J-prpaoc?9~oTw7FI;-fWNw?A4KR{#h2 zU-u?g=|m&U5`WZJJJrCF#RDCG<2oY>;F;ov$szV7r~-9;qd)@(ff%#*E@&QjVUvX( z-LL`@ES`N6aU_WdI81?8Bq!D*G#5_2NgT4kMam-VY4TK=$bnjFGXNPCg<&D}XxfnJ zRq!-;lKyOD6~gmhUXSM)6F4hVBQlo$*`w&}gHh#7Y9OwfuVUgNg{UZ{VBUV%0SEMNB@oQIV*?@euS>>iz!1w}!7m|-P=qHE24SnI zH(!X{pgo{UPw@|IA-9z)&YE11n^vfbj`9h+jyhPtqCwJ89QU)0j2R!%Q#HCN!55Dz zQsy*M*JbZ6zhCPR$=|v8)cXYG46jt<$|f1-hOz}cTMSjKU>d_UsGJX;KsJIUXRV7w zQeyMQg@AG;5e04n{zwWhuqx)(@&W(Jy{bY0liAICiQ-PkzT3*Fi*|aQ=Ts8Fxu~#3 z-OhmPs{~sy%i{JrKVvIzTbmNxZSf{p$QLyyR$QOr@Jk)Y{F1PiGFwj`O3 zntnN{|Fr-r2VvY`2r@_4$m7Q&X6sd*L8#25ISGdc1jN6ph(0y-FneeNK#g;+PjeO*J)!-cO9s{FDueG6dYKao zZti+Fc!%G!TgB!FAN+L@R{f&$nVA-st{bXvabS(M84ySw#AG}9pkgCAl9`Wd$)pkT zw(o4}aQM#0sm$zfX8 zml+qfK+4n<7~RPdx-2mcakm!yu@Ndv0^Wg_EJQ9>a!G4~O!s{L>swzPs9FiR2@;Hi z5xStDGg~iX{pmtqroLDuI5Io7BuS_n=peNg_zt6Kk}X%ELa#G#3pX`ofXMv!d8q6` zG6I8;NvJUo)|!%pp_O&YgZB^7=Ug8K4cwl5Zbe2fI&*j0Cs<$TFl+-Z?-v2q;54ye zcYc{oQ{tLg-18=B#Yjn#mXLsWHr>C1L?X1}h({KLiwh>y+r-h`p)E&hM5@Xa<$@*A zZ5>irA#wEz1r9wlvVbu#dX9lJ4nTONtQ!QNWF0#;4Dh}|FK0%)$xa6KkUe0VA(_bG zcQ66}h_^e&f)edOJHSWFIB0Dv`QVi^h^pA46kJH@v2bs115T(C1XcvGv7l#)731-NjJ!?E~^rn999JM6xs9j9C92$of0z#>9 z;fS?KQO&{)Au-u-;oJsUPh1gL3B+X#fW&7Wg?UbRLvb14m?gT6Xeahw0p<_PDJo$uv7}qR>@}9+Nf{ zJ@^y;)q(Q_Bf4V-_9N;B_;R6eIq=xjr?aIyb-giq%vmReQsClZ@ovV%kV?g@XmH%nkY!=+=W&)?(wK#a*uU}GxBjVji6+9mxdTJZ8n^S6+}}+OT%fU_79izAXF5n45=9W7R>t##dQC>CJQ(NYrrbrc zmTpxwdEKd+(iqyadT&=E8HTjLU@p0Mv(6qoH$dX_hSe3h0+l5l5`AX`0< z^Wr(wE8iVQ^o;kd35#bVk_2a|eL%{{#0ec?ib@D^>iF>TW>|U&YwvtM7FtA$b}|G- zE^4goG`C8t9Xun_c39;Ku8Acejy0}%eX~`CSTrsOAR=uL-nCJypfdR&=3oiZkJ5af z5?&Cm{!(~^n8TJ%a=K92HVxSV^EEe|9`aA@Tz>^h=|3~gKHmiX6 z#*eB;oxCV1(Th)B7$1X3FUSJ5{-xX0i=Fj$kBQ?4=^@fs*=||Dr@9X6W=KCr&ZI-_ zXd}`3F_tBwS*=?OCghEb@?vtiV`yW4ut4i1F!5>B^o_9jWHZtDU>>j(NgW{h0hOAk zp@JmJ>)hKwhnR#L+CKbFOAPp9!n>3u^?}?!y_v+YtB{5KKo_eD(MMox%8L~fUubAR z`r}SZT8#0=6lp4+<7P?QEl$Hk;tX9GYI*qGaC#6*pqIOnyi8{9-7!prUADd`|d~UJapt(t*EOvqeAw{J(Cm0jy z9TyUJI$0E(5CRT3+kP#6M_9sf1}O>}VX$9iL<^M13Q>olmP+ZsPqb$$)nJ%QC~L;m zA1IiY$cU27BulGIY28ByG)51e0wHu8U$!bS=jKzRL@%k}E2Q!- zj1fiZi8aR_X;eaK@tl~iV5><4)jD$nj`1pzE(+5 zJ{<(kA@Fj#ukg|ycJHk7bHGnkM9HByz7Fk_b*h2^|4Cp}hEMQ!(d*46j!$5;D6=Iy z?v5u5fTI|`ZQ=>OtOG`CBa);xqDYm#o>+P6$yaK9W9<44?{KH;Y0u>)*=IOU0s!7% zBlE3JHH9pu)h>gW$o=sUHm*c2b5H8GOM3=!${4CMUb4uI_e;_MS#o0l^9o4-ZVZHB zoGd;Gkc=WfTGa4~A!8Ejsw%I}=a4c@Dw%E)^3|Jg(@}i7)vY}AVF0vQrZ%N{gH(Yw zsN6~usRy0VbmDxsbn$fc4ADFQd?M$_a|SfxgT!mB_vNngTpmqSxC%9;5W;ahazKS} zH)JL2uTpyfMF-vKFsr%9 zsMTlijKn}%qQRKdwBm< zQEWXTW|URUicNQWKh=#Z8g!>8vg3fjF*4Qqa|zpEav{9kBYVEbcZ#U3$v0u-Lb? za|c21t(y$7kopUg(`7B?%`@TXNh&%zB)pMzF>)@4Ux*p5PC!OPL9FuPDw(% zhcOh!oX_??e9FpNCA*8DvdX*ZhV#ZSUWY8)<4f#vmXB%jWXAhf;X1W3@&GfX3{!cv z)nj(kD;^4cnazi!FyNDGx&(X4wG#6~$365kBmEBub$d(Ep}2Je-F zK9phYqtLSmRVpJI0z;@!pwb%n4i8A_;!-%__hN0Z%Rqp>pcjjgaQRFiZBfaDsP9;5 zsTi>S!hga50`9CIj+Z?y$cSI-x+)r!N2m=@w=hipzwvu@qF39dPNw9LEl$bM9xf3R zzf~K)Np^lkN4ohYjbKSD;2Mq@6s2x{C(gNoX3LT1b}6HrngF1!fvghZm3JBw)X4=W z)hP(*n~LPRpZU`Bx-LV@$)|4NZLaIP*2xGjw7NJQH_FmS71^xl`Y|6nF^M8}z9BG#$6&dS?^co6`$VJ|aLRbEq=YpxNe3kMzb;{l z?w>jXlOs>f5{?qdN6!(BzBH(d&Z~eKC4YA7&vc^i-DQew_RPM>i>hTFCiTUi z9AfOpLg$pc-S(4yTbsJ8)M;lU{j2wc5HC4Zac9%+TC48M5!0G!6+8teC>IE_#3VoOr`3V_84nPN&1vrxsGc#o&@(0ZOqHPDBl_g zHZCN5w3AIV`O6prQ-Hiv{rnwO`i`#_;qYJtW_?#Hh;BLEcYVw8$b=?A@%;=V0b1k{ z1sSq*ted(}^>+=WA=9j=6WY`!pO!K^dVp?|$!1zpM1dePztN1AI3oO`nbao)Xihz) zwqoe&Q5r>RL+Q&27X5+Fe)>?k1**B=M(X0Y-FttlG_2_6>A$KKP^!^5Z9{F$qaj&( zd8P1=lC1jD#~?l8M>cw?@f<4W(48r%<&E|tBZub+!TK9GfWC|Jpp1MeE2cf*)x+ud zGFeQX2o&FyQuO&Jp;*mcgsGA(=logu9 zDleZq@0rAmI7s{KzO%ab;{i=DSy5X|(sOTPbvckNNOE0CRxFjuPu}HmwOZ=(A^|MV zDyUqBkA2l-JZowO5mh%Q70sR8Q%aua63W9FXOC19$cG7CN2IwWr+>{J>t{*G?GG$^ zMF)w>mP80FFzN2^R#X_~9;QMM}78V6J7F;6fP7>c;GHy0!^;Y zSmV|c9~ciX-oDlig7d?YQRgTM3p$Aniq0$u`%*r~s}+UU>zvG~OMD509Vfd50F>oC zl*uP*nTM76dIx!kSwAj$GTuhV=MqbJM1wP}q5+OY|Mf77_@sE4Olxq~gG6%L_=w>8 zGc`da{|Y3Kl8b}1Q^kUsl$^m3sg9M1Ie1hjx?dmQm5^#BH`1T5j+R8IJO%%O?clJZ zQ6fxz=|H5fIZ%MCp{gCvLSccJMOMeKn(_2A`6ATH%=31d4K z%u7TId~BjyKyCpb+paULl4L%C_LEIC)!Z&y*_b+8N!+HqtRSH_J2L={$_W~U)s{$1 zp$2*}?y!+F!~j>#>h#iDDBdOZa*jvt%x~!y4|UwabDvr1?3Bl^z172=wf&+E_hhxd z326*24McYC2#eI0Engpo`%o%7&^mIfLft7kKi4=}6>y8eNL0d|r}SJWMjbrQs#={I zGbbzUSikQgf9p8qmPr~QCii)Q9#ryf9y@orDKSP^wX?S=LLk?#NP7l&Il;_oe<|I5 z#vDALL)`61IT7EiY!@|d5RQqKI& zfwS`Q42n;+IL_)5G3q?oj`4mOG&yrWYUrl(pd|3+$ZUc= z1x@3XH^p~a70@w0W12%O!8+WoqZo8qTQ#bV+}#rD%=g~dN6VVoB$gftb795%+b-xs zJ7#p?+0P%!qi52541{PzMDh@k()$*N%QG&F0v?(SUuy%T0r_A!_ed5ds>UC zw9*zr8Qu}hNxx9q5pv~_RLY(1!w*)zQH1e&$*?WeP`_Ty%3wWBFd|DkONgoyn43dp zozL?8e1{(|NB8a)CGg3!u>tFIk!`>9Vf1Bk49=uw=+c|peaY}&*k;~-Cq0dk`F22% zKFrf}52j0;h`xiN6~C7LJ+&N z>Zt6No%7yZ)h-wQLe}@3M>jkZ$h|%fjRa^y!sg*@RzCumbR3tHWJ*=*49}VC>!%F$ zbnrRQ8K4n=T^kZM9uEL&48jd%@XU7R?jKf z!Lq$tmmwPYF6IL@sgYrQ!ObU{NCTBZia1i9G?p`T*wC3`@HfYU7jCjUDcI8|(jAJ& zpHC@-r?{p1h8riRKFt}8=;npH45@7yU3m=_;_li0P# zKUY#U@1T&c=$@faiGK_CrFH*)d&jC<1?;>(Y7Ao}ayXwZ4Xk{q2li5;kJxE=GDVxK z@V*N_4FkJ#4w(8d4SB~WRSql_;!yv_2jwA(3%f&E)%+WQZHg#gY?eW&>^#FZo2+*u znT8KCf&yhxy}H5Kn88mDz`_3h%Y^c9?Dh#aNAxY0uaaP6x)?Hap&dZ2%T&U`_X>HW-m1DOc1fY;Y2b;t_nm@)F4&q!cb} zRebe@PYx-QOv4v5SuVYJaPR#lmg&)44TPvRlbmsoq7h+o?$!pIkmz@)i_gYS4ZrP;xCHz zY2=CyeiO_SrLokp+xvOpdLlMgIlXtGglqBzL>YWpIO1^tBYi&B$);1_uS-9hpi@^` z_=?M$1J+f_bUt7^T-cLtVSnV#B=_4ae3=CCWPCHea4@J+{b6pRzAC@#GHU|`u(!!@ z<#{!8#4FCwF1Rx98B7%_GuMjf+4ff}hrR}lgndO0IT;nIIxBGN$3ZG^fcLgzg!@4nd(firbtArki6e82ms{bZZC8LLoWAq0 zj{%C~h`oWSeN8gN$;cx`P>4BC;{gFYMcMUgAK0=U4xet|4VY;J1y<=5 z0m;Y~%zt>aVmN1U$fG**co~6x@ug(+{DT$5^ppkijhOOXIrER5Fgb9%h7G0}+BOq5 zOn{H5QJ_&>>^+<9>8p{#)H!>kcP7FE(B&Txt=07;Y8`jnH$VBB0HtL>rZ!<$(R_R zrSG!}yR2uro?9OeD?%*f@S~{^7x#2l9DrJn3^C}a}IqwHA}lbRGjcZuZE2gRqC zh2?1M0H*Xg4yfsHBH`hyB4)vIJu?KgW~Ye>{uiT<<{CKDlPo9rAMo3F3m}>v=p?LM zWLsc3VSN5BEWhjF{K%GvWv0Je9}H`6gDjcAh$U%ZauNd7g&mT1t|LX32^M!(1&kPT z5?Uh3r=Hm(VzlFDV3W#3IMQWDqOd4#l;6Gs8xxl58g&`BiU~R($n2kM2t`CknNpbN z?dpcXh67nwR5-W$jCZ8HyOq6XUJ8Jd+&EMYQLX9H1TDPye3)f;D8mQO>=ouVB|;jJ z9-QDCzcQQVy};s1HM+;lX_934v!&_P;^SMF6{a&umVa3NiAlUFqDt1PT_pD@eubNJHfO&`l zW02&Ys(_lNfHEm$2^G&*7OS2mtnR52eyFE#waUD{C4MdRnJ78+bdM-nMy-2eYktLn zf948$Su#JDPsP9$0Z?$#k*QiCj|HB*Y|KR)p9Wmn&O zf%?u>x@GkhP2wb%JvUxbnDS=#I~J zz$4+z8OsV8^~kOHzN*bmODVQ`a%gpAzQQ@Xhd<|565$^)!RB7c7{>Qp7*k?r0uQ@Osqyn_!r7cl4@%clAhF6oouJZrW4?n>|0C1 zvbTYV$oAF@Eh1az3Ui77RBPk_*5wDM&Eg(+nPMPB(_v%wEsT3OzKUq)IZireEo}oL z9c}{nWTIOD=erpiNVLF}Ru{% zk9CA10sV0tTgBho#+qV;MKI8vxA~p?Vuf$t&-2*^dL7pXq6zvf0sWNk$2(tNz}Lq8 z`DnkK;026xdi#6i_^AwC?fBBTI(6WzyPai`d<8y{1Dy3pRvQG+e_ z5D$$;<|h?o6(8{;#N~R&vL!;0PrN=zviiH!Af@wsFICl*3vR0-h64;$V@k=_Lei~)R{Q*z{YxNZWxc$e)M6J$507{Bat>IcTTg=?E{VSr;wKl~tJ%?pp))^n2@xAY z{DgFPhA$%1VBIqFyx4OP%Z5yX*7PABhOm_Uy|SMck}w2-N;=^K<9QxDx)*A^Rk~Qr zsxz}c+PX;9TZ=;&$X-yWOUwHA;2b9YTt7`<@+K6~x3%7Z?$z1;up@@A`O;T?g@L5+ zQ1lK7Kagzl-Hn7qwFzELg_d-Sep>`E!XYwXS1mLPPAucOn*mUukmr4BhNy4X_Z8f0 z$*$ES&;?a@G*FCN;bH`!!`~5{48&jn(+fBTHd1<%K3sRZtvgyrE?$;6Wo65@(ToZj z7Yc=GUn4OrU~QDIixGx^7LcIwi1!r;m(Lx)cObN-Y@<@l%3MmdVZKUAAwJ3j6K}fW z;h@*{FbNM&)+?~wr~h{Z75<(AH2FZjKu?=d&vG^=3Y2hE3v8a@PDrJmV=35-Z}a#8 zpi4tP&K=!6SYxMl*{yZ(!B{h%DS+mnWap4$14`#}&}y}6juEt7(|tBu75mG5HDSkK z=K+j{M{WTdyn&ajl9M`#%rowFFrehsGoMjyK1j;0kT0*~$(ul#O>U*#k0ZZ#M8bCp z`YOuI!I>8SkU8TLK3opq?$0fE!rrJ^9b(?hQNCsM#<||o;};j>;K?;Z({%60wk58Y z#rFnG(AG9@Seb})j_Emc2PC%-PsVjBIC221S}i}VChq5MtMjJ()AcU)Q!=H zt{;~s&Oyc&0MzGXigIcdHwFQKX?xk&8lZ6ihC!r&&p#n>cPHyXzra+o;#%PGcL3`k z$fcHiIgujHt$pnGs-?D|UlVo?wgBpeyC5Xl49Fm%Men?5*EiAPo(nl~9+e-o1xx{n zJ1_8H;mFe6hIQqzJ|udoxazt?ab7^c z-C*}LkeeOrTEOmR8(iZ&Po&V1GW@B*#-7m7VyNCy6Z$jr&|Lmyu>#pW3jSGZJ=hpz zL2XNQK}ijk;JwnZ1K7=9c8*rIUBK1GGS$rBtjS?>>F36lm9J}vW_;rpr(y<^YKEeS z9)s58`1g+lM9oZ?&t|~Ewju~*DNz4S3rmYBlPt3B`ENfejb`R*&e3vTzN#sKo2oQmGkMaLA0g|c2vtH?BC@o1W!#_cnrBDs_DK{gaw1}>*FxIOvol;l#91MtQV_XMuJ zPe@oM!79fs{DG=yZN12T*Z;+e=YJv_Us`rv{x48UtJ){$ZB*nM=d}}2*Fibdu} z89DY$7hRe4CGL7~v9-ND8F2cJ5o)l~Y4z~-^=5DAEpJybYKQPZ zI@OyunxMRtG8#Urm<1C-nMCtMeO@-O>z8h!?~KU2hx<$$R@@=}^q z!3z#%!Ef)NppP4`{ykoP9nL=Aw7(l${C+PcXCDvjyYnvTjsq>7lYQY|3ay} z$yI2(2Bz~1g1%oVeJMnSXK9J4yE*98OCi=F#<1+XlcvXo$dt>065G*=+N95Ze8X6Q z(_C!-#!JU9vq6FgJs0x4lsG>z`h@u0`%ceFzXYP+%fX4s(G9UoE5)Sbe zbL!&Qfcn1n9cO0aQ8?Pm_TH)45Uyr(S6gzZ7^JgO#-U z0M&vp+~~HiDzK~fFV+|>58RMJ37?OiFD#)%W|I=`WPvtu$G#SyudwY+MjEaRNzsy` zT^2{vsGAKY-$?kN00DhVo}AK~*AS~;6k?_>vu)8%mydl0_BEpWjst2gn#W~3pXn?M zLR;W49HRUM)rkO0o;`eY8bM;@iX~J2Ooskuv2=3iQ1Wsf11ZvH5RgQt;!hN`SmMoaZK1$BM{1uNoviqD$fBhr##imZHM45x(-!XrY!6M_hev zGB$l_WekkNZo!@$orpbvGq1z z%sz>?!SjyKezR82Vvm!ObaVB}vwPHfCsS8ZOrY~g;hF<1T;@R3Xg#GDqLRlnH@9FO z&6#nhY4iGHh#RfIjGs`9GsD`r!SBPdTR3B~W-S*ky3(m-s#CnCS#@$SJm zaQa#k_A9rM!+&gaz`}uSu+<{6XH3c!n8)@GKZE;Z^}3uVvMZEc1DV&#^EDg#&FVdG zz}5XRUnqn zdC5Jlj6tsUtgOYbN@dXny1|-i*_^0yp2Bku2e~i4Nvq1POuf<_39_$>HNvJCV)AxP zCDtXEL^A4fe{8Pre{wA@L&sZj=uLtG8KiC4cf%FyqQWn8#(KR!b^d#W=r2)+s|kXt zDU4gJ(Ao48k?SMh$CETuT2yt>4 z_=A#0c#wTIYAwc0Vsoa=5zLvAK6>nIbh|%+9~l*rqnhm~Jo+rSza>Q}7Sb!rng=U) zxGs{QrbN-adX>5n%6ByugGE8zrOwmni`CJNmL0CfY=fm_iwQgZ?X*OBYD<#T3XY2e zU7)A8w=7z{9{ZfZGiv2JA8U4B8;hE`@Ch>aVikA4z%`+T$|5aWX7y zYWqLQiSd)y-h|/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 diff --git a/fnordmetric-webui/tests/lib/jasmine-2.1.2/boot.js b/fnordmetric-webui/tests/lib/jasmine-2.1.2/boot.js deleted file mode 100644 index 164f068b0..000000000 --- a/fnordmetric-webui/tests/lib/jasmine-2.1.2/boot.js +++ /dev/null @@ -1,120 +0,0 @@ -/** - Starting with version 2.0, this file "boots" Jasmine, performing all of the necessary initialization before executing the loaded environment and all of a project's specs. This file should be loaded after `jasmine.js` and `jasmine_html.js`, but before any project source files or spec files are loaded. Thus this file can also be used to customize Jasmine for a project. - - If a project is using Jasmine via the standalone distribution, this file can be customized directly. If a project is using Jasmine via the [Ruby gem][jasmine-gem], this file can be copied into the support directory via `jasmine copy_boot_js`. Other environments (e.g., Python) will have different mechanisms. - - The location of `boot.js` can be specified and/or overridden in `jasmine.yml`. - - [jasmine-gem]: http://github.com/pivotal/jasmine-gem - */ - -(function() { - - /** - * ## Require & Instantiate - * - * Require Jasmine's core files. Specifically, this requires and attaches all of Jasmine's code to the `jasmine` reference. - */ - window.jasmine = jasmineRequire.core(jasmineRequire); - - /** - * Since this is being run in a browser and the results should populate to an HTML page, require the HTML-specific Jasmine code, injecting the same reference. - */ - jasmineRequire.html(jasmine); - - /** - * Create the Jasmine environment. This is used to run all specs in a project. - */ - var env = jasmine.getEnv(); - - /** - * ## The Global Interface - * - * Build up the functions that will be exposed as the Jasmine public interface. A project can customize, rename or alias any of these functions as desired, provided the implementation remains unchanged. - */ - var jasmineInterface = jasmineRequire.interface(jasmine, env); - - /** - * Add all of the Jasmine global/public interface to the proper global, so a project can use the public interface directly. For example, calling `describe` in specs instead of `jasmine.getEnv().describe`. - */ - if (typeof window == "undefined" && typeof exports == "object") { - extend(exports, jasmineInterface); - } else { - extend(window, jasmineInterface); - } - - /** - * ## Runner Parameters - * - * More browser specific code - wrap the query string in an object and to allow for getting/setting parameters from the runner user interface. - */ - - var queryString = new jasmine.QueryString({ - getWindowLocation: function() { return window.location; } - }); - - var catchingExceptions = queryString.getParam("catch"); - env.catchExceptions(typeof catchingExceptions === "undefined" ? true : catchingExceptions); - - /** - * ## Reporters - * The `HtmlReporter` builds all of the HTML UI for the runner page. This reporter paints the dots, stars, and x's for specs, as well as all spec names and all failures (if any). - */ - var htmlReporter = new jasmine.HtmlReporter({ - env: env, - onRaiseExceptionsClick: function() { queryString.setParam("catch", !env.catchingExceptions()); }, - getContainer: function() { return document.body; }, - createElement: function() { return document.createElement.apply(document, arguments); }, - createTextNode: function() { return document.createTextNode.apply(document, arguments); }, - timer: new jasmine.Timer() - }); - - /** - * The `jsApiReporter` also receives spec results, and is used by any environment that needs to extract the results from JavaScript. - */ - env.addReporter(jasmineInterface.jsApiReporter); - env.addReporter(htmlReporter); - - /** - * Filter which specs will be run by matching the start of the full name against the `spec` query param. - */ - var specFilter = new jasmine.HtmlSpecFilter({ - filterString: function() { return queryString.getParam("spec"); } - }); - - env.specFilter = function(spec) { - return specFilter.matches(spec.getFullName()); - }; - - /** - * Setting up timing functions to be able to be overridden. Certain browsers (Safari, IE 8, phantomjs) require this hack. - */ - window.setTimeout = window.setTimeout; - window.setInterval = window.setInterval; - window.clearTimeout = window.clearTimeout; - window.clearInterval = window.clearInterval; - - /** - * ## Execution - * - * Replace the browser window's `onload`, ensure it's called, and then run all of the loaded specs. This includes initializing the `HtmlReporter` instance and then executing the loaded Jasmine environment. All of this will happen after all of the specs are loaded. - */ - var currentWindowOnload = window.onload; - - window.onload = function() { - if (currentWindowOnload) { - currentWindowOnload(); - } - htmlReporter.initialize(); - env.execute(); - }; - - /** - * Helper function for readability above. - */ - function extend(destination, source) { - for (var property in source) destination[property] = source[property]; - return destination; - } - -}()); diff --git a/fnordmetric-webui/tests/lib/jasmine-2.1.2/console.js b/fnordmetric-webui/tests/lib/jasmine-2.1.2/console.js deleted file mode 100644 index a65876e91..000000000 --- a/fnordmetric-webui/tests/lib/jasmine-2.1.2/console.js +++ /dev/null @@ -1,190 +0,0 @@ -/* -Copyright (c) 2008-2014 Pivotal Labs - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ -function getJasmineRequireObj() { - if (typeof module !== 'undefined' && module.exports) { - return exports; - } else { - window.jasmineRequire = window.jasmineRequire || {}; - return window.jasmineRequire; - } -} - -getJasmineRequireObj().console = function(jRequire, j$) { - j$.ConsoleReporter = jRequire.ConsoleReporter(); -}; - -getJasmineRequireObj().ConsoleReporter = function() { - - var noopTimer = { - start: function(){}, - elapsed: function(){ return 0; } - }; - - function ConsoleReporter(options) { - var print = options.print, - showColors = options.showColors || false, - onComplete = options.onComplete || function() {}, - timer = options.timer || noopTimer, - specCount, - failureCount, - failedSpecs = [], - pendingCount, - ansi = { - green: '\x1B[32m', - red: '\x1B[31m', - yellow: '\x1B[33m', - none: '\x1B[0m' - }, - failedSuites = []; - - print('ConsoleReporter is deprecated and will be removed in a future version.'); - - this.jasmineStarted = function() { - specCount = 0; - failureCount = 0; - pendingCount = 0; - print('Started'); - printNewline(); - timer.start(); - }; - - this.jasmineDone = function() { - printNewline(); - for (var i = 0; i < failedSpecs.length; i++) { - specFailureDetails(failedSpecs[i]); - } - - if(specCount > 0) { - printNewline(); - - var specCounts = specCount + ' ' + plural('spec', specCount) + ', ' + - failureCount + ' ' + plural('failure', failureCount); - - if (pendingCount) { - specCounts += ', ' + pendingCount + ' pending ' + plural('spec', pendingCount); - } - - print(specCounts); - } else { - print('No specs found'); - } - - printNewline(); - var seconds = timer.elapsed() / 1000; - print('Finished in ' + seconds + ' ' + plural('second', seconds)); - printNewline(); - - for(i = 0; i < failedSuites.length; i++) { - suiteFailureDetails(failedSuites[i]); - } - - onComplete(failureCount === 0); - }; - - this.specDone = function(result) { - specCount++; - - if (result.status == 'pending') { - pendingCount++; - print(colored('yellow', '*')); - return; - } - - if (result.status == 'passed') { - print(colored('green', '.')); - return; - } - - if (result.status == 'failed') { - failureCount++; - failedSpecs.push(result); - print(colored('red', 'F')); - } - }; - - this.suiteDone = function(result) { - if (result.failedExpectations && result.failedExpectations.length > 0) { - failureCount++; - failedSuites.push(result); - } - }; - - return this; - - function printNewline() { - print('\n'); - } - - function colored(color, str) { - return showColors ? (ansi[color] + str + ansi.none) : str; - } - - function plural(str, count) { - return count == 1 ? str : str + 's'; - } - - function repeat(thing, times) { - var arr = []; - for (var i = 0; i < times; i++) { - arr.push(thing); - } - return arr; - } - - function indent(str, spaces) { - var lines = (str || '').split('\n'); - var newArr = []; - for (var i = 0; i < lines.length; i++) { - newArr.push(repeat(' ', spaces).join('') + lines[i]); - } - return newArr.join('\n'); - } - - function specFailureDetails(result) { - printNewline(); - print(result.fullName); - - for (var i = 0; i < result.failedExpectations.length; i++) { - var failedExpectation = result.failedExpectations[i]; - printNewline(); - print(indent(failedExpectation.message, 2)); - print(indent(failedExpectation.stack, 2)); - } - - printNewline(); - } - - function suiteFailureDetails(result) { - for (var i = 0; i < result.failedExpectations.length; i++) { - printNewline(); - print(colored('red', 'An error was thrown in an afterAll')); - printNewline(); - print(colored('red', 'AfterAll ' + result.failedExpectations[i].message)); - - } - printNewline(); - } - } - - return ConsoleReporter; -}; diff --git a/fnordmetric-webui/tests/lib/jasmine-2.1.2/jasmine-html.js b/fnordmetric-webui/tests/lib/jasmine-2.1.2/jasmine-html.js deleted file mode 100644 index 898108b77..000000000 --- a/fnordmetric-webui/tests/lib/jasmine-2.1.2/jasmine-html.js +++ /dev/null @@ -1,404 +0,0 @@ -/* -Copyright (c) 2008-2014 Pivotal Labs - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ -jasmineRequire.html = function(j$) { - j$.ResultsNode = jasmineRequire.ResultsNode(); - j$.HtmlReporter = jasmineRequire.HtmlReporter(j$); - j$.QueryString = jasmineRequire.QueryString(); - j$.HtmlSpecFilter = jasmineRequire.HtmlSpecFilter(); -}; - -jasmineRequire.HtmlReporter = function(j$) { - - var noopTimer = { - start: function() {}, - elapsed: function() { return 0; } - }; - - function HtmlReporter(options) { - var env = options.env || {}, - getContainer = options.getContainer, - createElement = options.createElement, - createTextNode = options.createTextNode, - onRaiseExceptionsClick = options.onRaiseExceptionsClick || function() {}, - timer = options.timer || noopTimer, - results = [], - specsExecuted = 0, - failureCount = 0, - pendingSpecCount = 0, - htmlReporterMain, - symbols, - failedSuites = []; - - this.initialize = function() { - clearPrior(); - htmlReporterMain = createDom('div', {className: 'jasmine_html-reporter'}, - createDom('div', {className: 'banner'}, - createDom('a', {className: 'title', href: 'http://jasmine.github.io/', target: '_blank'}), - createDom('span', {className: 'version'}, j$.version) - ), - createDom('ul', {className: 'symbol-summary'}), - createDom('div', {className: 'alert'}), - createDom('div', {className: 'results'}, - createDom('div', {className: 'failures'}) - ) - ); - getContainer().appendChild(htmlReporterMain); - - symbols = find('.symbol-summary'); - }; - - var totalSpecsDefined; - this.jasmineStarted = function(options) { - totalSpecsDefined = options.totalSpecsDefined || 0; - timer.start(); - }; - - var summary = createDom('div', {className: 'summary'}); - - var topResults = new j$.ResultsNode({}, '', null), - currentParent = topResults; - - this.suiteStarted = function(result) { - currentParent.addChild(result, 'suite'); - currentParent = currentParent.last(); - }; - - this.suiteDone = function(result) { - if (result.status == 'failed') { - failedSuites.push(result); - } - - if (currentParent == topResults) { - return; - } - - currentParent = currentParent.parent; - }; - - this.specStarted = function(result) { - currentParent.addChild(result, 'spec'); - }; - - var failures = []; - this.specDone = function(result) { - if(noExpectations(result) && typeof console !== 'undefined' && typeof console.error !== 'undefined') { - console.error('Spec \'' + result.fullName + '\' has no expectations.'); - } - - if (result.status != 'disabled') { - specsExecuted++; - } - - symbols.appendChild(createDom('li', { - className: noExpectations(result) ? 'empty' : result.status, - id: 'spec_' + result.id, - title: result.fullName - } - )); - - if (result.status == 'failed') { - failureCount++; - - var failure = - createDom('div', {className: 'spec-detail failed'}, - createDom('div', {className: 'description'}, - createDom('a', {title: result.fullName, href: specHref(result)}, result.fullName) - ), - createDom('div', {className: 'messages'}) - ); - var messages = failure.childNodes[1]; - - for (var i = 0; i < result.failedExpectations.length; i++) { - var expectation = result.failedExpectations[i]; - messages.appendChild(createDom('div', {className: 'result-message'}, expectation.message)); - messages.appendChild(createDom('div', {className: 'stack-trace'}, expectation.stack)); - } - - failures.push(failure); - } - - if (result.status == 'pending') { - pendingSpecCount++; - } - }; - - this.jasmineDone = function() { - var banner = find('.banner'); - banner.appendChild(createDom('span', {className: 'duration'}, 'finished in ' + timer.elapsed() / 1000 + 's')); - - var alert = find('.alert'); - - alert.appendChild(createDom('span', { className: 'exceptions' }, - createDom('label', { className: 'label', 'for': 'raise-exceptions' }, 'raise exceptions'), - createDom('input', { - className: 'raise', - id: 'raise-exceptions', - type: 'checkbox' - }) - )); - var checkbox = find('#raise-exceptions'); - - checkbox.checked = !env.catchingExceptions(); - checkbox.onclick = onRaiseExceptionsClick; - - if (specsExecuted < totalSpecsDefined) { - var skippedMessage = 'Ran ' + specsExecuted + ' of ' + totalSpecsDefined + ' specs - run all'; - alert.appendChild( - createDom('span', {className: 'bar skipped'}, - createDom('a', {href: '?', title: 'Run all specs'}, skippedMessage) - ) - ); - } - var statusBarMessage = ''; - var statusBarClassName = 'bar '; - - if (totalSpecsDefined > 0) { - statusBarMessage += pluralize('spec', specsExecuted) + ', ' + pluralize('failure', failureCount); - if (pendingSpecCount) { statusBarMessage += ', ' + pluralize('pending spec', pendingSpecCount); } - statusBarClassName += (failureCount > 0) ? 'failed' : 'passed'; - } else { - statusBarClassName += 'skipped'; - statusBarMessage += 'No specs found'; - } - - alert.appendChild(createDom('span', {className: statusBarClassName}, statusBarMessage)); - - for(i = 0; i < failedSuites.length; i++) { - var failedSuite = failedSuites[i]; - for(var j = 0; j < failedSuite.failedExpectations.length; j++) { - var errorBarMessage = 'AfterAll ' + failedSuite.failedExpectations[j].message; - var errorBarClassName = 'bar errored'; - alert.appendChild(createDom('span', {className: errorBarClassName}, errorBarMessage)); - } - } - - var results = find('.results'); - results.appendChild(summary); - - summaryList(topResults, summary); - - function summaryList(resultsTree, domParent) { - var specListNode; - for (var i = 0; i < resultsTree.children.length; i++) { - var resultNode = resultsTree.children[i]; - if (resultNode.type == 'suite') { - var suiteListNode = createDom('ul', {className: 'suite', id: 'suite-' + resultNode.result.id}, - createDom('li', {className: 'suite-detail'}, - createDom('a', {href: specHref(resultNode.result)}, resultNode.result.description) - ) - ); - - summaryList(resultNode, suiteListNode); - domParent.appendChild(suiteListNode); - } - if (resultNode.type == 'spec') { - if (domParent.getAttribute('class') != 'specs') { - specListNode = createDom('ul', {className: 'specs'}); - domParent.appendChild(specListNode); - } - var specDescription = resultNode.result.description; - if(noExpectations(resultNode.result)) { - specDescription = 'SPEC HAS NO EXPECTATIONS ' + specDescription; - } - specListNode.appendChild( - createDom('li', { - className: resultNode.result.status, - id: 'spec-' + resultNode.result.id - }, - createDom('a', {href: specHref(resultNode.result)}, specDescription) - ) - ); - } - } - } - - if (failures.length) { - alert.appendChild( - createDom('span', {className: 'menu bar spec-list'}, - createDom('span', {}, 'Spec List | '), - createDom('a', {className: 'failures-menu', href: '#'}, 'Failures'))); - alert.appendChild( - createDom('span', {className: 'menu bar failure-list'}, - createDom('a', {className: 'spec-list-menu', href: '#'}, 'Spec List'), - createDom('span', {}, ' | Failures '))); - - find('.failures-menu').onclick = function() { - setMenuModeTo('failure-list'); - }; - find('.spec-list-menu').onclick = function() { - setMenuModeTo('spec-list'); - }; - - setMenuModeTo('failure-list'); - - var failureNode = find('.failures'); - for (var i = 0; i < failures.length; i++) { - failureNode.appendChild(failures[i]); - } - } - }; - - return this; - - function find(selector) { - return getContainer().querySelector('.jasmine_html-reporter ' + selector); - } - - function clearPrior() { - // return the reporter - var oldReporter = find(''); - - if(oldReporter) { - getContainer().removeChild(oldReporter); - } - } - - function createDom(type, attrs, childrenVarArgs) { - var el = createElement(type); - - for (var i = 2; i < arguments.length; i++) { - var child = arguments[i]; - - if (typeof child === 'string') { - el.appendChild(createTextNode(child)); - } else { - if (child) { - el.appendChild(child); - } - } - } - - for (var attr in attrs) { - if (attr == 'className') { - el[attr] = attrs[attr]; - } else { - el.setAttribute(attr, attrs[attr]); - } - } - - return el; - } - - function pluralize(singular, count) { - var word = (count == 1 ? singular : singular + 's'); - - return '' + count + ' ' + word; - } - - function specHref(result) { - return '?spec=' + encodeURIComponent(result.fullName); - } - - function setMenuModeTo(mode) { - htmlReporterMain.setAttribute('class', 'jasmine_html-reporter ' + mode); - } - - function noExpectations(result) { - return (result.failedExpectations.length + result.passedExpectations.length) === 0 && - result.status === 'passed'; - } - } - - return HtmlReporter; -}; - -jasmineRequire.HtmlSpecFilter = function() { - function HtmlSpecFilter(options) { - var filterString = options && options.filterString() && options.filterString().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); - var filterPattern = new RegExp(filterString); - - this.matches = function(specName) { - return filterPattern.test(specName); - }; - } - - return HtmlSpecFilter; -}; - -jasmineRequire.ResultsNode = function() { - function ResultsNode(result, type, parent) { - this.result = result; - this.type = type; - this.parent = parent; - - this.children = []; - - this.addChild = function(result, type) { - this.children.push(new ResultsNode(result, type, this)); - }; - - this.last = function() { - return this.children[this.children.length - 1]; - }; - } - - return ResultsNode; -}; - -jasmineRequire.QueryString = function() { - function QueryString(options) { - - this.setParam = function(key, value) { - var paramMap = queryStringToParamMap(); - paramMap[key] = value; - options.getWindowLocation().search = toQueryString(paramMap); - }; - - this.getParam = function(key) { - return queryStringToParamMap()[key]; - }; - - return this; - - function toQueryString(paramMap) { - var qStrPairs = []; - for (var prop in paramMap) { - qStrPairs.push(encodeURIComponent(prop) + '=' + encodeURIComponent(paramMap[prop])); - } - return '?' + qStrPairs.join('&'); - } - - function queryStringToParamMap() { - var paramStr = options.getWindowLocation().search.substring(1), - params = [], - paramMap = {}; - - if (paramStr.length > 0) { - params = paramStr.split('&'); - for (var i = 0; i < params.length; i++) { - var p = params[i].split('='); - var value = decodeURIComponent(p[1]); - if (value === 'true' || value === 'false') { - value = JSON.parse(value); - } - paramMap[decodeURIComponent(p[0])] = value; - } - } - - return paramMap; - } - - } - - return QueryString; -}; diff --git a/fnordmetric-webui/tests/lib/jasmine-2.1.2/jasmine.css b/fnordmetric-webui/tests/lib/jasmine-2.1.2/jasmine.css deleted file mode 100644 index ecc5f5e7b..000000000 --- a/fnordmetric-webui/tests/lib/jasmine-2.1.2/jasmine.css +++ /dev/null @@ -1,62 +0,0 @@ -body { overflow-y: scroll; } - -.jasmine_html-reporter { background-color: #eee; padding: 5px; margin: -8px; font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333; } -.jasmine_html-reporter a { text-decoration: none; } -.jasmine_html-reporter a:hover { text-decoration: underline; } -.jasmine_html-reporter p, .jasmine_html-reporter h1, .jasmine_html-reporter h2, .jasmine_html-reporter h3, .jasmine_html-reporter h4, .jasmine_html-reporter h5, .jasmine_html-reporter h6 { margin: 0; line-height: 14px; } -.jasmine_html-reporter .banner, .jasmine_html-reporter .symbol-summary, .jasmine_html-reporter .summary, .jasmine_html-reporter .result-message, .jasmine_html-reporter .spec .description, .jasmine_html-reporter .spec-detail .description, .jasmine_html-reporter .alert .bar, .jasmine_html-reporter .stack-trace { padding-left: 9px; padding-right: 9px; } -.jasmine_html-reporter .banner { position: relative; } -.jasmine_html-reporter .banner .title { background: url('data:image/png;base64,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') no-repeat; background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgoKPHN2ZwogICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiCiAgIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyIKICAgeG1sbnM6c3ZnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgdmVyc2lvbj0iMS4xIgogICB3aWR0aD0iNjgxLjk2MjUyIgogICBoZWlnaHQ9IjE4Ny41IgogICBpZD0ic3ZnMiIKICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PG1ldGFkYXRhCiAgICAgaWQ9Im1ldGFkYXRhOCI+PHJkZjpSREY+PGNjOldvcmsKICAgICAgICAgcmRmOmFib3V0PSIiPjxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PjxkYzp0eXBlCiAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz48L2NjOldvcms+PC9yZGY6UkRGPjwvbWV0YWRhdGE+PGRlZnMKICAgICBpZD0iZGVmczYiPjxjbGlwUGF0aAogICAgICAgaWQ9ImNsaXBQYXRoMTgiPjxwYXRoCiAgICAgICAgIGQ9Ik0gMCwxNTAwIDAsMCBsIDU0NTUuNzQsMCAwLDE1MDAgTCAwLDE1MDAgeiIKICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgaWQ9InBhdGgyMCIgLz48L2NsaXBQYXRoPjwvZGVmcz48ZwogICAgIHRyYW5zZm9ybT0ibWF0cml4KDEuMjUsMCwwLC0xLjI1LDAsMTg3LjUpIgogICAgIGlkPSJnMTAiPjxnCiAgICAgICB0cmFuc2Zvcm09InNjYWxlKDAuMSwwLjEpIgogICAgICAgaWQ9ImcxMiI+PGcKICAgICAgICAgaWQ9ImcxNCI+PGcKICAgICAgICAgICBjbGlwLXBhdGg9InVybCgjY2xpcFBhdGgxOCkiCiAgICAgICAgICAgaWQ9ImcxNiI+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gMTU0NCw1OTkuNDM0IGMgMC45MiwtNDAuMzUyIDI1LjY4LC04MS42MDIgNzEuNTMsLTgxLjYwMiAyNy41MSwwIDQ3LjY4LDEyLjgzMiA2MS40NCwzNS43NTQgMTIuODMsMjIuOTMgMTIuODMsNTYuODUyIDEyLjgzLDgyLjUyNyBsIDAsMzI5LjE4NCAtNzEuNTIsMCAwLDEwNC41NDMgMjY2LjgzLDAgMCwtMTA0LjU0MyAtNzAuNiwwIDAsLTM0NC43NyBjIDAsLTU4LjY5MSAtMy42OCwtMTA0LjUzMSAtNDQuOTMsLTE1Mi4yMTggLTM2LjY4LC00Mi4xOCAtOTYuMjgsLTY2LjAyIC0xNTMuMTQsLTY2LjAyIC0xMTcuMzcsMCAtMjA3LjI0LDc3Ljk0MSAtMjAyLjY0LDE5Ny4xNDUgbCAxMzAuMiwwIgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMjIiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDIzMDEuNCw2NjIuNjk1IGMgMCw4MC43MDMgLTY2Ljk0LDE0NS44MTMgLTE0Ny42MywxNDUuODEzIC04My40NCwwIC0xNDcuNjMsLTY4Ljc4MSAtMTQ3LjYzLC0xNTEuMzAxIDAsLTc5Ljc4NSA2Ni45NCwtMTQ1LjgwMSAxNDUuOCwtMTQ1LjgwMSA4NC4zNSwwIDE0OS40Niw2Ny44NTIgMTQ5LjQ2LDE1MS4yODkgeiBtIC0xLjgzLC0xODEuNTQ3IGMgLTM1Ljc3LC01NC4wOTcgLTkzLjUzLC03OC44NTkgLTE1Ny43MiwtNzguODU5IC0xNDAuMywwIC0yNTEuMjQsMTE2LjQ0OSAtMjUxLjI0LDI1NC45MTggMCwxNDIuMTI5IDExMy43LDI2MC40MSAyNTYuNzQsMjYwLjQxIDYzLjI3LDAgMTE4LjI5LC0yOS4zMzYgMTUyLjIyLC04Mi41MjMgbCAwLDY5LjY4NyAxNzUuMTQsMCAwLC0xMDQuNTI3IC02MS40NCwwIDAsLTI4MC41OTggNjEuNDQsMCAwLC0xMDQuNTI3IC0xNzUuMTQsMCAwLDY2LjAxOSIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDI0IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSAyNjIyLjMzLDU1Ny4yNTggYyAzLjY3LC00NC4wMTYgMzMuMDEsLTczLjM0OCA3OC44NiwtNzMuMzQ4IDMzLjkzLDAgNjYuOTMsMjMuODI0IDY2LjkzLDYwLjUwNCAwLDQ4LjYwNiAtNDUuODQsNTYuODU2IC04My40NCw2Ni45NDEgLTg1LjI4LDIyLjAwNCAtMTc4LjgxLDQ4LjYwNiAtMTc4LjgxLDE1NS44NzkgMCw5My41MzYgNzguODYsMTQ3LjYzMyAxNjUuOTgsMTQ3LjYzMyA0NCwwIDgzLjQzLC05LjE3NiAxMTAuOTQsLTQ0LjAwOCBsIDAsMzMuOTIyIDgyLjUzLDAgMCwtMTMyLjk2NSAtMTA4LjIxLDAgYyAtMS44MywzNC44NTYgLTI4LjQyLDU3Ljc3NCAtNjMuMjYsNTcuNzc0IC0zMC4yNiwwIC02Mi4zNSwtMTcuNDIyIC02Mi4zNSwtNTEuMzQ4IDAsLTQ1Ljg0NyA0NC45MywtNTUuOTMgODAuNjksLTY0LjE4IDg4LjAyLC0yMC4xNzUgMTgyLjQ3LC00Ny42OTUgMTgyLjQ3LC0xNTcuNzM0IDAsLTk5LjAyNyAtODMuNDQsLTE1NC4wMzkgLTE3NS4xMywtMTU0LjAzOSAtNDkuNTMsMCAtOTQuNDYsMTUuNTgyIC0xMjYuNTUsNTMuMTggbCAwLC00MC4zNCAtODUuMjcsMCAwLDE0Mi4xMjkgMTE0LjYyLDAiCiAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgaWQ9InBhdGgyNiIKICAgICAgICAgICAgIHN0eWxlPSJmaWxsOiM4YTQxODI7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiIC8+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gMjk4OC4xOCw4MDAuMjU0IC02My4yNiwwIDAsMTA0LjUyNyAxNjUuMDUsMCAwLC03My4zNTUgYyAzMS4xOCw1MS4zNDcgNzguODYsODUuMjc3IDE0MS4yMSw4NS4yNzcgNjcuODUsMCAxMjQuNzEsLTQxLjI1OCAxNTIuMjEsLTEwMi42OTkgMjYuNiw2Mi4zNTEgOTIuNjIsMTAyLjY5OSAxNjAuNDcsMTAyLjY5OSA1My4xOSwwIDEwNS40NiwtMjIgMTQxLjIxLC02Mi4zNTEgMzguNTIsLTQ0LjkzOCAzOC41MiwtOTMuNTMyIDM4LjUyLC0xNDkuNDU3IGwgMCwtMTg1LjIzOSA2My4yNywwIDAsLTEwNC41MjcgLTIzOC40MiwwIDAsMTA0LjUyNyA2My4yOCwwIDAsMTU3LjcxNSBjIDAsMzIuMTAyIDAsNjAuNTI3IC0xNC42Nyw4OC45NTcgLTE4LjM0LDI2LjU4MiAtNDguNjEsNDAuMzQ0IC03OS43Nyw0MC4zNDQgLTMwLjI2LDAgLTYzLjI4LC0xMi44NDQgLTgyLjUzLC0zNi42NzIgLTIyLjkzLC0yOS4zNTUgLTIyLjkzLC01Ni44NjMgLTIyLjkzLC05Mi42MjkgbCAwLC0xNTcuNzE1IDYzLjI3LDAgMCwtMTA0LjUyNyAtMjM4LjQxLDAgMCwxMDQuNTI3IDYzLjI4LDAgMCwxNTAuMzgzIGMgMCwyOS4zNDggMCw2Ni4wMjMgLTE0LjY3LDkxLjY5OSAtMTUuNTksMjkuMzM2IC00Ny42OSw0NC45MzQgLTgwLjcsNDQuOTM0IC0zMS4xOCwwIC01Ny43NywtMTEuMDA4IC03Ny45NCwtMzUuNzc0IC0yNC43NywtMzAuMjUzIC0yNi42LC02Mi4zNDMgLTI2LjYsLTk5Ljk0MSBsIDAsLTE1MS4zMDEgNjMuMjcsMCAwLC0xMDQuNTI3IC0yMzguNCwwIDAsMTA0LjUyNyA2My4yNiwwIDAsMjgwLjU5OCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDI4IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSAzOTk4LjY2LDk1MS41NDcgLTExMS44NywwIDAsMTE4LjI5MyAxMTEuODcsMCAwLC0xMTguMjkzIHogbSAwLC00MzEuODkxIDYzLjI3LDAgMCwtMTA0LjUyNyAtMjM5LjMzLDAgMCwxMDQuNTI3IDY0LjE5LDAgMCwyODAuNTk4IC02My4yNywwIDAsMTA0LjUyNyAxNzUuMTQsMCAwLC0zODUuMTI1IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMzAiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDQxNTkuMTIsODAwLjI1NCAtNjMuMjcsMCAwLDEwNC41MjcgMTc1LjE0LDAgMCwtNjkuNjg3IGMgMjkuMzUsNTQuMTAxIDg0LjM2LDgwLjY5OSAxNDQuODcsODAuNjk5IDUzLjE5LDAgMTA1LjQ1LC0yMi4wMTYgMTQxLjIyLC02MC41MjcgNDAuMzQsLTQ0LjkzNCA0MS4yNiwtODguMDMyIDQxLjI2LC0xNDMuOTU3IGwgMCwtMTkxLjY1MyA2My4yNywwIDAsLTEwNC41MjcgLTIzOC40LDAgMCwxMDQuNTI3IDYzLjI2LDAgMCwxNTguNjM3IGMgMCwzMC4yNjIgMCw2MS40MzQgLTE5LjI2LDg4LjAzNSAtMjAuMTcsMjYuNTgyIC01My4xOCwzOS40MTQgLTg2LjE5LDM5LjQxNCAtMzMuOTMsMCAtNjguNzcsLTEzLjc1IC04OC45NCwtNDEuMjUgLTIxLjA5LC0yNy41IC0yMS4wOSwtNjkuNjg3IC0yMS4wOSwtMTAyLjcwNyBsIDAsLTE0Mi4xMjkgNjMuMjYsMCAwLC0xMDQuNTI3IC0yMzguNCwwIDAsMTA0LjUyNyA2My4yNywwIDAsMjgwLjU5OCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDMyIgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA1MDgyLjQ4LDcwMy45NjUgYyAtMTkuMjQsNzAuNjA1IC04MS42LDExNS41NDcgLTE1NC4wNCwxMTUuNTQ3IC02Ni4wNCwwIC0xMjkuMywtNTEuMzQ4IC0xNDMuMDUsLTExNS41NDcgbCAyOTcuMDksMCB6IG0gODUuMjcsLTE0NC44ODMgYyAtMzguNTEsLTkzLjUyMyAtMTI5LjI3LC0xNTYuNzkzIC0yMzEuMDUsLTE1Ni43OTMgLTE0My4wNywwIC0yNTcuNjgsMTExLjg3MSAtMjU3LjY4LDI1NS44MzYgMCwxNDQuODgzIDEwOS4xMiwyNjEuMzI4IDI1NC45MSwyNjEuMzI4IDY3Ljg3LDAgMTM1LjcyLC0zMC4yNTggMTgzLjM5LC03OC44NjMgNDguNjIsLTUxLjM0NCA2OC43OSwtMTEzLjY5NSA2OC43OSwtMTgzLjM4MyBsIC0zLjY3LC0zOS40MzQgLTM5Ni4xMywwIGMgMTQuNjcsLTY3Ljg2MyA3Ny4wMywtMTE3LjM2MyAxNDYuNzIsLTExNy4zNjMgNDguNTksMCA5MC43NiwxOC4zMjggMTE4LjI4LDU4LjY3MiBsIDExNi40NCwwIgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMzQiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDY5MC44OTUsODUwLjcwMyA5MC43NSwwIDIyLjU0MywzMS4wMzUgMCwyNDMuMTIyIC0xMzUuODI5LDAgMCwtMjQzLjE0MSAyMi41MzYsLTMxLjAxNiIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDM2IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA2MzIuMzk1LDc0Mi4yNTggMjguMDM5LDg2LjMwNCAtMjIuNTUxLDMxLjA0IC0yMzEuMjIzLDc1LjEyOCAtNDEuOTc2LC0xMjkuMTgzIDIzMS4yNTcsLTc1LjEzNyAzNi40NTQsMTEuODQ4IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMzgiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDcxNy40NDksNjUzLjEwNSAtNzMuNDEsNTMuMzYgLTM2LjQ4OCwtMTEuODc1IC0xNDIuOTAzLC0xOTYuNjkyIDEwOS44ODMsLTc5LjgyOCAxNDIuOTE4LDE5Ni43MDMgMCwzOC4zMzIiCiAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgaWQ9InBhdGg0MCIKICAgICAgICAgICAgIHN0eWxlPSJmaWxsOiM4YTQxODI7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiIC8+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gODI4LjUyLDcwNi40NjUgLTczLjQyNiwtNTMuMzQgMC4wMTEsLTM4LjM1OSBMIDg5OC4wMDQsNDE4LjA3IDEwMDcuOSw0OTcuODk4IDg2NC45NzMsNjk0LjYwOSA4MjguNTIsNzA2LjQ2NSIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDQyIgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA4MTIuMDg2LDgyOC41ODYgMjguMDU1LC04Ni4zMiAzNi40ODQsLTExLjgzNiAyMzEuMjI1LDc1LjExNyAtNDEuOTcsMTI5LjE4MyAtMjMxLjIzOSwtNzUuMTQgLTIyLjU1NSwtMzEuMDA0IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoNDQiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDczNi4zMDEsMTMzNS44OCBjIC0zMjMuMDQ3LDAgLTU4NS44NzUsLTI2Mi43OCAtNTg1Ljg3NSwtNTg1Ljc4MiAwLC0zMjMuMTE4IDI2Mi44MjgsLTU4NS45NzcgNTg1Ljg3NSwtNTg1Ljk3NyAzMjMuMDE5LDAgNTg1LjgwOSwyNjIuODU5IDU4NS44MDksNTg1Ljk3NyAwLDMyMy4wMDIgLTI2Mi43OSw1ODUuNzgyIC01ODUuODA5LDU4NS43ODIgbCAwLDAgeiBtIDAsLTExOC42MSBjIDI1Ny45NzIsMCA0NjcuMTg5LC0yMDkuMTMgNDY3LjE4OSwtNDY3LjE3MiAwLC0yNTguMTI5IC0yMDkuMjE3LC00NjcuMzQ4IC00NjcuMTg5LC00NjcuMzQ4IC0yNTguMDc0LDAgLTQ2Ny4yNTQsMjA5LjIxOSAtNDY3LjI1NCw0NjcuMzQ4IDAsMjU4LjA0MiAyMDkuMTgsNDY3LjE3MiA0NjcuMjU0LDQ2Ny4xNzIiCiAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgaWQ9InBhdGg0NiIKICAgICAgICAgICAgIHN0eWxlPSJmaWxsOiM4YTQxODI7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiIC8+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gMTA5MS4xMyw2MTkuODgzIC0xNzUuNzcxLDU3LjEyMSAxMS42MjksMzUuODA4IDE3NS43NjIsLTU3LjEyMSAtMTEuNjIsLTM1LjgwOCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDQ4IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0iTSA4NjYuOTU3LDkwMi4wNzQgODM2LjUsOTI0LjE5OSA5NDUuMTIxLDEwNzMuNzMgOTc1LjU4NiwxMDUxLjYxIDg2Ni45NTcsOTAyLjA3NCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDUwIgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0iTSA2MDcuNDY1LDkwMy40NDUgNDk4Ljg1NSwxMDUyLjk3IDUyOS4zMiwxMDc1LjEgNjM3LjkzLDkyNS41NjYgNjA3LjQ2NSw5MDMuNDQ1IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoNTIiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDM4MC42ODgsNjIyLjEyOSAtMTEuNjI2LDM1LjgwMSAxNzUuNzU4LDU3LjA5IDExLjYyMSwtMzUuODAxIC0xNzUuNzUzLC01Ny4wOSIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDU0IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA3MTYuMjg5LDM3Ni41OSAzNy42NDA2LDAgMCwxODQuODE2IC0zNy42NDA2LDAgMCwtMTg0LjgxNiB6IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoNTYiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjwvZz48L2c+PC9nPjwvZz48L3N2Zz4=') no-repeat, none; -moz-background-size: 100%; -o-background-size: 100%; -webkit-background-size: 100%; background-size: 100%; display: block; float: left; width: 90px; height: 25px; } -.jasmine_html-reporter .banner .version { margin-left: 14px; position: relative; top: 6px; } -.jasmine_html-reporter .banner .duration { position: absolute; right: 14px; top: 6px; } -.jasmine_html-reporter #jasmine_content { position: fixed; right: 100%; } -.jasmine_html-reporter .version { color: #aaa; } -.jasmine_html-reporter .banner { margin-top: 14px; } -.jasmine_html-reporter .duration { color: #aaa; float: right; } -.jasmine_html-reporter .symbol-summary { overflow: hidden; *zoom: 1; margin: 14px 0; } -.jasmine_html-reporter .symbol-summary li { display: inline-block; height: 8px; width: 14px; font-size: 16px; } -.jasmine_html-reporter .symbol-summary li.passed { font-size: 14px; } -.jasmine_html-reporter .symbol-summary li.passed:before { color: #007069; content: "\02022"; } -.jasmine_html-reporter .symbol-summary li.failed { line-height: 9px; } -.jasmine_html-reporter .symbol-summary li.failed:before { color: #ca3a11; content: "\d7"; font-weight: bold; margin-left: -1px; } -.jasmine_html-reporter .symbol-summary li.disabled { font-size: 14px; } -.jasmine_html-reporter .symbol-summary li.disabled:before { color: #bababa; content: "\02022"; } -.jasmine_html-reporter .symbol-summary li.pending { line-height: 17px; } -.jasmine_html-reporter .symbol-summary li.pending:before { color: #ba9d37; content: "*"; } -.jasmine_html-reporter .symbol-summary li.empty { font-size: 14px; } -.jasmine_html-reporter .symbol-summary li.empty:before { color: #ba9d37; content: "\02022"; } -.jasmine_html-reporter .exceptions { color: #fff; float: right; margin-top: 5px; margin-right: 5px; } -.jasmine_html-reporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; } -.jasmine_html-reporter .bar.failed { background-color: #ca3a11; } -.jasmine_html-reporter .bar.passed { background-color: #007069; } -.jasmine_html-reporter .bar.skipped { background-color: #bababa; } -.jasmine_html-reporter .bar.errored { background-color: #ca3a11; } -.jasmine_html-reporter .bar.menu { background-color: #fff; color: #aaa; } -.jasmine_html-reporter .bar.menu a { color: #333; } -.jasmine_html-reporter .bar a { color: white; } -.jasmine_html-reporter.spec-list .bar.menu.failure-list, .jasmine_html-reporter.spec-list .results .failures { display: none; } -.jasmine_html-reporter.failure-list .bar.menu.spec-list, .jasmine_html-reporter.failure-list .summary { display: none; } -.jasmine_html-reporter .running-alert { background-color: #666; } -.jasmine_html-reporter .results { margin-top: 14px; } -.jasmine_html-reporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; } -.jasmine_html-reporter.showDetails .summaryMenuItem:hover { text-decoration: underline; } -.jasmine_html-reporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; } -.jasmine_html-reporter.showDetails .summary { display: none; } -.jasmine_html-reporter.showDetails #details { display: block; } -.jasmine_html-reporter .summaryMenuItem { font-weight: bold; text-decoration: underline; } -.jasmine_html-reporter .summary { margin-top: 14px; } -.jasmine_html-reporter .summary ul { list-style-type: none; margin-left: 14px; padding-top: 0; padding-left: 0; } -.jasmine_html-reporter .summary ul.suite { margin-top: 7px; margin-bottom: 7px; } -.jasmine_html-reporter .summary li.passed a { color: #007069; } -.jasmine_html-reporter .summary li.failed a { color: #ca3a11; } -.jasmine_html-reporter .summary li.empty a { color: #ba9d37; } -.jasmine_html-reporter .summary li.pending a { color: #ba9d37; } -.jasmine_html-reporter .description + .suite { margin-top: 0; } -.jasmine_html-reporter .suite { margin-top: 14px; } -.jasmine_html-reporter .suite a { color: #333; } -.jasmine_html-reporter .failures .spec-detail { margin-bottom: 28px; } -.jasmine_html-reporter .failures .spec-detail .description { background-color: #ca3a11; } -.jasmine_html-reporter .failures .spec-detail .description a { color: white; } -.jasmine_html-reporter .result-message { padding-top: 14px; color: #333; white-space: pre; } -.jasmine_html-reporter .result-message span.result { display: block; } -.jasmine_html-reporter .stack-trace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666; border: 1px solid #ddd; background: white; white-space: pre; } diff --git a/fnordmetric-webui/tests/lib/jasmine-2.1.2/jasmine.js b/fnordmetric-webui/tests/lib/jasmine-2.1.2/jasmine.js deleted file mode 100644 index e4c574fc5..000000000 --- a/fnordmetric-webui/tests/lib/jasmine-2.1.2/jasmine.js +++ /dev/null @@ -1,2909 +0,0 @@ -/* -Copyright (c) 2008-2014 Pivotal Labs - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ -getJasmineRequireObj = (function (jasmineGlobal) { - var jasmineRequire; - - if (typeof module !== 'undefined' && module.exports) { - jasmineGlobal = global; - jasmineRequire = exports; - } else { - jasmineRequire = jasmineGlobal.jasmineRequire = jasmineGlobal.jasmineRequire || {}; - } - - function getJasmineRequire() { - return jasmineRequire; - } - - getJasmineRequire().core = function(jRequire) { - var j$ = {}; - - jRequire.base(j$, jasmineGlobal); - j$.util = jRequire.util(); - j$.Any = jRequire.Any(); - j$.CallTracker = jRequire.CallTracker(); - j$.MockDate = jRequire.MockDate(); - j$.Clock = jRequire.Clock(); - j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler(); - j$.Env = jRequire.Env(j$); - j$.ExceptionFormatter = jRequire.ExceptionFormatter(); - j$.Expectation = jRequire.Expectation(); - j$.buildExpectationResult = jRequire.buildExpectationResult(); - j$.JsApiReporter = jRequire.JsApiReporter(); - j$.matchersUtil = jRequire.matchersUtil(j$); - j$.ObjectContaining = jRequire.ObjectContaining(j$); - j$.pp = jRequire.pp(j$); - j$.QueueRunner = jRequire.QueueRunner(j$); - j$.ReportDispatcher = jRequire.ReportDispatcher(); - j$.Spec = jRequire.Spec(j$); - j$.SpyRegistry = jRequire.SpyRegistry(j$); - j$.SpyStrategy = jRequire.SpyStrategy(); - j$.Suite = jRequire.Suite(); - j$.Timer = jRequire.Timer(); - j$.version = jRequire.version(); - - j$.matchers = jRequire.requireMatchers(jRequire, j$); - - return j$; - }; - - return getJasmineRequire; -})(this); - -getJasmineRequireObj().requireMatchers = function(jRequire, j$) { - var availableMatchers = [ - 'toBe', - 'toBeCloseTo', - 'toBeDefined', - 'toBeFalsy', - 'toBeGreaterThan', - 'toBeLessThan', - 'toBeNaN', - 'toBeNull', - 'toBeTruthy', - 'toBeUndefined', - 'toContain', - 'toEqual', - 'toHaveBeenCalled', - 'toHaveBeenCalledWith', - 'toMatch', - 'toThrow', - 'toThrowError' - ], - matchers = {}; - - for (var i = 0; i < availableMatchers.length; i++) { - var name = availableMatchers[i]; - matchers[name] = jRequire[name](j$); - } - - return matchers; -}; - -getJasmineRequireObj().base = function(j$, jasmineGlobal) { - j$.unimplementedMethod_ = function() { - throw new Error('unimplemented method'); - }; - - j$.MAX_PRETTY_PRINT_DEPTH = 40; - j$.MAX_PRETTY_PRINT_ARRAY_LENGTH = 100; - j$.DEFAULT_TIMEOUT_INTERVAL = 5000; - - j$.getGlobal = function() { - return jasmineGlobal; - }; - - j$.getEnv = function(options) { - var env = j$.currentEnv_ = j$.currentEnv_ || new j$.Env(options); - //jasmine. singletons in here (setTimeout blah blah). - return env; - }; - - j$.isArray_ = function(value) { - return j$.isA_('Array', value); - }; - - j$.isString_ = function(value) { - return j$.isA_('String', value); - }; - - j$.isNumber_ = function(value) { - return j$.isA_('Number', value); - }; - - j$.isA_ = function(typeName, value) { - return Object.prototype.toString.apply(value) === '[object ' + typeName + ']'; - }; - - j$.isDomNode = function(obj) { - return obj.nodeType > 0; - }; - - j$.any = function(clazz) { - return new j$.Any(clazz); - }; - - j$.objectContaining = function(sample) { - return new j$.ObjectContaining(sample); - }; - - j$.createSpy = function(name, originalFn) { - - var spyStrategy = new j$.SpyStrategy({ - name: name, - fn: originalFn, - getSpy: function() { return spy; } - }), - callTracker = new j$.CallTracker(), - spy = function() { - var callData = { - object: this, - args: Array.prototype.slice.apply(arguments) - }; - - callTracker.track(callData); - var returnValue = spyStrategy.exec.apply(this, arguments); - callData.returnValue = returnValue; - - return returnValue; - }; - - for (var prop in originalFn) { - if (prop === 'and' || prop === 'calls') { - throw new Error('Jasmine spies would overwrite the \'and\' and \'calls\' properties on the object being spied upon'); - } - - spy[prop] = originalFn[prop]; - } - - spy.and = spyStrategy; - spy.calls = callTracker; - - return spy; - }; - - j$.isSpy = function(putativeSpy) { - if (!putativeSpy) { - return false; - } - return putativeSpy.and instanceof j$.SpyStrategy && - putativeSpy.calls instanceof j$.CallTracker; - }; - - j$.createSpyObj = function(baseName, methodNames) { - if (!j$.isArray_(methodNames) || methodNames.length === 0) { - throw 'createSpyObj requires a non-empty array of method names to create spies for'; - } - var obj = {}; - for (var i = 0; i < methodNames.length; i++) { - obj[methodNames[i]] = j$.createSpy(baseName + '.' + methodNames[i]); - } - return obj; - }; -}; - -getJasmineRequireObj().util = function() { - - var util = {}; - - util.inherit = function(childClass, parentClass) { - var Subclass = function() { - }; - Subclass.prototype = parentClass.prototype; - childClass.prototype = new Subclass(); - }; - - util.htmlEscape = function(str) { - if (!str) { - return str; - } - return str.replace(/&/g, '&') - .replace(//g, '>'); - }; - - util.argsToArray = function(args) { - var arrayOfArgs = []; - for (var i = 0; i < args.length; i++) { - arrayOfArgs.push(args[i]); - } - return arrayOfArgs; - }; - - util.isUndefined = function(obj) { - return obj === void 0; - }; - - util.arrayContains = function(array, search) { - var i = array.length; - while (i--) { - if (array[i] === search) { - return true; - } - } - return false; - }; - - util.clone = function(obj) { - if (Object.prototype.toString.apply(obj) === '[object Array]') { - return obj.slice(); - } - - var cloned = {}; - for (var prop in obj) { - if (obj.hasOwnProperty(prop)) { - cloned[prop] = obj[prop]; - } - } - - return cloned; - }; - - return util; -}; - -getJasmineRequireObj().Spec = function(j$) { - function Spec(attrs) { - this.expectationFactory = attrs.expectationFactory; - this.resultCallback = attrs.resultCallback || function() {}; - this.id = attrs.id; - this.description = attrs.description || ''; - this.queueableFn = attrs.queueableFn; - this.beforeAndAfterFns = attrs.beforeAndAfterFns || function() { return {befores: [], afters: []}; }; - this.userContext = attrs.userContext || function() { return {}; }; - this.onStart = attrs.onStart || function() {}; - this.getSpecName = attrs.getSpecName || function() { return ''; }; - this.expectationResultFactory = attrs.expectationResultFactory || function() { }; - this.queueRunnerFactory = attrs.queueRunnerFactory || function() {}; - this.catchingExceptions = attrs.catchingExceptions || function() { return true; }; - - if (!this.queueableFn.fn) { - this.pend(); - } - - this.result = { - id: this.id, - description: this.description, - fullName: this.getFullName(), - failedExpectations: [], - passedExpectations: [] - }; - } - - Spec.prototype.addExpectationResult = function(passed, data) { - var expectationResult = this.expectationResultFactory(data); - if (passed) { - this.result.passedExpectations.push(expectationResult); - } else { - this.result.failedExpectations.push(expectationResult); - } - }; - - Spec.prototype.expect = function(actual) { - return this.expectationFactory(actual, this); - }; - - Spec.prototype.execute = function(onComplete) { - var self = this; - - this.onStart(this); - - if (this.markedPending || this.disabled) { - complete(); - return; - } - - var fns = this.beforeAndAfterFns(); - var allFns = fns.befores.concat(this.queueableFn).concat(fns.afters); - - this.queueRunnerFactory({ - queueableFns: allFns, - onException: function() { self.onException.apply(self, arguments); }, - onComplete: complete, - userContext: this.userContext() - }); - - function complete() { - self.result.status = self.status(); - self.resultCallback(self.result); - - if (onComplete) { - onComplete(); - } - } - }; - - Spec.prototype.onException = function onException(e) { - if (Spec.isPendingSpecException(e)) { - this.pend(); - return; - } - - this.addExpectationResult(false, { - matcherName: '', - passed: false, - expected: '', - actual: '', - error: e - }); - }; - - Spec.prototype.disable = function() { - this.disabled = true; - }; - - Spec.prototype.pend = function() { - this.markedPending = true; - }; - - Spec.prototype.status = function() { - if (this.disabled) { - return 'disabled'; - } - - if (this.markedPending) { - return 'pending'; - } - - if (this.result.failedExpectations.length > 0) { - return 'failed'; - } else { - return 'passed'; - } - }; - - Spec.prototype.isExecutable = function() { - return !this.disabled && !this.markedPending; - }; - - Spec.prototype.getFullName = function() { - return this.getSpecName(this); - }; - - Spec.pendingSpecExceptionMessage = '=> marked Pending'; - - Spec.isPendingSpecException = function(e) { - return !!(e && e.toString && e.toString().indexOf(Spec.pendingSpecExceptionMessage) !== -1); - }; - - return Spec; -}; - -if (typeof window == void 0 && typeof exports == 'object') { - exports.Spec = jasmineRequire.Spec; -} - -getJasmineRequireObj().Env = function(j$) { - function Env(options) { - options = options || {}; - - var self = this; - var global = options.global || j$.getGlobal(); - - var totalSpecsDefined = 0; - - var catchExceptions = true; - - var realSetTimeout = j$.getGlobal().setTimeout; - var realClearTimeout = j$.getGlobal().clearTimeout; - this.clock = new j$.Clock(global, new j$.DelayedFunctionScheduler(), new j$.MockDate(global)); - - var runnableLookupTable = {}; - var runnableResources = {}; - - var currentSpec = null; - var currentlyExecutingSuites = []; - var currentDeclarationSuite = null; - - var currentSuite = function() { - return currentlyExecutingSuites[currentlyExecutingSuites.length - 1]; - }; - - var currentRunnable = function() { - return currentSpec || currentSuite(); - }; - - var reporter = new j$.ReportDispatcher([ - 'jasmineStarted', - 'jasmineDone', - 'suiteStarted', - 'suiteDone', - 'specStarted', - 'specDone' - ]); - - this.specFilter = function() { - return true; - }; - - this.addCustomEqualityTester = function(tester) { - if(!currentRunnable()) { - throw new Error('Custom Equalities must be added in a before function or a spec'); - } - runnableResources[currentRunnable().id].customEqualityTesters.push(tester); - }; - - this.addMatchers = function(matchersToAdd) { - if(!currentRunnable()) { - throw new Error('Matchers must be added in a before function or a spec'); - } - var customMatchers = runnableResources[currentRunnable().id].customMatchers; - for (var matcherName in matchersToAdd) { - customMatchers[matcherName] = matchersToAdd[matcherName]; - } - }; - - j$.Expectation.addCoreMatchers(j$.matchers); - - var nextSpecId = 0; - var getNextSpecId = function() { - return 'spec' + nextSpecId++; - }; - - var nextSuiteId = 0; - var getNextSuiteId = function() { - return 'suite' + nextSuiteId++; - }; - - var expectationFactory = function(actual, spec) { - return j$.Expectation.Factory({ - util: j$.matchersUtil, - customEqualityTesters: runnableResources[spec.id].customEqualityTesters, - customMatchers: runnableResources[spec.id].customMatchers, - actual: actual, - addExpectationResult: addExpectationResult - }); - - function addExpectationResult(passed, result) { - return spec.addExpectationResult(passed, result); - } - }; - - var defaultResourcesForRunnable = function(id, parentRunnableId) { - var resources = {spies: [], customEqualityTesters: [], customMatchers: {}}; - - if(runnableResources[parentRunnableId]){ - resources.customEqualityTesters = j$.util.clone(runnableResources[parentRunnableId].customEqualityTesters); - resources.customMatchers = j$.util.clone(runnableResources[parentRunnableId].customMatchers); - } - - runnableResources[id] = resources; - }; - - var clearResourcesForRunnable = function(id) { - spyRegistry.clearSpies(); - delete runnableResources[id]; - }; - - var beforeAndAfterFns = function(suite, runnablesExplictlySet) { - return function() { - var befores = [], - afters = [], - beforeAlls = [], - afterAlls = []; - - while(suite) { - befores = befores.concat(suite.beforeFns); - afters = afters.concat(suite.afterFns); - - if (runnablesExplictlySet()) { - beforeAlls = beforeAlls.concat(suite.beforeAllFns); - afterAlls = afterAlls.concat(suite.afterAllFns); - } - - suite = suite.parentSuite; - } - return { - befores: beforeAlls.reverse().concat(befores.reverse()), - afters: afters.concat(afterAlls) - }; - }; - }; - - var getSpecName = function(spec, suite) { - return suite.getFullName() + ' ' + spec.description; - }; - - // TODO: we may just be able to pass in the fn instead of wrapping here - var buildExpectationResult = j$.buildExpectationResult, - exceptionFormatter = new j$.ExceptionFormatter(), - expectationResultFactory = function(attrs) { - attrs.messageFormatter = exceptionFormatter.message; - attrs.stackFormatter = exceptionFormatter.stack; - - return buildExpectationResult(attrs); - }; - - // TODO: fix this naming, and here's where the value comes in - this.catchExceptions = function(value) { - catchExceptions = !!value; - return catchExceptions; - }; - - this.catchingExceptions = function() { - return catchExceptions; - }; - - var maximumSpecCallbackDepth = 20; - var currentSpecCallbackDepth = 0; - - function clearStack(fn) { - currentSpecCallbackDepth++; - if (currentSpecCallbackDepth >= maximumSpecCallbackDepth) { - currentSpecCallbackDepth = 0; - realSetTimeout(fn, 0); - } else { - fn(); - } - } - - var catchException = function(e) { - return j$.Spec.isPendingSpecException(e) || catchExceptions; - }; - - var queueRunnerFactory = function(options) { - options.catchException = catchException; - options.clearStack = options.clearStack || clearStack; - options.timer = {setTimeout: realSetTimeout, clearTimeout: realClearTimeout}; - options.fail = self.fail; - - new j$.QueueRunner(options).execute(); - }; - - var topSuite = new j$.Suite({ - env: this, - id: getNextSuiteId(), - description: 'Jasmine__TopLevel__Suite', - queueRunner: queueRunnerFactory, - onStart: function(suite) { - reporter.suiteStarted(suite.result); - }, - resultCallback: function(attrs) { - reporter.suiteDone(attrs); - } - }); - runnableLookupTable[topSuite.id] = topSuite; - defaultResourcesForRunnable(topSuite.id); - currentDeclarationSuite = topSuite; - - this.topSuite = function() { - return topSuite; - }; - - this.execute = function(runnablesToRun) { - if(runnablesToRun) { - runnablesExplictlySet = true; - } else if (focusedRunnables.length) { - runnablesExplictlySet = true; - runnablesToRun = focusedRunnables; - } else { - runnablesToRun = [topSuite.id]; - } - - var allFns = []; - for(var i = 0; i < runnablesToRun.length; i++) { - var runnable = runnableLookupTable[runnablesToRun[i]]; - allFns.push((function(runnable) { return { fn: function(done) { runnable.execute(done); } }; })(runnable)); - } - - reporter.jasmineStarted({ - totalSpecsDefined: totalSpecsDefined - }); - - queueRunnerFactory({queueableFns: allFns, onComplete: reporter.jasmineDone}); - }; - - this.addReporter = function(reporterToAdd) { - reporter.addReporter(reporterToAdd); - }; - - var spyRegistry = new j$.SpyRegistry({currentSpies: function() { - if(!currentRunnable()) { - throw new Error('Spies must be created in a before function or a spec'); - } - return runnableResources[currentRunnable().id].spies; - }}); - - this.spyOn = function() { - return spyRegistry.spyOn.apply(spyRegistry, arguments); - }; - - var suiteFactory = function(description) { - var suite = new j$.Suite({ - env: self, - id: getNextSuiteId(), - description: description, - parentSuite: currentDeclarationSuite, - queueRunner: queueRunnerFactory, - onStart: suiteStarted, - expectationFactory: expectationFactory, - expectationResultFactory: expectationResultFactory, - resultCallback: function(attrs) { - if (!suite.disabled) { - clearResourcesForRunnable(suite.id); - currentlyExecutingSuites.pop(); - } - reporter.suiteDone(attrs); - } - }); - - runnableLookupTable[suite.id] = suite; - return suite; - - function suiteStarted(suite) { - currentlyExecutingSuites.push(suite); - defaultResourcesForRunnable(suite.id, suite.parentSuite.id); - reporter.suiteStarted(suite.result); - } - }; - - this.describe = function(description, specDefinitions) { - var suite = suiteFactory(description); - addSpecsToSuite(suite, specDefinitions); - return suite; - }; - - this.xdescribe = function(description, specDefinitions) { - var suite = this.describe(description, specDefinitions); - suite.disable(); - return suite; - }; - - var focusedRunnables = []; - - this.fdescribe = function(description, specDefinitions) { - var suite = suiteFactory(description); - suite.isFocused = true; - - focusedRunnables.push(suite.id); - unfocusAncestor(); - addSpecsToSuite(suite, specDefinitions); - - return suite; - }; - - function addSpecsToSuite(suite, specDefinitions) { - var parentSuite = currentDeclarationSuite; - parentSuite.addChild(suite); - currentDeclarationSuite = suite; - - var declarationError = null; - try { - specDefinitions.call(suite); - } catch (e) { - declarationError = e; - } - - if (declarationError) { - self.it('encountered a declaration exception', function() { - throw declarationError; - }); - } - - currentDeclarationSuite = parentSuite; - } - - function findFocusedAncestor(suite) { - while (suite) { - if (suite.isFocused) { - return suite.id; - } - suite = suite.parentSuite; - } - - return null; - } - - function unfocusAncestor() { - var focusedAncestor = findFocusedAncestor(currentDeclarationSuite); - if (focusedAncestor) { - for (var i = 0; i < focusedRunnables.length; i++) { - if (focusedRunnables[i] === focusedAncestor) { - focusedRunnables.splice(i, 1); - break; - } - } - } - } - - var runnablesExplictlySet = false; - - var runnablesExplictlySetGetter = function(){ - return runnablesExplictlySet; - }; - - var specFactory = function(description, fn, suite, timeout) { - totalSpecsDefined++; - var spec = new j$.Spec({ - id: getNextSpecId(), - beforeAndAfterFns: beforeAndAfterFns(suite, runnablesExplictlySetGetter), - expectationFactory: expectationFactory, - resultCallback: specResultCallback, - getSpecName: function(spec) { - return getSpecName(spec, suite); - }, - onStart: specStarted, - description: description, - expectationResultFactory: expectationResultFactory, - queueRunnerFactory: queueRunnerFactory, - userContext: function() { return suite.clonedSharedUserContext(); }, - queueableFn: { - fn: fn, - timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } - } - }); - - runnableLookupTable[spec.id] = spec; - - if (!self.specFilter(spec)) { - spec.disable(); - } - - return spec; - - function specResultCallback(result) { - clearResourcesForRunnable(spec.id); - currentSpec = null; - reporter.specDone(result); - } - - function specStarted(spec) { - currentSpec = spec; - defaultResourcesForRunnable(spec.id, suite.id); - reporter.specStarted(spec.result); - } - }; - - this.it = function(description, fn, timeout) { - var spec = specFactory(description, fn, currentDeclarationSuite, timeout); - currentDeclarationSuite.addChild(spec); - return spec; - }; - - this.xit = function() { - var spec = this.it.apply(this, arguments); - spec.pend(); - return spec; - }; - - this.fit = function(){ - var spec = this.it.apply(this, arguments); - - focusedRunnables.push(spec.id); - unfocusAncestor(); - return spec; - }; - - this.expect = function(actual) { - if (!currentRunnable()) { - throw new Error('\'expect\' was used when there was no current spec, this could be because an asynchronous test timed out'); - } - - return currentRunnable().expect(actual); - }; - - this.beforeEach = function(beforeEachFunction, timeout) { - currentDeclarationSuite.beforeEach({ - fn: beforeEachFunction, - timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } - }); - }; - - this.beforeAll = function(beforeAllFunction, timeout) { - currentDeclarationSuite.beforeAll({ - fn: beforeAllFunction, - timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } - }); - }; - - this.afterEach = function(afterEachFunction, timeout) { - currentDeclarationSuite.afterEach({ - fn: afterEachFunction, - timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } - }); - }; - - this.afterAll = function(afterAllFunction, timeout) { - currentDeclarationSuite.afterAll({ - fn: afterAllFunction, - timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } - }); - }; - - this.pending = function() { - throw j$.Spec.pendingSpecExceptionMessage; - }; - - this.fail = function(error) { - var message = 'Failed'; - if (error) { - message += ': '; - message += error.message || error; - } - - currentRunnable().addExpectationResult(false, { - matcherName: '', - passed: false, - expected: '', - actual: '', - message: message - }); - }; - } - - return Env; -}; - -getJasmineRequireObj().JsApiReporter = function() { - - var noopTimer = { - start: function(){}, - elapsed: function(){ return 0; } - }; - - function JsApiReporter(options) { - var timer = options.timer || noopTimer, - status = 'loaded'; - - this.started = false; - this.finished = false; - - this.jasmineStarted = function() { - this.started = true; - status = 'started'; - timer.start(); - }; - - var executionTime; - - this.jasmineDone = function() { - this.finished = true; - executionTime = timer.elapsed(); - status = 'done'; - }; - - this.status = function() { - return status; - }; - - var suites = [], - suites_hash = {}; - - this.suiteStarted = function(result) { - suites_hash[result.id] = result; - }; - - this.suiteDone = function(result) { - storeSuite(result); - }; - - this.suiteResults = function(index, length) { - return suites.slice(index, index + length); - }; - - function storeSuite(result) { - suites.push(result); - suites_hash[result.id] = result; - } - - this.suites = function() { - return suites_hash; - }; - - var specs = []; - - this.specDone = function(result) { - specs.push(result); - }; - - this.specResults = function(index, length) { - return specs.slice(index, index + length); - }; - - this.specs = function() { - return specs; - }; - - this.executionTime = function() { - return executionTime; - }; - - } - - return JsApiReporter; -}; - -getJasmineRequireObj().Any = function() { - - function Any(expectedObject) { - this.expectedObject = expectedObject; - } - - Any.prototype.jasmineMatches = function(other) { - if (this.expectedObject == String) { - return typeof other == 'string' || other instanceof String; - } - - if (this.expectedObject == Number) { - return typeof other == 'number' || other instanceof Number; - } - - if (this.expectedObject == Function) { - return typeof other == 'function' || other instanceof Function; - } - - if (this.expectedObject == Object) { - return typeof other == 'object'; - } - - if (this.expectedObject == Boolean) { - return typeof other == 'boolean'; - } - - return other instanceof this.expectedObject; - }; - - Any.prototype.jasmineToString = function() { - return ''; - }; - - return Any; -}; - -getJasmineRequireObj().CallTracker = function() { - - function CallTracker() { - var calls = []; - - this.track = function(context) { - calls.push(context); - }; - - this.any = function() { - return !!calls.length; - }; - - this.count = function() { - return calls.length; - }; - - this.argsFor = function(index) { - var call = calls[index]; - return call ? call.args : []; - }; - - this.all = function() { - return calls; - }; - - this.allArgs = function() { - var callArgs = []; - for(var i = 0; i < calls.length; i++){ - callArgs.push(calls[i].args); - } - - return callArgs; - }; - - this.first = function() { - return calls[0]; - }; - - this.mostRecent = function() { - return calls[calls.length - 1]; - }; - - this.reset = function() { - calls = []; - }; - } - - return CallTracker; -}; - -getJasmineRequireObj().Clock = function() { - function Clock(global, delayedFunctionScheduler, mockDate) { - var self = this, - realTimingFunctions = { - setTimeout: global.setTimeout, - clearTimeout: global.clearTimeout, - setInterval: global.setInterval, - clearInterval: global.clearInterval - }, - fakeTimingFunctions = { - setTimeout: setTimeout, - clearTimeout: clearTimeout, - setInterval: setInterval, - clearInterval: clearInterval - }, - installed = false, - timer; - - - self.install = function() { - replace(global, fakeTimingFunctions); - timer = fakeTimingFunctions; - installed = true; - - return self; - }; - - self.uninstall = function() { - delayedFunctionScheduler.reset(); - mockDate.uninstall(); - replace(global, realTimingFunctions); - - timer = realTimingFunctions; - installed = false; - }; - - self.mockDate = function(initialDate) { - mockDate.install(initialDate); - }; - - self.setTimeout = function(fn, delay, params) { - if (legacyIE()) { - if (arguments.length > 2) { - throw new Error('IE < 9 cannot support extra params to setTimeout without a polyfill'); - } - return timer.setTimeout(fn, delay); - } - return Function.prototype.apply.apply(timer.setTimeout, [global, arguments]); - }; - - self.setInterval = function(fn, delay, params) { - if (legacyIE()) { - if (arguments.length > 2) { - throw new Error('IE < 9 cannot support extra params to setInterval without a polyfill'); - } - return timer.setInterval(fn, delay); - } - return Function.prototype.apply.apply(timer.setInterval, [global, arguments]); - }; - - self.clearTimeout = function(id) { - return Function.prototype.call.apply(timer.clearTimeout, [global, id]); - }; - - self.clearInterval = function(id) { - return Function.prototype.call.apply(timer.clearInterval, [global, id]); - }; - - self.tick = function(millis) { - if (installed) { - mockDate.tick(millis); - delayedFunctionScheduler.tick(millis); - } else { - throw new Error('Mock clock is not installed, use jasmine.clock().install()'); - } - }; - - return self; - - function legacyIE() { - //if these methods are polyfilled, apply will be present - return !(realTimingFunctions.setTimeout || realTimingFunctions.setInterval).apply; - } - - function replace(dest, source) { - for (var prop in source) { - dest[prop] = source[prop]; - } - } - - function setTimeout(fn, delay) { - return delayedFunctionScheduler.scheduleFunction(fn, delay, argSlice(arguments, 2)); - } - - function clearTimeout(id) { - return delayedFunctionScheduler.removeFunctionWithId(id); - } - - function setInterval(fn, interval) { - return delayedFunctionScheduler.scheduleFunction(fn, interval, argSlice(arguments, 2), true); - } - - function clearInterval(id) { - return delayedFunctionScheduler.removeFunctionWithId(id); - } - - function argSlice(argsObj, n) { - return Array.prototype.slice.call(argsObj, n); - } - } - - return Clock; -}; - -getJasmineRequireObj().DelayedFunctionScheduler = function() { - function DelayedFunctionScheduler() { - var self = this; - var scheduledLookup = []; - var scheduledFunctions = {}; - var currentTime = 0; - var delayedFnCount = 0; - - self.tick = function(millis) { - millis = millis || 0; - var endTime = currentTime + millis; - - runScheduledFunctions(endTime); - currentTime = endTime; - }; - - self.scheduleFunction = function(funcToCall, millis, params, recurring, timeoutKey, runAtMillis) { - var f; - if (typeof(funcToCall) === 'string') { - /* jshint evil: true */ - f = function() { return eval(funcToCall); }; - /* jshint evil: false */ - } else { - f = funcToCall; - } - - millis = millis || 0; - timeoutKey = timeoutKey || ++delayedFnCount; - runAtMillis = runAtMillis || (currentTime + millis); - - var funcToSchedule = { - runAtMillis: runAtMillis, - funcToCall: f, - recurring: recurring, - params: params, - timeoutKey: timeoutKey, - millis: millis - }; - - if (runAtMillis in scheduledFunctions) { - scheduledFunctions[runAtMillis].push(funcToSchedule); - } else { - scheduledFunctions[runAtMillis] = [funcToSchedule]; - scheduledLookup.push(runAtMillis); - scheduledLookup.sort(function (a, b) { - return a - b; - }); - } - - return timeoutKey; - }; - - self.removeFunctionWithId = function(timeoutKey) { - for (var runAtMillis in scheduledFunctions) { - var funcs = scheduledFunctions[runAtMillis]; - var i = indexOfFirstToPass(funcs, function (func) { - return func.timeoutKey === timeoutKey; - }); - - if (i > -1) { - if (funcs.length === 1) { - delete scheduledFunctions[runAtMillis]; - deleteFromLookup(runAtMillis); - } else { - funcs.splice(i, 1); - } - - // intervals get rescheduled when executed, so there's never more - // than a single scheduled function with a given timeoutKey - break; - } - } - }; - - self.reset = function() { - currentTime = 0; - scheduledLookup = []; - scheduledFunctions = {}; - delayedFnCount = 0; - }; - - return self; - - function indexOfFirstToPass(array, testFn) { - var index = -1; - - for (var i = 0; i < array.length; ++i) { - if (testFn(array[i])) { - index = i; - break; - } - } - - return index; - } - - function deleteFromLookup(key) { - var value = Number(key); - var i = indexOfFirstToPass(scheduledLookup, function (millis) { - return millis === value; - }); - - if (i > -1) { - scheduledLookup.splice(i, 1); - } - } - - function reschedule(scheduledFn) { - self.scheduleFunction(scheduledFn.funcToCall, - scheduledFn.millis, - scheduledFn.params, - true, - scheduledFn.timeoutKey, - scheduledFn.runAtMillis + scheduledFn.millis); - } - - function runScheduledFunctions(endTime) { - if (scheduledLookup.length === 0 || scheduledLookup[0] > endTime) { - return; - } - - do { - currentTime = scheduledLookup.shift(); - - var funcsToRun = scheduledFunctions[currentTime]; - delete scheduledFunctions[currentTime]; - - for (var i = 0; i < funcsToRun.length; ++i) { - var funcToRun = funcsToRun[i]; - - if (funcToRun.recurring) { - reschedule(funcToRun); - } - - funcToRun.funcToCall.apply(null, funcToRun.params || []); - } - } while (scheduledLookup.length > 0 && - // checking first if we're out of time prevents setTimeout(0) - // scheduled in a funcToRun from forcing an extra iteration - currentTime !== endTime && - scheduledLookup[0] <= endTime); - } - } - - return DelayedFunctionScheduler; -}; - -getJasmineRequireObj().ExceptionFormatter = function() { - function ExceptionFormatter() { - this.message = function(error) { - var message = ''; - - if (error.name && error.message) { - message += error.name + ': ' + error.message; - } else { - message += error.toString() + ' thrown'; - } - - if (error.fileName || error.sourceURL) { - message += ' in ' + (error.fileName || error.sourceURL); - } - - if (error.line || error.lineNumber) { - message += ' (line ' + (error.line || error.lineNumber) + ')'; - } - - return message; - }; - - this.stack = function(error) { - return error ? error.stack : null; - }; - } - - return ExceptionFormatter; -}; - -getJasmineRequireObj().Expectation = function() { - - function Expectation(options) { - this.util = options.util || { buildFailureMessage: function() {} }; - this.customEqualityTesters = options.customEqualityTesters || []; - this.actual = options.actual; - this.addExpectationResult = options.addExpectationResult || function(){}; - this.isNot = options.isNot; - - var customMatchers = options.customMatchers || {}; - for (var matcherName in customMatchers) { - this[matcherName] = Expectation.prototype.wrapCompare(matcherName, customMatchers[matcherName]); - } - } - - Expectation.prototype.wrapCompare = function(name, matcherFactory) { - return function() { - var args = Array.prototype.slice.call(arguments, 0), - expected = args.slice(0), - message = ''; - - args.unshift(this.actual); - - var matcher = matcherFactory(this.util, this.customEqualityTesters), - matcherCompare = matcher.compare; - - function defaultNegativeCompare() { - var result = matcher.compare.apply(null, args); - result.pass = !result.pass; - return result; - } - - if (this.isNot) { - matcherCompare = matcher.negativeCompare || defaultNegativeCompare; - } - - var result = matcherCompare.apply(null, args); - - if (!result.pass) { - if (!result.message) { - args.unshift(this.isNot); - args.unshift(name); - message = this.util.buildFailureMessage.apply(null, args); - } else { - if (Object.prototype.toString.apply(result.message) === '[object Function]') { - message = result.message(); - } else { - message = result.message; - } - } - } - - if (expected.length == 1) { - expected = expected[0]; - } - - // TODO: how many of these params are needed? - this.addExpectationResult( - result.pass, - { - matcherName: name, - passed: result.pass, - message: message, - actual: this.actual, - expected: expected // TODO: this may need to be arrayified/sliced - } - ); - }; - }; - - Expectation.addCoreMatchers = function(matchers) { - var prototype = Expectation.prototype; - for (var matcherName in matchers) { - var matcher = matchers[matcherName]; - prototype[matcherName] = prototype.wrapCompare(matcherName, matcher); - } - }; - - Expectation.Factory = function(options) { - options = options || {}; - - var expect = new Expectation(options); - - // TODO: this would be nice as its own Object - NegativeExpectation - // TODO: copy instead of mutate options - options.isNot = true; - expect.not = new Expectation(options); - - return expect; - }; - - return Expectation; -}; - -//TODO: expectation result may make more sense as a presentation of an expectation. -getJasmineRequireObj().buildExpectationResult = function() { - function buildExpectationResult(options) { - var messageFormatter = options.messageFormatter || function() {}, - stackFormatter = options.stackFormatter || function() {}; - - return { - matcherName: options.matcherName, - expected: options.expected, - actual: options.actual, - message: message(), - stack: stack(), - passed: options.passed - }; - - function message() { - if (options.passed) { - return 'Passed.'; - } else if (options.message) { - return options.message; - } else if (options.error) { - return messageFormatter(options.error); - } - return ''; - } - - function stack() { - if (options.passed) { - return ''; - } - - var error = options.error; - if (!error) { - try { - throw new Error(message()); - } catch (e) { - error = e; - } - } - return stackFormatter(error); - } - } - - return buildExpectationResult; -}; - -getJasmineRequireObj().MockDate = function() { - function MockDate(global) { - var self = this; - var currentTime = 0; - - if (!global || !global.Date) { - self.install = function() {}; - self.tick = function() {}; - self.uninstall = function() {}; - return self; - } - - var GlobalDate = global.Date; - - self.install = function(mockDate) { - if (mockDate instanceof GlobalDate) { - currentTime = mockDate.getTime(); - } else { - currentTime = new GlobalDate().getTime(); - } - - global.Date = FakeDate; - }; - - self.tick = function(millis) { - millis = millis || 0; - currentTime = currentTime + millis; - }; - - self.uninstall = function() { - currentTime = 0; - global.Date = GlobalDate; - }; - - createDateProperties(); - - return self; - - function FakeDate() { - switch(arguments.length) { - case 0: - return new GlobalDate(currentTime); - case 1: - return new GlobalDate(arguments[0]); - case 2: - return new GlobalDate(arguments[0], arguments[1]); - case 3: - return new GlobalDate(arguments[0], arguments[1], arguments[2]); - case 4: - return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3]); - case 5: - return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3], - arguments[4]); - case 6: - return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3], - arguments[4], arguments[5]); - case 7: - return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3], - arguments[4], arguments[5], arguments[6]); - } - } - - function createDateProperties() { - FakeDate.prototype = GlobalDate.prototype; - - FakeDate.now = function() { - if (GlobalDate.now) { - return currentTime; - } else { - throw new Error('Browser does not support Date.now()'); - } - }; - - FakeDate.toSource = GlobalDate.toSource; - FakeDate.toString = GlobalDate.toString; - FakeDate.parse = GlobalDate.parse; - FakeDate.UTC = GlobalDate.UTC; - } - } - - return MockDate; -}; - -getJasmineRequireObj().ObjectContaining = function(j$) { - - function ObjectContaining(sample) { - this.sample = sample; - } - - ObjectContaining.prototype.jasmineMatches = function(other, mismatchKeys, mismatchValues) { - if (typeof(this.sample) !== 'object') { throw new Error('You must provide an object to objectContaining, not \''+this.sample+'\'.'); } - - mismatchKeys = mismatchKeys || []; - mismatchValues = mismatchValues || []; - - var hasKey = function(obj, keyName) { - return obj !== null && !j$.util.isUndefined(obj[keyName]); - }; - - for (var property in this.sample) { - if (!hasKey(other, property) && hasKey(this.sample, property)) { - mismatchKeys.push('expected has key \'' + property + '\', but missing from actual.'); - } - else if (!j$.matchersUtil.equals(other[property], this.sample[property])) { - mismatchValues.push('\'' + property + '\' was \'' + (other[property] ? j$.util.htmlEscape(other[property].toString()) : other[property]) + '\' in actual, but was \'' + (this.sample[property] ? j$.util.htmlEscape(this.sample[property].toString()) : this.sample[property]) + '\' in expected.'); - } - } - - return (mismatchKeys.length === 0 && mismatchValues.length === 0); - }; - - ObjectContaining.prototype.jasmineToString = function() { - return ''; - }; - - return ObjectContaining; -}; - -getJasmineRequireObj().pp = function(j$) { - - function PrettyPrinter() { - this.ppNestLevel_ = 0; - this.seen = []; - } - - PrettyPrinter.prototype.format = function(value) { - this.ppNestLevel_++; - try { - if (j$.util.isUndefined(value)) { - this.emitScalar('undefined'); - } else if (value === null) { - this.emitScalar('null'); - } else if (value === 0 && 1/value === -Infinity) { - this.emitScalar('-0'); - } else if (value === j$.getGlobal()) { - this.emitScalar(''); - } else if (value.jasmineToString) { - this.emitScalar(value.jasmineToString()); - } else if (typeof value === 'string') { - this.emitString(value); - } else if (j$.isSpy(value)) { - this.emitScalar('spy on ' + value.and.identity()); - } else if (value instanceof RegExp) { - this.emitScalar(value.toString()); - } else if (typeof value === 'function') { - this.emitScalar('Function'); - } else if (typeof value.nodeType === 'number') { - this.emitScalar('HTMLNode'); - } else if (value instanceof Date) { - this.emitScalar('Date(' + value + ')'); - } else if (j$.util.arrayContains(this.seen, value)) { - this.emitScalar(''); - } else if (j$.isArray_(value) || j$.isA_('Object', value)) { - this.seen.push(value); - if (j$.isArray_(value)) { - this.emitArray(value); - } else { - this.emitObject(value); - } - this.seen.pop(); - } else { - this.emitScalar(value.toString()); - } - } finally { - this.ppNestLevel_--; - } - }; - - PrettyPrinter.prototype.iterateObject = function(obj, fn) { - for (var property in obj) { - if (!Object.prototype.hasOwnProperty.call(obj, property)) { continue; } - fn(property, obj.__lookupGetter__ ? (!j$.util.isUndefined(obj.__lookupGetter__(property)) && - obj.__lookupGetter__(property) !== null) : false); - } - }; - - PrettyPrinter.prototype.emitArray = j$.unimplementedMethod_; - PrettyPrinter.prototype.emitObject = j$.unimplementedMethod_; - PrettyPrinter.prototype.emitScalar = j$.unimplementedMethod_; - PrettyPrinter.prototype.emitString = j$.unimplementedMethod_; - - function StringPrettyPrinter() { - PrettyPrinter.call(this); - - this.string = ''; - } - - j$.util.inherit(StringPrettyPrinter, PrettyPrinter); - - StringPrettyPrinter.prototype.emitScalar = function(value) { - this.append(value); - }; - - StringPrettyPrinter.prototype.emitString = function(value) { - this.append('\'' + value + '\''); - }; - - StringPrettyPrinter.prototype.emitArray = function(array) { - if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) { - this.append('Array'); - return; - } - var length = Math.min(array.length, j$.MAX_PRETTY_PRINT_ARRAY_LENGTH); - this.append('[ '); - for (var i = 0; i < length; i++) { - if (i > 0) { - this.append(', '); - } - this.format(array[i]); - } - if(array.length > length){ - this.append(', ...'); - } - this.append(' ]'); - }; - - StringPrettyPrinter.prototype.emitObject = function(obj) { - if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) { - this.append('Object'); - return; - } - - var self = this; - this.append('{ '); - var first = true; - - this.iterateObject(obj, function(property, isGetter) { - if (first) { - first = false; - } else { - self.append(', '); - } - - self.append(property); - self.append(': '); - if (isGetter) { - self.append(''); - } else { - self.format(obj[property]); - } - }); - - this.append(' }'); - }; - - StringPrettyPrinter.prototype.append = function(value) { - this.string += value; - }; - - return function(value) { - var stringPrettyPrinter = new StringPrettyPrinter(); - stringPrettyPrinter.format(value); - return stringPrettyPrinter.string; - }; -}; - -getJasmineRequireObj().QueueRunner = function(j$) { - - function once(fn) { - var called = false; - return function() { - if (!called) { - called = true; - fn(); - } - }; - } - - function QueueRunner(attrs) { - this.queueableFns = attrs.queueableFns || []; - this.onComplete = attrs.onComplete || function() {}; - this.clearStack = attrs.clearStack || function(fn) {fn();}; - this.onException = attrs.onException || function() {}; - this.catchException = attrs.catchException || function() { return true; }; - this.userContext = attrs.userContext || {}; - this.timer = attrs.timeout || {setTimeout: setTimeout, clearTimeout: clearTimeout}; - this.fail = attrs.fail || function() {}; - } - - QueueRunner.prototype.execute = function() { - this.run(this.queueableFns, 0); - }; - - QueueRunner.prototype.run = function(queueableFns, recursiveIndex) { - var length = queueableFns.length, - self = this, - iterativeIndex; - - - for(iterativeIndex = recursiveIndex; iterativeIndex < length; iterativeIndex++) { - var queueableFn = queueableFns[iterativeIndex]; - if (queueableFn.fn.length > 0) { - return attemptAsync(queueableFn); - } else { - attemptSync(queueableFn); - } - } - - var runnerDone = iterativeIndex >= length; - - if (runnerDone) { - this.clearStack(this.onComplete); - } - - function attemptSync(queueableFn) { - try { - queueableFn.fn.call(self.userContext); - } catch (e) { - handleException(e, queueableFn); - } - } - - function attemptAsync(queueableFn) { - var clearTimeout = function () { - Function.prototype.apply.apply(self.timer.clearTimeout, [j$.getGlobal(), [timeoutId]]); - }, - next = once(function () { - clearTimeout(timeoutId); - self.run(queueableFns, iterativeIndex + 1); - }), - timeoutId; - - next.fail = function() { - self.fail.apply(null, arguments); - next(); - }; - - if (queueableFn.timeout) { - timeoutId = Function.prototype.apply.apply(self.timer.setTimeout, [j$.getGlobal(), [function() { - var error = new Error('Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.'); - onException(error, queueableFn); - next(); - }, queueableFn.timeout()]]); - } - - try { - queueableFn.fn.call(self.userContext, next); - } catch (e) { - handleException(e, queueableFn); - next(); - } - } - - function onException(e, queueableFn) { - self.onException(e); - } - - function handleException(e, queueableFn) { - onException(e, queueableFn); - if (!self.catchException(e)) { - //TODO: set a var when we catch an exception and - //use a finally block to close the loop in a nice way.. - throw e; - } - } - }; - - return QueueRunner; -}; - -getJasmineRequireObj().ReportDispatcher = function() { - function ReportDispatcher(methods) { - - var dispatchedMethods = methods || []; - - for (var i = 0; i < dispatchedMethods.length; i++) { - var method = dispatchedMethods[i]; - this[method] = (function(m) { - return function() { - dispatch(m, arguments); - }; - }(method)); - } - - var reporters = []; - - this.addReporter = function(reporter) { - reporters.push(reporter); - }; - - return this; - - function dispatch(method, args) { - for (var i = 0; i < reporters.length; i++) { - var reporter = reporters[i]; - if (reporter[method]) { - reporter[method].apply(reporter, args); - } - } - } - } - - return ReportDispatcher; -}; - - -getJasmineRequireObj().SpyRegistry = function(j$) { - - function SpyRegistry(options) { - options = options || {}; - var currentSpies = options.currentSpies || function() { return []; }; - - this.spyOn = function(obj, methodName) { - if (j$.util.isUndefined(obj)) { - throw new Error('spyOn could not find an object to spy upon for ' + methodName + '()'); - } - - if (j$.util.isUndefined(obj[methodName])) { - throw new Error(methodName + '() method does not exist'); - } - - if (obj[methodName] && j$.isSpy(obj[methodName])) { - //TODO?: should this return the current spy? Downside: may cause user confusion about spy state - throw new Error(methodName + ' has already been spied upon'); - } - - var spy = j$.createSpy(methodName, obj[methodName]); - - currentSpies().push({ - spy: spy, - baseObj: obj, - methodName: methodName, - originalValue: obj[methodName] - }); - - obj[methodName] = spy; - - return spy; - }; - - this.clearSpies = function() { - var spies = currentSpies(); - for (var i = 0; i < spies.length; i++) { - var spyEntry = spies[i]; - spyEntry.baseObj[spyEntry.methodName] = spyEntry.originalValue; - } - }; - } - - return SpyRegistry; -}; - -getJasmineRequireObj().SpyStrategy = function() { - - function SpyStrategy(options) { - options = options || {}; - - var identity = options.name || 'unknown', - originalFn = options.fn || function() {}, - getSpy = options.getSpy || function() {}, - plan = function() {}; - - this.identity = function() { - return identity; - }; - - this.exec = function() { - return plan.apply(this, arguments); - }; - - this.callThrough = function() { - plan = originalFn; - return getSpy(); - }; - - this.returnValue = function(value) { - plan = function() { - return value; - }; - return getSpy(); - }; - - this.returnValues = function() { - var values = Array.prototype.slice.call(arguments); - plan = function () { - return values.shift(); - }; - return getSpy(); - }; - - this.throwError = function(something) { - var error = (something instanceof Error) ? something : new Error(something); - plan = function() { - throw error; - }; - return getSpy(); - }; - - this.callFake = function(fn) { - plan = fn; - return getSpy(); - }; - - this.stub = function(fn) { - plan = function() {}; - return getSpy(); - }; - } - - return SpyStrategy; -}; - -getJasmineRequireObj().Suite = function() { - function Suite(attrs) { - this.env = attrs.env; - this.id = attrs.id; - this.parentSuite = attrs.parentSuite; - this.description = attrs.description; - this.onStart = attrs.onStart || function() {}; - this.resultCallback = attrs.resultCallback || function() {}; - this.clearStack = attrs.clearStack || function(fn) {fn();}; - this.expectationFactory = attrs.expectationFactory; - this.expectationResultFactory = attrs.expectationResultFactory; - - this.beforeFns = []; - this.afterFns = []; - this.beforeAllFns = []; - this.afterAllFns = []; - this.queueRunner = attrs.queueRunner || function() {}; - this.disabled = false; - - this.children = []; - - this.result = { - id: this.id, - description: this.description, - fullName: this.getFullName(), - failedExpectations: [] - }; - } - - Suite.prototype.expect = function(actual) { - return this.expectationFactory(actual, this); - }; - - Suite.prototype.getFullName = function() { - var fullName = this.description; - for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { - if (parentSuite.parentSuite) { - fullName = parentSuite.description + ' ' + fullName; - } - } - return fullName; - }; - - Suite.prototype.disable = function() { - this.disabled = true; - }; - - Suite.prototype.beforeEach = function(fn) { - this.beforeFns.unshift(fn); - }; - - Suite.prototype.beforeAll = function(fn) { - this.beforeAllFns.push(fn); - }; - - Suite.prototype.afterEach = function(fn) { - this.afterFns.unshift(fn); - }; - - Suite.prototype.afterAll = function(fn) { - this.afterAllFns.push(fn); - }; - - Suite.prototype.addChild = function(child) { - this.children.push(child); - }; - - Suite.prototype.status = function() { - if (this.disabled) { - return 'disabled'; - } - - if (this.result.failedExpectations.length > 0) { - return 'failed'; - } else { - return 'finished'; - } - }; - - Suite.prototype.execute = function(onComplete) { - var self = this; - - this.onStart(this); - - if (this.disabled) { - complete(); - return; - } - - var allFns = []; - - for (var i = 0; i < this.children.length; i++) { - allFns.push(wrapChildAsAsync(this.children[i])); - } - - if (this.isExecutable()) { - allFns = this.beforeAllFns.concat(allFns); - allFns = allFns.concat(this.afterAllFns); - } - - this.queueRunner({ - queueableFns: allFns, - onComplete: complete, - userContext: this.sharedUserContext(), - onException: function() { self.onException.apply(self, arguments); } - }); - - function complete() { - self.result.status = self.status(); - self.resultCallback(self.result); - - if (onComplete) { - onComplete(); - } - } - - function wrapChildAsAsync(child) { - return { fn: function(done) { child.execute(done); } }; - } - }; - - Suite.prototype.isExecutable = function() { - var foundActive = false; - for(var i = 0; i < this.children.length; i++) { - if(this.children[i].isExecutable()) { - foundActive = true; - break; - } - } - return foundActive; - }; - - Suite.prototype.sharedUserContext = function() { - if (!this.sharedContext) { - this.sharedContext = this.parentSuite ? clone(this.parentSuite.sharedUserContext()) : {}; - } - - return this.sharedContext; - }; - - Suite.prototype.clonedSharedUserContext = function() { - return clone(this.sharedUserContext()); - }; - - Suite.prototype.onException = function() { - if(isAfterAll(this.children)) { - var data = { - matcherName: '', - passed: false, - expected: '', - actual: '', - error: arguments[0] - }; - this.result.failedExpectations.push(this.expectationResultFactory(data)); - } else { - for (var i = 0; i < this.children.length; i++) { - var child = this.children[i]; - child.onException.apply(child, arguments); - } - } - }; - - Suite.prototype.addExpectationResult = function () { - if(isAfterAll(this.children) && isFailure(arguments)){ - var data = arguments[1]; - this.result.failedExpectations.push(this.expectationResultFactory(data)); - } else { - for (var i = 0; i < this.children.length; i++) { - var child = this.children[i]; - child.addExpectationResult.apply(child, arguments); - } - } - }; - - function isAfterAll(children) { - return children && children[0].result.status; - } - - function isFailure(args) { - return !args[0]; - } - - function clone(obj) { - var clonedObj = {}; - for (var prop in obj) { - if (obj.hasOwnProperty(prop)) { - clonedObj[prop] = obj[prop]; - } - } - - return clonedObj; - } - - return Suite; -}; - -if (typeof window == void 0 && typeof exports == 'object') { - exports.Suite = jasmineRequire.Suite; -} - -getJasmineRequireObj().Timer = function() { - var defaultNow = (function(Date) { - return function() { return new Date().getTime(); }; - })(Date); - - function Timer(options) { - options = options || {}; - - var now = options.now || defaultNow, - startTime; - - this.start = function() { - startTime = now(); - }; - - this.elapsed = function() { - return now() - startTime; - }; - } - - return Timer; -}; - -getJasmineRequireObj().matchersUtil = function(j$) { - // TODO: what to do about jasmine.pp not being inject? move to JSON.stringify? gut PrettyPrinter? - - return { - equals: function(a, b, customTesters) { - customTesters = customTesters || []; - - return eq(a, b, [], [], customTesters); - }, - - contains: function(haystack, needle, customTesters) { - customTesters = customTesters || []; - - if ((Object.prototype.toString.apply(haystack) === '[object Array]') || - (!!haystack && !haystack.indexOf)) - { - for (var i = 0; i < haystack.length; i++) { - if (eq(haystack[i], needle, [], [], customTesters)) { - return true; - } - } - return false; - } - - return !!haystack && haystack.indexOf(needle) >= 0; - }, - - buildFailureMessage: function() { - var args = Array.prototype.slice.call(arguments, 0), - matcherName = args[0], - isNot = args[1], - actual = args[2], - expected = args.slice(3), - englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); - - var message = 'Expected ' + - j$.pp(actual) + - (isNot ? ' not ' : ' ') + - englishyPredicate; - - if (expected.length > 0) { - for (var i = 0; i < expected.length; i++) { - if (i > 0) { - message += ','; - } - message += ' ' + j$.pp(expected[i]); - } - } - - return message + '.'; - } - }; - - // Equality function lovingly adapted from isEqual in - // [Underscore](http://underscorejs.org) - function eq(a, b, aStack, bStack, customTesters) { - var result = true; - - for (var i = 0; i < customTesters.length; i++) { - var customTesterResult = customTesters[i](a, b); - if (!j$.util.isUndefined(customTesterResult)) { - return customTesterResult; - } - } - - if (a instanceof j$.Any) { - result = a.jasmineMatches(b); - if (result) { - return true; - } - } - - if (b instanceof j$.Any) { - result = b.jasmineMatches(a); - if (result) { - return true; - } - } - - if (b instanceof j$.ObjectContaining) { - result = b.jasmineMatches(a); - if (result) { - return true; - } - } - - if (a instanceof Error && b instanceof Error) { - return a.message == b.message; - } - - // Identical objects are equal. `0 === -0`, but they aren't identical. - // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). - if (a === b) { return a !== 0 || 1 / a == 1 / b; } - // A strict comparison is necessary because `null == undefined`. - if (a === null || b === null) { return a === b; } - var className = Object.prototype.toString.call(a); - if (className != Object.prototype.toString.call(b)) { return false; } - switch (className) { - // Strings, numbers, dates, and booleans are compared by value. - case '[object String]': - // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is - // equivalent to `new String("5")`. - return a == String(b); - case '[object Number]': - // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for - // other numeric values. - return a != +a ? b != +b : (a === 0 ? 1 / a == 1 / b : a == +b); - case '[object Date]': - case '[object Boolean]': - // Coerce dates and booleans to numeric primitive values. Dates are compared by their - // millisecond representations. Note that invalid dates with millisecond representations - // of `NaN` are not equivalent. - return +a == +b; - // RegExps are compared by their source patterns and flags. - case '[object RegExp]': - return a.source == b.source && - a.global == b.global && - a.multiline == b.multiline && - a.ignoreCase == b.ignoreCase; - } - if (typeof a != 'object' || typeof b != 'object') { return false; } - // Assume equality for cyclic structures. The algorithm for detecting cyclic - // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. - var length = aStack.length; - while (length--) { - // Linear search. Performance is inversely proportional to the number of - // unique nested structures. - if (aStack[length] == a) { return bStack[length] == b; } - } - // Add the first object to the stack of traversed objects. - aStack.push(a); - bStack.push(b); - var size = 0; - // Recursively compare objects and arrays. - if (className == '[object Array]') { - // Compare array lengths to determine if a deep comparison is necessary. - size = a.length; - result = size == b.length; - if (result) { - // Deep compare the contents, ignoring non-numeric properties. - while (size--) { - if (!(result = eq(a[size], b[size], aStack, bStack, customTesters))) { break; } - } - } - } else { - // Objects with different constructors are not equivalent, but `Object`s - // from different frames are. - var aCtor = a.constructor, bCtor = b.constructor; - if (aCtor !== bCtor && !(isFunction(aCtor) && (aCtor instanceof aCtor) && - isFunction(bCtor) && (bCtor instanceof bCtor))) { - return false; - } - // Deep compare objects. - for (var key in a) { - if (has(a, key)) { - // Count the expected number of properties. - size++; - // Deep compare each member. - if (!(result = has(b, key) && eq(a[key], b[key], aStack, bStack, customTesters))) { break; } - } - } - // Ensure that both objects contain the same number of properties. - if (result) { - for (key in b) { - if (has(b, key) && !(size--)) { break; } - } - result = !size; - } - } - // Remove the first object from the stack of traversed objects. - aStack.pop(); - bStack.pop(); - - return result; - - function has(obj, key) { - return obj.hasOwnProperty(key); - } - - function isFunction(obj) { - return typeof obj === 'function'; - } - } -}; - -getJasmineRequireObj().toBe = function() { - function toBe() { - return { - compare: function(actual, expected) { - return { - pass: actual === expected - }; - } - }; - } - - return toBe; -}; - -getJasmineRequireObj().toBeCloseTo = function() { - - function toBeCloseTo() { - return { - compare: function(actual, expected, precision) { - if (precision !== 0) { - precision = precision || 2; - } - - return { - pass: Math.abs(expected - actual) < (Math.pow(10, -precision) / 2) - }; - } - }; - } - - return toBeCloseTo; -}; - -getJasmineRequireObj().toBeDefined = function() { - function toBeDefined() { - return { - compare: function(actual) { - return { - pass: (void 0 !== actual) - }; - } - }; - } - - return toBeDefined; -}; - -getJasmineRequireObj().toBeFalsy = function() { - function toBeFalsy() { - return { - compare: function(actual) { - return { - pass: !!!actual - }; - } - }; - } - - return toBeFalsy; -}; - -getJasmineRequireObj().toBeGreaterThan = function() { - - function toBeGreaterThan() { - return { - compare: function(actual, expected) { - return { - pass: actual > expected - }; - } - }; - } - - return toBeGreaterThan; -}; - - -getJasmineRequireObj().toBeLessThan = function() { - function toBeLessThan() { - return { - - compare: function(actual, expected) { - return { - pass: actual < expected - }; - } - }; - } - - return toBeLessThan; -}; -getJasmineRequireObj().toBeNaN = function(j$) { - - function toBeNaN() { - return { - compare: function(actual) { - var result = { - pass: (actual !== actual) - }; - - if (result.pass) { - result.message = 'Expected actual not to be NaN.'; - } else { - result.message = function() { return 'Expected ' + j$.pp(actual) + ' to be NaN.'; }; - } - - return result; - } - }; - } - - return toBeNaN; -}; - -getJasmineRequireObj().toBeNull = function() { - - function toBeNull() { - return { - compare: function(actual) { - return { - pass: actual === null - }; - } - }; - } - - return toBeNull; -}; - -getJasmineRequireObj().toBeTruthy = function() { - - function toBeTruthy() { - return { - compare: function(actual) { - return { - pass: !!actual - }; - } - }; - } - - return toBeTruthy; -}; - -getJasmineRequireObj().toBeUndefined = function() { - - function toBeUndefined() { - return { - compare: function(actual) { - return { - pass: void 0 === actual - }; - } - }; - } - - return toBeUndefined; -}; - -getJasmineRequireObj().toContain = function() { - function toContain(util, customEqualityTesters) { - customEqualityTesters = customEqualityTesters || []; - - return { - compare: function(actual, expected) { - - return { - pass: util.contains(actual, expected, customEqualityTesters) - }; - } - }; - } - - return toContain; -}; - -getJasmineRequireObj().toEqual = function() { - - function toEqual(util, customEqualityTesters) { - customEqualityTesters = customEqualityTesters || []; - - return { - compare: function(actual, expected) { - var result = { - pass: false - }; - - result.pass = util.equals(actual, expected, customEqualityTesters); - - return result; - } - }; - } - - return toEqual; -}; - -getJasmineRequireObj().toHaveBeenCalled = function(j$) { - - function toHaveBeenCalled() { - return { - compare: function(actual) { - var result = {}; - - if (!j$.isSpy(actual)) { - throw new Error('Expected a spy, but got ' + j$.pp(actual) + '.'); - } - - if (arguments.length > 1) { - throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith'); - } - - result.pass = actual.calls.any(); - - result.message = result.pass ? - 'Expected spy ' + actual.and.identity() + ' not to have been called.' : - 'Expected spy ' + actual.and.identity() + ' to have been called.'; - - return result; - } - }; - } - - return toHaveBeenCalled; -}; - -getJasmineRequireObj().toHaveBeenCalledWith = function(j$) { - - function toHaveBeenCalledWith(util, customEqualityTesters) { - return { - compare: function() { - var args = Array.prototype.slice.call(arguments, 0), - actual = args[0], - expectedArgs = args.slice(1), - result = { pass: false }; - - if (!j$.isSpy(actual)) { - throw new Error('Expected a spy, but got ' + j$.pp(actual) + '.'); - } - - if (!actual.calls.any()) { - result.message = function() { return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + j$.pp(expectedArgs) + ' but it was never called.'; }; - return result; - } - - if (util.contains(actual.calls.allArgs(), expectedArgs, customEqualityTesters)) { - result.pass = true; - result.message = function() { return 'Expected spy ' + actual.and.identity() + ' not to have been called with ' + j$.pp(expectedArgs) + ' but it was.'; }; - } else { - result.message = function() { return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + j$.pp(expectedArgs) + ' but actual calls were ' + j$.pp(actual.calls.allArgs()).replace(/^\[ | \]$/g, '') + '.'; }; - } - - return result; - } - }; - } - - return toHaveBeenCalledWith; -}; - -getJasmineRequireObj().toMatch = function() { - - function toMatch() { - return { - compare: function(actual, expected) { - var regexp = new RegExp(expected); - - return { - pass: regexp.test(actual) - }; - } - }; - } - - return toMatch; -}; - -getJasmineRequireObj().toThrow = function(j$) { - - function toThrow(util) { - return { - compare: function(actual, expected) { - var result = { pass: false }, - threw = false, - thrown; - - if (typeof actual != 'function') { - throw new Error('Actual is not a Function'); - } - - try { - actual(); - } catch (e) { - threw = true; - thrown = e; - } - - if (!threw) { - result.message = 'Expected function to throw an exception.'; - return result; - } - - if (arguments.length == 1) { - result.pass = true; - result.message = function() { return 'Expected function not to throw, but it threw ' + j$.pp(thrown) + '.'; }; - - return result; - } - - if (util.equals(thrown, expected)) { - result.pass = true; - result.message = function() { return 'Expected function not to throw ' + j$.pp(expected) + '.'; }; - } else { - result.message = function() { return 'Expected function to throw ' + j$.pp(expected) + ', but it threw ' + j$.pp(thrown) + '.'; }; - } - - return result; - } - }; - } - - return toThrow; -}; - -getJasmineRequireObj().toThrowError = function(j$) { - function toThrowError (util) { - return { - compare: function(actual) { - var threw = false, - pass = {pass: true}, - fail = {pass: false}, - thrown; - - if (typeof actual != 'function') { - throw new Error('Actual is not a Function'); - } - - var errorMatcher = getMatcher.apply(null, arguments); - - try { - actual(); - } catch (e) { - threw = true; - thrown = e; - } - - if (!threw) { - fail.message = 'Expected function to throw an Error.'; - return fail; - } - - if (!(thrown instanceof Error)) { - fail.message = function() { return 'Expected function to throw an Error, but it threw ' + j$.pp(thrown) + '.'; }; - return fail; - } - - if (errorMatcher.hasNoSpecifics()) { - pass.message = 'Expected function not to throw an Error, but it threw ' + fnNameFor(thrown) + '.'; - return pass; - } - - if (errorMatcher.matches(thrown)) { - pass.message = function() { - return 'Expected function not to throw ' + errorMatcher.errorTypeDescription + errorMatcher.messageDescription() + '.'; - }; - return pass; - } else { - fail.message = function() { - return 'Expected function to throw ' + errorMatcher.errorTypeDescription + errorMatcher.messageDescription() + - ', but it threw ' + errorMatcher.thrownDescription(thrown) + '.'; - }; - return fail; - } - } - }; - - function getMatcher() { - var expected = null, - errorType = null; - - if (arguments.length == 2) { - expected = arguments[1]; - if (isAnErrorType(expected)) { - errorType = expected; - expected = null; - } - } else if (arguments.length > 2) { - errorType = arguments[1]; - expected = arguments[2]; - if (!isAnErrorType(errorType)) { - throw new Error('Expected error type is not an Error.'); - } - } - - if (expected && !isStringOrRegExp(expected)) { - if (errorType) { - throw new Error('Expected error message is not a string or RegExp.'); - } else { - throw new Error('Expected is not an Error, string, or RegExp.'); - } - } - - function messageMatch(message) { - if (typeof expected == 'string') { - return expected == message; - } else { - return expected.test(message); - } - } - - return { - errorTypeDescription: errorType ? fnNameFor(errorType) : 'an exception', - thrownDescription: function(thrown) { - var thrownName = errorType ? fnNameFor(thrown.constructor) : 'an exception', - thrownMessage = ''; - - if (expected) { - thrownMessage = ' with message ' + j$.pp(thrown.message); - } - - return thrownName + thrownMessage; - }, - messageDescription: function() { - if (expected === null) { - return ''; - } else if (expected instanceof RegExp) { - return ' with a message matching ' + j$.pp(expected); - } else { - return ' with message ' + j$.pp(expected); - } - }, - hasNoSpecifics: function() { - return expected === null && errorType === null; - }, - matches: function(error) { - return (errorType === null || error.constructor === errorType) && - (expected === null || messageMatch(error.message)); - } - }; - } - - function fnNameFor(func) { - return func.name || func.toString().match(/^\s*function\s*(\w*)\s*\(/)[1]; - } - - function isStringOrRegExp(potential) { - return potential instanceof RegExp || (typeof potential == 'string'); - } - - function isAnErrorType(type) { - if (typeof type !== 'function') { - return false; - } - - var Surrogate = function() {}; - Surrogate.prototype = type.prototype; - return (new Surrogate()) instanceof Error; - } - } - - return toThrowError; -}; - -getJasmineRequireObj().interface = function(jasmine, env) { - var jasmineInterface = { - describe: function(description, specDefinitions) { - return env.describe(description, specDefinitions); - }, - - xdescribe: function(description, specDefinitions) { - return env.xdescribe(description, specDefinitions); - }, - - fdescribe: function(description, specDefinitions) { - return env.fdescribe(description, specDefinitions); - }, - - it: function(desc, func) { - return env.it(desc, func); - }, - - xit: function(desc, func) { - return env.xit(desc, func); - }, - - fit: function(desc, func) { - return env.fit(desc, func); - }, - - beforeEach: function(beforeEachFunction) { - return env.beforeEach(beforeEachFunction); - }, - - afterEach: function(afterEachFunction) { - return env.afterEach(afterEachFunction); - }, - - beforeAll: function(beforeAllFunction) { - return env.beforeAll(beforeAllFunction); - }, - - afterAll: function(afterAllFunction) { - return env.afterAll(afterAllFunction); - }, - - expect: function(actual) { - return env.expect(actual); - }, - - pending: function() { - return env.pending(); - }, - - fail: function() { - return env.fail.apply(env, arguments); - }, - - spyOn: function(obj, methodName) { - return env.spyOn(obj, methodName); - }, - - jsApiReporter: new jasmine.JsApiReporter({ - timer: new jasmine.Timer() - }), - - jasmine: jasmine - }; - - jasmine.addCustomEqualityTester = function(tester) { - env.addCustomEqualityTester(tester); - }; - - jasmine.addMatchers = function(matchers) { - return env.addMatchers(matchers); - }; - - jasmine.clock = function() { - return env.clock; - }; - - return jasmineInterface; -}; - -getJasmineRequireObj().version = function() { - return '2.1.2'; -}; diff --git a/fnordmetric-webui/tests/lib/jasmine-2.1.2/jasmine_favicon.png b/fnordmetric-webui/tests/lib/jasmine-2.1.2/jasmine_favicon.png deleted file mode 100644 index 3b84583be4b9d5ae9cd5cae07b2dbaa5ebb0ad1c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1486 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabRA=0VDb;}332@o1PuQh8X8uGu4-^- zn3*=SA+%wV=cI;&hMB$%-Lc(MLmN8%IwwUpbcA;F2Q;)twND9bn-tpC9oR4-vb8I; zp+Bg#FSKP+P(yD-%f!%zo}iZgh=%Ua=KkP@iNVbiLYsSn8~VeW`+}P$1~&ACHcbd_ z=nZb_32o{NZkZ6&1k^hrxT!a&r8lIdAIJ@9nh?|iRMsET+#A%`AKKg-(%2K)*cZ|~ zA-J&*$PEUH08MM`4Q=iVY3vVfhN$TUscGs5sR8P31X|G_+SnTcv<0XVC=Rr!u|K4# zHyCJU6UYRR;%2BtKv^I=q#2|DECn_k@wXU|=@ zeD&J(8#iy=zH|5fgNKiwJbm`!<*V0k-oF3v@zdvTKYsrD^Y>rGdNDU(R>|^oaSW-5 z%f0Y2x+hSk{p0&HA;E60Oq1S3r7U;(X3MH#?W9nxXzk)srlC4P;cZYe&v*G}mgi%< ze(bTl{@&_)cX8b-kvX%ff9D22e*M~7edm|;|Jb8m2JBzkzKiSBKR0n*?XSyM6fxZY zJ4bb;vGKRiKP(ztq3sJx9iq$Re`-EEQU0KE$sdyqayzp6H-7x+oGKZ_6;hQ_Y|p&y z7o`N7g@ z8Yl1HKL;mFo4BdxljF1}KbfMR6nNdLyPnEap1MqBdI?{g$L58$P8+wftLX5R70)7n`#pwItnbma$w{%`M(`wpsu2rJ60rdGCc> z+~lm)viMYM`=uXwK_$`#_UtRZMO=2-IV)l2vc~)doyPvg-RmxtACQ#v{7}5$&isV+ zPws~n?@yNGy#H1=vTI?;CAl9~9#VgqJ+7`4y~R+Wyj0U+){?lm9r6mtf0gl_VmUW^ z%eje%?jM%^eLuUg#ybXaQM{yQy&8UgiQ`HQ+=iTpjfd0iJ@ ssyFi&dF4%9dEry;pNN)Q>$jg_dr3r;PHA*CFc&d+y85}Sb4q9e0J812W&i*H diff --git a/fnordmetric-webui/tests/spec/SpecHelper.js b/fnordmetric-webui/tests/spec/SpecHelper.js deleted file mode 100644 index 578b3e862..000000000 --- a/fnordmetric-webui/tests/spec/SpecHelper.js +++ /dev/null @@ -1,15 +0,0 @@ -beforeEach(function () { - jasmine.addMatchers({ - toBePlaying: function () { - return { - compare: function (actual, expected) { - var player = actual; - - return { - pass: player.currentlyPlayingSong === expected && player.isPlaying - } - } - }; - } - }); -}); diff --git a/fnordmetric-webui/tests/spec/utilSpec.js b/fnordmetric-webui/tests/spec/utilSpec.js deleted file mode 100644 index 496b38039..000000000 --- a/fnordmetric-webui/tests/spec/utilSpec.js +++ /dev/null @@ -1,665 +0,0 @@ -/** - * This file is part of the "FnordMetric" project - * Copyright (c) 2014 Laura Schlimmer - * Copyright (c) 2014 Paul Asmuth, Google Inc. - * - * FnordMetric is free software: you can redistribute it and/or modify it under - * the terms of the GNU General Public License v3.0. You should have received a - * copy of the GNU General Public License along with this program. If not, see - * . - */ - -/** - * - * TODOS: - * - * Query Playground: - * - "embed this query" opens up a popup with html/js/ruby snippets - * - prevent reload/navigation to other page (body onunload) -* - stretch: display a [fake] loading bar - * - nice to have: represent current chart and table, view in url --> renderResultPane - * - * Metric list view: - * - write meaningful error messages - * - * - fix menuitems - * - fix resize tooltip, vertical split - * - wrong URL: redirect and display message - * - fix back and for (push and pop states) - * - */ - - -if (FnordMetric === undefined) { - FnordMetric = {}; -} - -if (FnordMetric.util === undefined) { - FnordMetric.util = {}; -} - -FnordMetric.util.parseQueryString = function(qstr) { - var path; - var query_params = {}; - - if (qstr.indexOf("?") >= 0) { - path = qstr.substr(0, qstr.indexOf("?")) - - var params_str = qstr.substr(qstr.indexOf("?") + 1); - var raw_params = params_str.split('&'); - - /* set first param which defines view's view (metric, search ...) */ - var param = raw_params[0].split('='); - query_params.innerView = decodeURIComponent(param[0]); - query_params.innerViewValue = decodeURIComponent(param[1]); - - for (var i = 1; i < raw_params.length; i++) { - var param = raw_params[i].split('='); - query_params[decodeURIComponent(param[0])] = - decodeURIComponent(param[1]); - } - - } else { - path = qstr; - } - return { - "path": path, - "query_params": query_params - }; -} - -FnordMetric.util.setURLQueryString = function(hash, query_params, encode, push_state) { - var path = "#"+hash+"?"; - - path += query_params.innerView + "="; - path += (encode)? - encodeURIComponent(query_params.innerViewValue) : - query_params.innerViewValue; - - for (var param in query_params) { - if (param != "innerView" && - param != "innerViewValue" && - query_params[param] != undefined && - query_params[param].length > 0) { - - path += - "&" + param + - "=" + query_params[param]; - } - } - - if (push_state) { - window.history.pushState({url:path}, "#", path); - } - window.location.hash = path; -} - -//REMOVE?? -FnordMetric.util.setFragmentURL = function(hash, name, value, encode, push_state) { - var path = window.location.pathname; - var value = value; - if (encode == true) { - value = encodeURIComponent(value); - } - var hash = - path + "#" + hash + "?" + name + "=" + value; - window.location = hash; - if (push_state == true) { - window.history.pushState({url: hash}, "#", hash); - } -} - - -FnordMetric.util.convertArrayToString = function(array) { - var string = ""; - if (array.length > 0) { - string = array.join(", "); - } - return string; -} - -/* simple loader foreground */ -FnordMetric.util.displayLoader = function(elem) { - elem.innerHTML = "
"; -} - -/* - * loader foreground if loader can't be - * destroyed with resetting the innerHTML -*/ -FnordMetric.util.Loader = function() { - var loader = document.createElement("div"); - loader.className = "load_foreground"; - loader.innerHTML = - ""; - on_click = null; - - function onClick(on_click_new) { - on_click = on_click_new; - } - - function display(elem) { - elem.appendChild(loader); - if (on_click != null) { - loader.onclick = on_click; - } - } - - function destroy(elem) { - //FIXME - loader = elem.querySelector(".load_foreground"); - elem.removeChild(loader); - } - - return { - "display" : display, - "destroy" : destroy, - "onClick" : onClick - } -} - - -FnordMetric.util.displayErrorMessage = function(elem, msg) { - elem.innerHTML = "
" + msg + "
"; // XSS! -} - - - - -FnordMetric.util.renderPageHeader = function(text, elem) { - var header = document.createElement("h1"); - header.className = "page_header"; - header.innerHTML = text; - - elem.appendChild(header); -} - -FnordMetric.util.parseTimestamp = function(timestamp) { - if (timestamp == 0) { - return "0"; - } - - var time_str; - var timestamp = timestamp / 1000; - var now = Date.now(); - var date = new Date(timestamp); - - var offset = Math.floor( - (now - timestamp) / 1000); - if (offset < 60) { - var label = (offset == 1)? " second ago" : " seconds ago"; - time_str = offset + label; - } else if (offset < 3600) { - var time = Math.floor(offset / 60); - var label = (time == 1)? " minute ago" : " minutes ago"; - time_str = time + label; - } else if (offset < 86400) { - var time = Math.floor(offset / 3600); - var label = (time == 1)? " hour ago" : " hours ago"; - time_str = time + label; - } else { - var time = Math.floor(offset / 86400); - var label = (time == 1)? " day ago" : " days ago"; - time_str = time + label; - } - - var months = ["Jan", "Feb", "Mar", "Apr", "May", - "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] - - var minutes = date.getMinutes(); - if (minutes < 10) { - minutes = "0" + minutes; - } - - var seconds = date.getSeconds(); - if (seconds < 10) { - seconds = "0" + seconds; - } - - time_str += - " - " + months[date.getMonth()] + - " " + date.getDate() + - " " + date.getFullYear() + - " " + date.getHours() + - ":" + minutes + - ":" + seconds - - return time_str; -} - -FnordMetric.util.parseMilliTS = function(ts) { - if (ts < 1000) { - if (ts == 0) { - return " less than 1 millisecond"; - } - if (ts == 1) { - return " 1 millisecond"; - } - return ts + " milliseconds"; - } - - if (ts < 60000) { - ts = ts / 1000; - return (ts + (ts == 1? " second" : " seconds")); - } - - if (ts < 3600000){ - ts = ts / 60000; - return (ts + (ts == 1? " minute" : " minutes")); - } - - ts = ts / 3600000; - return (ts + (ts == 1? " hour" : " hours")); -} - -FnordMetric.util.humanDateToMikroTS = function(date) { - /* first version until datepicker is implemented */ - var ts; - if (date == "NOW") { - ts = Date.now(); - } - return ts; -} - - -FnordMetric.util.humanCountRows = function(tables) { - var num = 0; - tables.map(function(table) { - num += table.rows.length; - }); - return (num == 1? num + " row" : num + " rows") -} - - -FnordMetric.util.sortMetricList = function(metrics, column_index, order) { - function compare(a, b) { - if (a < b) { - if (order == "asc") { - return -1; - } else { - return 1; - } - } - if (a > b) { - if (order == "asc") { - return 1; - } else { - return -1; - } - } - return 0; - } - - var sorted_metrics = metrics; - column_index = parseInt(column_index); - switch (column_index) { - case 0: - sorted_metrics.sort(function(a, b) { - return (compare( - a[column_index].toLowerCase(), - b[column_index].toLowerCase())); - }); - break; - case 2: - sorted_metrics.sort(function(a, b) { - return (compare( - a[column_index].toLowerCase(), - b[column_index].toLowerCase())); - }); - break; - case 3: - sorted_metrics.sort(function(a, b) { - return (compare( - a[column_index], b[column_index])); - }); - break; - default: - break; - } -} - -FnordMetric.util.getHorizontalEditorHeight = function( - editor_height, result_height) { - var default_height = (window.innerHeight - 49); - editor_height = Math.max(editor_height, default_height); - var height = Math.max(editor_height, result_height); - return height; -} - -FnordMetric.util.getHorizontalEditorWidth = - function(editor_width) { - //returns the percental editor width - var wdn_width = window.innerWidth; - if (editor_width > 0) { - editor_width = wdn_width / editor_width; - } - var width = Math.max(35, Math.min(50, editor_width)); - return width; -} - - -FnordMetric.createButton = function(href, class_name, inner_HTML) { - var button = document.createElement("a"); - button.href = "#"; - if (class_name !== undefined) { - button.className = class_name; - } - if (inner_HTML !== undefined) { - button.innerHTML = inner_HTML; - } - return button; -} - -FnordMetric.util.searchMetricList = function(metrics, search_item) { - //FIXME works but seems not to be the best solution - var data = []; - metrics.map(function(item) { - if (item.key.indexOf(search_item) > -1) { - data.push(item); - } - }); - return data; -} - -FnordMetric.util.htmlEscape = function(str) { - return str; -} - - -/* returns all words that includes filter */ -FnordMetric.util.filterStringArray = function(strings, filter, limit) { - //FIXME ? - var data = []; - strings.map(function(string) { - if (string.indexOf(filter) > -1 && limit > 0) { - data.push(string); - limit--; - } - }); - return data; -} - -FnordMetric.util.toMilliSeconds = function(timestr) { - var time = timestr.split(/([a-z])/); - var conversion = { - "s" : 1000, - "m" : 60000, - "h" : 3600000, - "d" : 86400000 - } - var seconds = time[0] * conversion[time[1]]; - return parseInt(seconds, 10); -} - -FnordMetric.util.milliSecondsToTimeString = function(seconds) { - if (seconds < 60000) { - return (seconds / 1000) + "s"; - } - if (seconds < 3600000) { - return (seconds / 60000) + "m"; - } - return (seconds / 3600000) + "h"; -} - - -/* in singleMetricView */ -FnordMetric.util.generateSQLQueryFromParams = function(params) { - //FIX html escape - var table_ref = params.innerViewValue - var view = params.view; - /* column for rollups */ - var columns = params.columns.split(","); - var start_time = Math.round(params.start_time / 1000); - var end_time = Math.round(params.end_time / 1000); - var t_step = params.t_step; - var t_window = params.t_window; - var by = params.by; - - var query; - var draw_stm = - "DRAW LINECHART\n XDOMAIN\n FROM_TIMESTAMP(" + - start_time + "),\n FROM_TIMESTAMP(" + end_time + ")" - + "\n AXIS BOTTOM\n AXIS LEFT;\n\n"; - var select_expr = "SELECT\n time AS x,\n "; - var from_expr = "\n FROM\n"; - var where_expr = ""; - var group_expr = ""; - var hasAggregation = false; - - - /* complete select_expr */ - if (view == "value") { - select_expr += "value as y "; - } else if (view == "rollup_sum" || view == "rollup_count" || view == "rollup_mean") { - draw_stm = "DRAW BARCHART\n AXIS BOTTOM\n AXIS LEFT;"; - var func = (view.split("_"))[1]; - - /* if the metric hasn't any labels total is selected */ - var column = (columns[0].length > 0)? - ("`" + columns[0] + "`") : "'total'"; - - select_expr = - " SELECT " + column + " AS X, " + func + "(value) AS Y"; - - hasAggregation = true; - } else { - select_expr += - view.toLowerCase() + "(value) AS Y"; - hasAggregation = true; - } - if (by != undefined && by.length > 0) { - var series = by.replace(/,/g, " + ',' + "); - select_expr += ", " + series + " AS series"; - } - - /* complete from_expr */ - from_expr += " `" + table_ref + "`\n"; - - /*complete where_expr */ - if (start_time != undefined && end_time != undefined) { - where_expr = - " WHERE\n time > FROM_TIMESTAMP(" + start_time + ")\n" + - " AND time < FROM_TIMESTAMP(" + end_time + ")"; - } - - - /*complete group_expr if an aggregate function is selected */ - if (hasAggregation) { - group_expr = " GROUP "; - var hasGroupStm = false; - - if (t_step != undefined) { - hasGroupStm = true; - - group_expr += - "OVER TIMEWINDOW(time, " + Math.round(t_step / 1000) + ","; - - group_expr += (t_window != undefined)? - Math.round(t_window / 1000) : Math.round(t_step / 1000); - - group_expr+= ")"; - - } - - if (by != undefined && by.length > 0) { - hasGroupStm = true; - - group_expr += " BY " + by; - } - - /* aggregate function without group_by statement */ - if (!hasGroupStm) { - group_expr = ""; - } - } - - - query = - draw_stm + select_expr + from_expr + - where_expr + group_expr + ";"; - - return query; -} - - -FnordMetric.util.getMonthStr = function(index) { - var months = [ - "January", - "February", - "March", - "April", - "May", - "June", - "July", - "August", - "September", - "October", - "November", - "December"]; - - return months[index]; -} - -FnordMetric.util.isNumKey = function(keycode) { - return ( - (keycode >= 48 && keycode <= 57) || (keycode >= 96 && keycode <= 105)); -} - -/* tab, arrow-left, arrow-right, deletekeys */ -FnordMetric.util.isNavKey = function(keycode) { - return ( - keycode == 8 || - keycode == 9 || - keycode == 37 || - keycode == 39 || - keycode == 46); -} - - -FnordMetric.util.validatedTimeInput = function (time_input, type) { - var input = time_input.value; - - time_input.addEventListener('keydown', function(e) { - if (FnordMetric.util.isNumKey(e.keyCode)) { - var n = String.fromCharCode(e.keyCode); - input = time_input.value; - - if (type == "hour") { - if (input.length == 0) { - if (n >= 0 && n <= 2) { - input = n; - time_input.value = n; - } else{ - e.preventDefault(); - } - } else if (input.length == 1) { - console.log(input); - if (input < 2 || (input == 2 && n < 4)) { - input = input * 10 + n; - time_input.value += n; - } else { - e.preventDefault(); - } - } else { - e.preventDefault(); - } - - } else if (type == "minute") { - if (input.length == 0) { - if (n >= 0 && n <= 5) { - input = n; - time_input.value = n; - } else { - e.preventDefault(); - } - } else if (input.length == 1) { - input = input * 10 + n; - time_input.value += n; - } else { - e.preventDefault(); - } - } else { - e.preventDefault(); - } - } - - if (!FnordMetric.util.isNavKey(e.keyCode)) { - e.preventDefault(); - } - }, false); - -} - -FnordMetric.util.appendLeadingZero = function (num) { - var num = num; - if (typeof num == 'string') { - return (num.length > 1)? num : "0" + num; - } - return (num > 9)? num : "0" + num; -} - - -/* returns mm/dd/yyyy hh:mm */ -FnordMetric.util.getDateTimeString = function(timestamp) { - var timestamp = timestamp == undefined? - new Date() : new Date(parseInt(timestamp, 10)); - - var month = timestamp.getMonth(); - month = FnordMetric.util.appendLeadingZero(month +1); - var day = FnordMetric.util.appendLeadingZero(timestamp.getDate()); - var hours = FnordMetric.util.appendLeadingZero(timestamp.getHours()); - var minutes = FnordMetric.util.appendLeadingZero(timestamp.getMinutes()); - return ( - month + "/" + day + "/" + - timestamp.getFullYear() + " " + hours + - ":" + minutes); -} - -FnordMetric.util.makeLowerCaseUnderscore = function(string) { - return (string.toLowerCase().replace(/ /g,"_")); -} - -FnordMetric.util.reverseLowerCaseUnderscore = function(string) { - var str = string[0].toUpperCase(); - for (var i = 1; i < string.length; i++) { - if (string[i] == "_") { - str += " " + string[i+1].toUpperCase(); - i++; - } else { - str += string[i]; - } - } - return str; -} - -FnordMetric.util.removeFromString = function(start, end, str) { - var length = str.length; - if (end >= length) { - return ""; - } - - var res = str.substr(0, length - start); - res += str.substr(end, length-1); - return res; -} - -FnordMetric.util.renderFlyout = function(text, elem, left) { - var flyout = document.createElement("div"); - flyout.className = "hover_tooltip"; - flyout.innerHTML = text; - flyout.style.top = "85px"; - flyout.style.left = left + "px"; - elem.appendChild(flyout); - return flyout; -} - - -FnordMetric.util.removeIfChild = function(child_n, parent_n) { - if (typeof child_n == 'object' && child_n.parentNode == parent_n) { - parent_n.removeChild(child_n); - } -} - - - - diff --git a/fnordmetric-webui/tests/src/util.js b/fnordmetric-webui/tests/src/util.js deleted file mode 100644 index 496b38039..000000000 --- a/fnordmetric-webui/tests/src/util.js +++ /dev/null @@ -1,665 +0,0 @@ -/** - * This file is part of the "FnordMetric" project - * Copyright (c) 2014 Laura Schlimmer - * Copyright (c) 2014 Paul Asmuth, Google Inc. - * - * FnordMetric is free software: you can redistribute it and/or modify it under - * the terms of the GNU General Public License v3.0. You should have received a - * copy of the GNU General Public License along with this program. If not, see - * . - */ - -/** - * - * TODOS: - * - * Query Playground: - * - "embed this query" opens up a popup with html/js/ruby snippets - * - prevent reload/navigation to other page (body onunload) -* - stretch: display a [fake] loading bar - * - nice to have: represent current chart and table, view in url --> renderResultPane - * - * Metric list view: - * - write meaningful error messages - * - * - fix menuitems - * - fix resize tooltip, vertical split - * - wrong URL: redirect and display message - * - fix back and for (push and pop states) - * - */ - - -if (FnordMetric === undefined) { - FnordMetric = {}; -} - -if (FnordMetric.util === undefined) { - FnordMetric.util = {}; -} - -FnordMetric.util.parseQueryString = function(qstr) { - var path; - var query_params = {}; - - if (qstr.indexOf("?") >= 0) { - path = qstr.substr(0, qstr.indexOf("?")) - - var params_str = qstr.substr(qstr.indexOf("?") + 1); - var raw_params = params_str.split('&'); - - /* set first param which defines view's view (metric, search ...) */ - var param = raw_params[0].split('='); - query_params.innerView = decodeURIComponent(param[0]); - query_params.innerViewValue = decodeURIComponent(param[1]); - - for (var i = 1; i < raw_params.length; i++) { - var param = raw_params[i].split('='); - query_params[decodeURIComponent(param[0])] = - decodeURIComponent(param[1]); - } - - } else { - path = qstr; - } - return { - "path": path, - "query_params": query_params - }; -} - -FnordMetric.util.setURLQueryString = function(hash, query_params, encode, push_state) { - var path = "#"+hash+"?"; - - path += query_params.innerView + "="; - path += (encode)? - encodeURIComponent(query_params.innerViewValue) : - query_params.innerViewValue; - - for (var param in query_params) { - if (param != "innerView" && - param != "innerViewValue" && - query_params[param] != undefined && - query_params[param].length > 0) { - - path += - "&" + param + - "=" + query_params[param]; - } - } - - if (push_state) { - window.history.pushState({url:path}, "#", path); - } - window.location.hash = path; -} - -//REMOVE?? -FnordMetric.util.setFragmentURL = function(hash, name, value, encode, push_state) { - var path = window.location.pathname; - var value = value; - if (encode == true) { - value = encodeURIComponent(value); - } - var hash = - path + "#" + hash + "?" + name + "=" + value; - window.location = hash; - if (push_state == true) { - window.history.pushState({url: hash}, "#", hash); - } -} - - -FnordMetric.util.convertArrayToString = function(array) { - var string = ""; - if (array.length > 0) { - string = array.join(", "); - } - return string; -} - -/* simple loader foreground */ -FnordMetric.util.displayLoader = function(elem) { - elem.innerHTML = "
"; -} - -/* - * loader foreground if loader can't be - * destroyed with resetting the innerHTML -*/ -FnordMetric.util.Loader = function() { - var loader = document.createElement("div"); - loader.className = "load_foreground"; - loader.innerHTML = - ""; - on_click = null; - - function onClick(on_click_new) { - on_click = on_click_new; - } - - function display(elem) { - elem.appendChild(loader); - if (on_click != null) { - loader.onclick = on_click; - } - } - - function destroy(elem) { - //FIXME - loader = elem.querySelector(".load_foreground"); - elem.removeChild(loader); - } - - return { - "display" : display, - "destroy" : destroy, - "onClick" : onClick - } -} - - -FnordMetric.util.displayErrorMessage = function(elem, msg) { - elem.innerHTML = "
" + msg + "
"; // XSS! -} - - - - -FnordMetric.util.renderPageHeader = function(text, elem) { - var header = document.createElement("h1"); - header.className = "page_header"; - header.innerHTML = text; - - elem.appendChild(header); -} - -FnordMetric.util.parseTimestamp = function(timestamp) { - if (timestamp == 0) { - return "0"; - } - - var time_str; - var timestamp = timestamp / 1000; - var now = Date.now(); - var date = new Date(timestamp); - - var offset = Math.floor( - (now - timestamp) / 1000); - if (offset < 60) { - var label = (offset == 1)? " second ago" : " seconds ago"; - time_str = offset + label; - } else if (offset < 3600) { - var time = Math.floor(offset / 60); - var label = (time == 1)? " minute ago" : " minutes ago"; - time_str = time + label; - } else if (offset < 86400) { - var time = Math.floor(offset / 3600); - var label = (time == 1)? " hour ago" : " hours ago"; - time_str = time + label; - } else { - var time = Math.floor(offset / 86400); - var label = (time == 1)? " day ago" : " days ago"; - time_str = time + label; - } - - var months = ["Jan", "Feb", "Mar", "Apr", "May", - "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] - - var minutes = date.getMinutes(); - if (minutes < 10) { - minutes = "0" + minutes; - } - - var seconds = date.getSeconds(); - if (seconds < 10) { - seconds = "0" + seconds; - } - - time_str += - " - " + months[date.getMonth()] + - " " + date.getDate() + - " " + date.getFullYear() + - " " + date.getHours() + - ":" + minutes + - ":" + seconds - - return time_str; -} - -FnordMetric.util.parseMilliTS = function(ts) { - if (ts < 1000) { - if (ts == 0) { - return " less than 1 millisecond"; - } - if (ts == 1) { - return " 1 millisecond"; - } - return ts + " milliseconds"; - } - - if (ts < 60000) { - ts = ts / 1000; - return (ts + (ts == 1? " second" : " seconds")); - } - - if (ts < 3600000){ - ts = ts / 60000; - return (ts + (ts == 1? " minute" : " minutes")); - } - - ts = ts / 3600000; - return (ts + (ts == 1? " hour" : " hours")); -} - -FnordMetric.util.humanDateToMikroTS = function(date) { - /* first version until datepicker is implemented */ - var ts; - if (date == "NOW") { - ts = Date.now(); - } - return ts; -} - - -FnordMetric.util.humanCountRows = function(tables) { - var num = 0; - tables.map(function(table) { - num += table.rows.length; - }); - return (num == 1? num + " row" : num + " rows") -} - - -FnordMetric.util.sortMetricList = function(metrics, column_index, order) { - function compare(a, b) { - if (a < b) { - if (order == "asc") { - return -1; - } else { - return 1; - } - } - if (a > b) { - if (order == "asc") { - return 1; - } else { - return -1; - } - } - return 0; - } - - var sorted_metrics = metrics; - column_index = parseInt(column_index); - switch (column_index) { - case 0: - sorted_metrics.sort(function(a, b) { - return (compare( - a[column_index].toLowerCase(), - b[column_index].toLowerCase())); - }); - break; - case 2: - sorted_metrics.sort(function(a, b) { - return (compare( - a[column_index].toLowerCase(), - b[column_index].toLowerCase())); - }); - break; - case 3: - sorted_metrics.sort(function(a, b) { - return (compare( - a[column_index], b[column_index])); - }); - break; - default: - break; - } -} - -FnordMetric.util.getHorizontalEditorHeight = function( - editor_height, result_height) { - var default_height = (window.innerHeight - 49); - editor_height = Math.max(editor_height, default_height); - var height = Math.max(editor_height, result_height); - return height; -} - -FnordMetric.util.getHorizontalEditorWidth = - function(editor_width) { - //returns the percental editor width - var wdn_width = window.innerWidth; - if (editor_width > 0) { - editor_width = wdn_width / editor_width; - } - var width = Math.max(35, Math.min(50, editor_width)); - return width; -} - - -FnordMetric.createButton = function(href, class_name, inner_HTML) { - var button = document.createElement("a"); - button.href = "#"; - if (class_name !== undefined) { - button.className = class_name; - } - if (inner_HTML !== undefined) { - button.innerHTML = inner_HTML; - } - return button; -} - -FnordMetric.util.searchMetricList = function(metrics, search_item) { - //FIXME works but seems not to be the best solution - var data = []; - metrics.map(function(item) { - if (item.key.indexOf(search_item) > -1) { - data.push(item); - } - }); - return data; -} - -FnordMetric.util.htmlEscape = function(str) { - return str; -} - - -/* returns all words that includes filter */ -FnordMetric.util.filterStringArray = function(strings, filter, limit) { - //FIXME ? - var data = []; - strings.map(function(string) { - if (string.indexOf(filter) > -1 && limit > 0) { - data.push(string); - limit--; - } - }); - return data; -} - -FnordMetric.util.toMilliSeconds = function(timestr) { - var time = timestr.split(/([a-z])/); - var conversion = { - "s" : 1000, - "m" : 60000, - "h" : 3600000, - "d" : 86400000 - } - var seconds = time[0] * conversion[time[1]]; - return parseInt(seconds, 10); -} - -FnordMetric.util.milliSecondsToTimeString = function(seconds) { - if (seconds < 60000) { - return (seconds / 1000) + "s"; - } - if (seconds < 3600000) { - return (seconds / 60000) + "m"; - } - return (seconds / 3600000) + "h"; -} - - -/* in singleMetricView */ -FnordMetric.util.generateSQLQueryFromParams = function(params) { - //FIX html escape - var table_ref = params.innerViewValue - var view = params.view; - /* column for rollups */ - var columns = params.columns.split(","); - var start_time = Math.round(params.start_time / 1000); - var end_time = Math.round(params.end_time / 1000); - var t_step = params.t_step; - var t_window = params.t_window; - var by = params.by; - - var query; - var draw_stm = - "DRAW LINECHART\n XDOMAIN\n FROM_TIMESTAMP(" + - start_time + "),\n FROM_TIMESTAMP(" + end_time + ")" - + "\n AXIS BOTTOM\n AXIS LEFT;\n\n"; - var select_expr = "SELECT\n time AS x,\n "; - var from_expr = "\n FROM\n"; - var where_expr = ""; - var group_expr = ""; - var hasAggregation = false; - - - /* complete select_expr */ - if (view == "value") { - select_expr += "value as y "; - } else if (view == "rollup_sum" || view == "rollup_count" || view == "rollup_mean") { - draw_stm = "DRAW BARCHART\n AXIS BOTTOM\n AXIS LEFT;"; - var func = (view.split("_"))[1]; - - /* if the metric hasn't any labels total is selected */ - var column = (columns[0].length > 0)? - ("`" + columns[0] + "`") : "'total'"; - - select_expr = - " SELECT " + column + " AS X, " + func + "(value) AS Y"; - - hasAggregation = true; - } else { - select_expr += - view.toLowerCase() + "(value) AS Y"; - hasAggregation = true; - } - if (by != undefined && by.length > 0) { - var series = by.replace(/,/g, " + ',' + "); - select_expr += ", " + series + " AS series"; - } - - /* complete from_expr */ - from_expr += " `" + table_ref + "`\n"; - - /*complete where_expr */ - if (start_time != undefined && end_time != undefined) { - where_expr = - " WHERE\n time > FROM_TIMESTAMP(" + start_time + ")\n" + - " AND time < FROM_TIMESTAMP(" + end_time + ")"; - } - - - /*complete group_expr if an aggregate function is selected */ - if (hasAggregation) { - group_expr = " GROUP "; - var hasGroupStm = false; - - if (t_step != undefined) { - hasGroupStm = true; - - group_expr += - "OVER TIMEWINDOW(time, " + Math.round(t_step / 1000) + ","; - - group_expr += (t_window != undefined)? - Math.round(t_window / 1000) : Math.round(t_step / 1000); - - group_expr+= ")"; - - } - - if (by != undefined && by.length > 0) { - hasGroupStm = true; - - group_expr += " BY " + by; - } - - /* aggregate function without group_by statement */ - if (!hasGroupStm) { - group_expr = ""; - } - } - - - query = - draw_stm + select_expr + from_expr + - where_expr + group_expr + ";"; - - return query; -} - - -FnordMetric.util.getMonthStr = function(index) { - var months = [ - "January", - "February", - "March", - "April", - "May", - "June", - "July", - "August", - "September", - "October", - "November", - "December"]; - - return months[index]; -} - -FnordMetric.util.isNumKey = function(keycode) { - return ( - (keycode >= 48 && keycode <= 57) || (keycode >= 96 && keycode <= 105)); -} - -/* tab, arrow-left, arrow-right, deletekeys */ -FnordMetric.util.isNavKey = function(keycode) { - return ( - keycode == 8 || - keycode == 9 || - keycode == 37 || - keycode == 39 || - keycode == 46); -} - - -FnordMetric.util.validatedTimeInput = function (time_input, type) { - var input = time_input.value; - - time_input.addEventListener('keydown', function(e) { - if (FnordMetric.util.isNumKey(e.keyCode)) { - var n = String.fromCharCode(e.keyCode); - input = time_input.value; - - if (type == "hour") { - if (input.length == 0) { - if (n >= 0 && n <= 2) { - input = n; - time_input.value = n; - } else{ - e.preventDefault(); - } - } else if (input.length == 1) { - console.log(input); - if (input < 2 || (input == 2 && n < 4)) { - input = input * 10 + n; - time_input.value += n; - } else { - e.preventDefault(); - } - } else { - e.preventDefault(); - } - - } else if (type == "minute") { - if (input.length == 0) { - if (n >= 0 && n <= 5) { - input = n; - time_input.value = n; - } else { - e.preventDefault(); - } - } else if (input.length == 1) { - input = input * 10 + n; - time_input.value += n; - } else { - e.preventDefault(); - } - } else { - e.preventDefault(); - } - } - - if (!FnordMetric.util.isNavKey(e.keyCode)) { - e.preventDefault(); - } - }, false); - -} - -FnordMetric.util.appendLeadingZero = function (num) { - var num = num; - if (typeof num == 'string') { - return (num.length > 1)? num : "0" + num; - } - return (num > 9)? num : "0" + num; -} - - -/* returns mm/dd/yyyy hh:mm */ -FnordMetric.util.getDateTimeString = function(timestamp) { - var timestamp = timestamp == undefined? - new Date() : new Date(parseInt(timestamp, 10)); - - var month = timestamp.getMonth(); - month = FnordMetric.util.appendLeadingZero(month +1); - var day = FnordMetric.util.appendLeadingZero(timestamp.getDate()); - var hours = FnordMetric.util.appendLeadingZero(timestamp.getHours()); - var minutes = FnordMetric.util.appendLeadingZero(timestamp.getMinutes()); - return ( - month + "/" + day + "/" + - timestamp.getFullYear() + " " + hours + - ":" + minutes); -} - -FnordMetric.util.makeLowerCaseUnderscore = function(string) { - return (string.toLowerCase().replace(/ /g,"_")); -} - -FnordMetric.util.reverseLowerCaseUnderscore = function(string) { - var str = string[0].toUpperCase(); - for (var i = 1; i < string.length; i++) { - if (string[i] == "_") { - str += " " + string[i+1].toUpperCase(); - i++; - } else { - str += string[i]; - } - } - return str; -} - -FnordMetric.util.removeFromString = function(start, end, str) { - var length = str.length; - if (end >= length) { - return ""; - } - - var res = str.substr(0, length - start); - res += str.substr(end, length-1); - return res; -} - -FnordMetric.util.renderFlyout = function(text, elem, left) { - var flyout = document.createElement("div"); - flyout.className = "hover_tooltip"; - flyout.innerHTML = text; - flyout.style.top = "85px"; - flyout.style.left = left + "px"; - elem.appendChild(flyout); - return flyout; -} - - -FnordMetric.util.removeIfChild = function(child_n, parent_n) { - if (typeof child_n == 'object' && child_n.parentNode == parent_n) { - parent_n.removeChild(child_n); - } -} - - - - From ed668b5d3ff7542f3d5f8e12f382b62a16b370d4 Mon Sep 17 00:00:00 2001 From: Laura Schlimmer Date: Wed, 19 Nov 2014 13:59:31 +0000 Subject: [PATCH 14/15] fix url path --- fnordmetric-webui/fnordmetric-webui-util.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fnordmetric-webui/fnordmetric-webui-util.js b/fnordmetric-webui/fnordmetric-webui-util.js index 496b38039..828378e87 100644 --- a/fnordmetric-webui/fnordmetric-webui-util.js +++ b/fnordmetric-webui/fnordmetric-webui-util.js @@ -43,7 +43,8 @@ FnordMetric.util.parseQueryString = function(qstr) { var query_params = {}; if (qstr.indexOf("?") >= 0) { - path = qstr.substr(0, qstr.indexOf("?")) + path = qstr.substr(0, qstr.indexOf("?")); + path = path.replace("#", ""); var params_str = qstr.substr(qstr.indexOf("?") + 1); var raw_params = params_str.split('&'); @@ -497,7 +498,6 @@ FnordMetric.util.generateSQLQueryFromParams = function(params) { query = draw_stm + select_expr + from_expr + where_expr + group_expr + ";"; - return query; } From 81450215cb549c29c4290face337ca37300c5ff1 Mon Sep 17 00:00:00 2001 From: Laura Schlimmer Date: Wed, 19 Nov 2014 15:06:37 +0000 Subject: [PATCH 15/15] change datepicker time input --- fnordmetric-webui/fnordmetric-webui-util.js | 25 +++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/fnordmetric-webui/fnordmetric-webui-util.js b/fnordmetric-webui/fnordmetric-webui-util.js index 828378e87..ffbc1919d 100644 --- a/fnordmetric-webui/fnordmetric-webui-util.js +++ b/fnordmetric-webui/fnordmetric-webui-util.js @@ -90,6 +90,7 @@ FnordMetric.util.setURLQueryString = function(hash, query_params, encode, push_s } if (push_state) { + console.log("push state"); window.history.pushState({url:path}, "#", path); } window.location.hash = path; @@ -522,7 +523,7 @@ FnordMetric.util.getMonthStr = function(index) { FnordMetric.util.isNumKey = function(keycode) { return ( - (keycode >= 48 && keycode <= 57) || (keycode >= 96 && keycode <= 105)); + (keycode >= 48 && keycode <= 57)); } /* tab, arrow-left, arrow-right, deletekeys */ @@ -536,14 +537,16 @@ FnordMetric.util.isNavKey = function(keycode) { } +/* FnordMetric.util.validatedTimeInput = function (time_input, type) { var input = time_input.value; - - time_input.addEventListener('keydown', function(e) { + // bug: keypress is invoked twice + time_input.addEventListener('keypress', function(e) { + console.log("keypress"); +>>>>>>> Stashed changes if (FnordMetric.util.isNumKey(e.keyCode)) { var n = String.fromCharCode(e.keyCode); input = time_input.value; - if (type == "hour") { if (input.length == 0) { if (n >= 0 && n <= 2) { @@ -553,7 +556,11 @@ FnordMetric.util.validatedTimeInput = function (time_input, type) { e.preventDefault(); } } else if (input.length == 1) { +<<<<<<< Updated upstream console.log(input); +======= + +>>>>>>> Stashed changes if (input < 2 || (input == 2 && n < 4)) { input = input * 10 + n; time_input.value += n; @@ -588,6 +595,16 @@ FnordMetric.util.validatedTimeInput = function (time_input, type) { } }, false); +}*/ + +FnordMetric.util.validatedTimeInput = function(time_input) { + time_input.maxLength = "2"; + time_input.addEventListener('keypress', function(e) { + if (!FnordMetric.util.isNavKey(e.keyCode) && + !FnordMetric.util.isNumKey(e.keyCode)) { + e.preventDefault(); + } + },false); } FnordMetric.util.appendLeadingZero = function (num) {