diff --git a/SpecRunner.html b/SpecRunner.html
new file mode 100644
index 0000000..eecbd0b
--- /dev/null
+++ b/SpecRunner.html
@@ -0,0 +1,26 @@
+
+
+
+ Testing with Jasmine
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/js/TimeExplorer.js b/js/TimeExplorer.js
index 810a13c..43cb296 100644
--- a/js/TimeExplorer.js
+++ b/js/TimeExplorer.js
@@ -4,11 +4,7 @@
* @param {array} values - Array of values for output array
*/
function zip_arrays(keys, values) {
- var returnValues = {};
- values.forEach(function(val, i) {
- returnValues[keys[i]] = val;
- });
- return returnValues
+ return Object.assign(...keys.map((v,i) => ( {[v]: values[i]} )));
}
/**
@@ -32,62 +28,30 @@ function get_url_params() {
* @param {string} timestring - A string representing a time, such as "10:30am"
*/
function timeParse(timestring) {
- var ampm_match = timestring.match(/(.*)[AP]M$/);
- if (ampm_match) {
- var pm_match = timestring.match(/(.*)pm\s*$/i);
- if (pm_match) {
- var timeArray = pm_match[1].split(':');
- if (timeArray.length == 2) {
- var hour = parseInt(timeArray[0])+12;
- var minute = parseInt(timeArray[1]);
- if (hour == 24) {
- hour = 12;
- }
- return [hour,minute];
- } else {
- return null;
- }
- } else {
- var timeArray = ampm_match[1].split(':');
- if (timeArray.length == 2) {
- var hour = parseInt(timeArray[0]);
- var minute = parseInt(timeArray[1]);
- if (hour == 12) {
- hour = 24;
- }
- return [hour,minute];
- } else {
- return null;
- }
+ let pmMatch = timestring.match(/(.*)pm\s*$/i);
+ let timeArray = pmMatch ? pmMatch[1].split(':') : timestring.split(':');
+ if (timeArray.length === 2) {
+ let hour = pmMatch ? parseInt(timeArray[0])+12 : parseInt(timeArray[0]);
+ let minute = parseInt(timeArray[1]);
+ if (pmMatch && hour == 12) {
+ hour = 24;
}
+ return [hour,minute]
} else {
- var timeArray = timestring.split(':');
- if (timeArray.length == 2) {
- var hour = parseInt(timeArray[0]);
- var minute = parseInt(timeArray[1]);
- return [hour,minute];
- } else {
- return null;
- }
+ return null;
}
}
/**
* Zero-pads a number to a 4-digit string
*/
-function padToNDigit(number,nDigits) {
- var str = "" + number
- if (str[0] == "-" ){
- str = str.slice(1);
- var pad = Array(nDigits+1).join("0");
- var ans = pad.substring(0, pad.length - str.length) + str;
- ans = "-" + ans;
- return ans
- } else {
- var pad = Array(nDigits+1).join("0");
- var ans = pad.substring(0, pad.length - str.length) + str;
- return ans
- }
+function padToNDigit(number, nDigits) {
+ let str = String(number);
+ let currentLength = str[0] === '-' ? str.length - 1 : str.length;
+ let digits = nDigits - (currentLength - 1);
+ let pad = Array(digits).join("0");
+ let insert = str[0] === "-" ? 1 : 0;
+ return str.slice(0, insert) + pad + str.slice(insert);
}
/**
@@ -95,11 +59,7 @@ function padToNDigit(number,nDigits) {
* @param {string} id_string - string to be modified
*/
function plainId(id_string) {
- if (id_string.startsWith('#')) {
- return id_string.slice(1);
- } else {
- return id_string;
- }
+ return id_string.startsWith('#') ? id_string.slice(1) : id_string;
}
/**
@@ -110,33 +70,27 @@ function plainId(id_string) {
* @param {datetime} startDate
* @param {datetime} endDate
*/
-function GetDisplayDate(row,startDate,endDate) {
- if (startDate == null) {
- return null;
- }
- var months = ["January","February","March","April","May","June","July","August","September","October","November","December"];
- displayDate = "";
- if (row['Month'] && row['Month'] != "") { displayDate += months[startDate.getMonth()]+" "; }
- if (row['Day'] && row['Day'] != "") { displayDate += startDate.getDate()+", "; }
- if (row['Year'] && row['Year'] != "") { displayDate += startDate.getFullYear(); }
- if (row['Time'] && row['Time'] != "") {
- displayDate += " at " + startDate.getHours() + ":";
- var minutes = String(startDate.getMinutes()).length == 1 ? "0"+String(startDate.getMinutes()) : String(startDate.getMinutes());
- displayDate += minutes;
- }
- if (endDate) {
- displayDate += " - ";
- if (row['End Month'] && row['End Month'] != "") { displayDate += months[endDate.getMonth()]+" "; }
- if (row['End Day'] && row['End Day'] != "") { displayDate += endDate.getDate()+", "; }
- if (row['End Year'] && row['End Year'] != "") { displayDate += endDate.getFullYear(); }
- if (row['End Time'] && row['End Time'] != "") {
- displayDate += " at " + endDate.getHours() + ":";
- var minutes = String(endDate.getMinutes()).length == 1 ? "0"+String(endDate.getMinutes()) : String(endDate.getMinutes());
- displayDate += minutes;
- }
- }
- return displayDate;
-}
+ function GetDisplayDate(row, startDate, endDate) {
+ if (startDate == null) {return null;}
+ let displayDate = "";
+ displayDate = appendDate(row, displayDate, startDate, "");
+ if(endDate != null) {displayDate = appendDate(row, displayDate, endDate, "End ");}
+ return displayDate;
+
+ function appendDate(row, displayDate, date, rowIndicator) {
+ const months = ["January","February","March","April","May","June","July","August","September","October","November","December"];
+ if (rowIndicator === "End ") {displayDate += " - ";}
+ if (row[rowIndicator + 'Month'] && row[rowIndicator + 'Month'] != "") { displayDate += months[date.getMonth()]+" "; }
+ if (row[rowIndicator + 'Day'] && row[rowIndicator + 'Day'] != "") { displayDate += date.getDate()+", "; }
+ if (row[rowIndicator + 'Year'] && row[rowIndicator + 'Year'] != "") { displayDate += date.getFullYear(); }
+ if (row[rowIndicator + 'Time'] && row[rowIndicator + 'Time'] != "") {
+ displayDate += " at " + date.getHours() + ":";
+ let minutes = String(date.getMinutes()).length == 1 ? "0"+String(date.getMinutes()) : String(date.getMinutes());
+ displayDate += minutes;
+ }
+ return displayDate;
+ }
+ }
/**
* // TODO: What is this function for?
@@ -161,9 +115,7 @@ function testFilter(self) {
* @param {string} displayDate - String representing date as it should be displayed
*/
function GetDisplayTitle(row,displayDate) {
- var output = ""
- return output;
+ return "";
}
/**
@@ -321,12 +273,7 @@ class TimeExplorer {
}
get_tag_col() {
- var url_params = get_url_params();
- if ('tag_col' in url_params) {
- return url_params['tag_col'];
- } else {
- return 'Tags';
- }
+ return 'tag_col' in get_url_params() ? url_params['tag_col'] : 'Tags';
}
/**
@@ -392,9 +339,8 @@ class TimeExplorer {
*
*/
create_timeline(options) {
- var container = document.getElementById('ft-visualization');
- var timeline = new vis.Timeline(container,options.timelineOptions);
- return timeline;
+ let container = document.getElementById('ft-visualization');
+ return new vis.Timeline(container,options.timelineOptions);
}
/**
@@ -571,15 +517,14 @@ class TimeExplorer {
* @param {string} sheet_id - ID of Google spreadsheet containing data
*/
get_sheet_data(sheet_id) {
- var self = this;
- var dfd = $.Deferred();
- var request_url = `https://sheets.googleapis.com/v4/spreadsheets/${sheet_id}/values/A:ZZZ?key=${this.api_key}`;
+ let self = this;
+ let dfd = $.Deferred();
+ const request_url = `https://sheets.googleapis.com/v4/spreadsheets/${sheet_id}/values/A:ZZZ?key=${this.api_key}`;
$.getJSON(request_url).done(function(data) {
- var columns = data.values[0];
- for (var i = 1; i < data.values.length; i++) {
- var values = zip_arrays(columns, data.values[i]);
- self.sheet_data.push(values);
- };
+ let columns = data.values[0];
+ self.sheet_data = data.values.slice(1).map( (item)=> {
+ return zip_arrays(columns,item);
+ });
dfd.resolve();
});
return dfd.promise();
@@ -607,12 +552,10 @@ class TimeExplorer {
* @uses get_sheet_data
*/
get_all_sheet_data() {
- var self = this;
- var promises = [];
- for (var i = 0; i < this.sheet_ids.length; i++) {
- var sheet_id = this.sheet_ids[i];
- promises.push(this.get_sheet_data(sheet_id));
- };
+ let self = this;
+ const promises = this.sheet_ids.map( (id) => {
+ return this.get_sheet_data(id);
+ });
return $.when.apply($,promises);
};
@@ -812,28 +755,24 @@ class TimeExplorer {
* Uses groups from Timeline JS to color the timeline.
*/
set_groups(self) {
- var groups = [];
- for (var i = 0; i < self.items.length; i++) {
- if (self.items[i]['sheet_group']) {
- var group = self.items[i]['sheet_group'];
- var slug = self.slugify(group);
- if ($.inArray(group,groups) == -1) {
- groups.push(group);
- }
- self.items[i]['className'] = slug;
+ let groups = self.items.map( (item)=> {
+ if (item['sheet_group']) {
+ item['className'] = self.slugify(item['sheet_group']);
+ return item['sheet_group'];
} else {
- self.items[i]['className'] = "Ungrouped";
- self.items[i]['group_slug'] = "Ungrouped";
- if ($.inArray('Ungrouped',groups) == -1) {
- groups.push("Ungrouped");
- }
+ item['className'] = "Ungrouped";
+ item['group_slug'] = "Ungrouped";
+ return "Ungrouped";
}
- }
+ });
+ groups.filter(self.onlyUnique);
groups.sort();
self.setup_group_ui(self, groups);
return groups;
}
+
+
/**
* Sets up color scheme and filters for groups.
*/
@@ -882,19 +821,15 @@ class TimeExplorer {
* Sets up tags to be used as filters
*/
set_tags(self) {
- var tags = [];
- for (var i = 0; i < self.items.length; i++) {
- if (self.items[i]['tags']) {
- var these_tags = self.items[i]['tags'];
- var slugs = these_tags.map(self.slugify);
- tags = tags.concat(these_tags);
- if (self.items[i]['className']) {
- self.items[i]['className'] = self.items[i]['className'] + ' ' + slugs.join(' ');
- } else {
- self.items[i]['className'] = slugs.join(' ');
- }
+ let tags = [];
+ self.items.forEach( (item)=> {
+ if (item['tags']) {
+ let slugs = item['tags'].map(self.slugify);
+ let concatter = item['classname'] ? item['classname'] : '';
+ item['classname'] = concatter + ' ' + slugs.join(' ');
+ tags = tags.concat(item['tags']);
}
- }
+ });
tags = tags.filter( self.onlyUnique );
tags.sort();
self.setup_filters(self,tags,"Tags");
@@ -981,23 +916,16 @@ class TimeExplorer {
*/
set_filters(slug, self) {
// Set Group filters
- var activeGroups = [];
- var groupCheckboxes = $(".Groups input.filter-checkbox");
- for (var i = 0; i < groupCheckboxes.length; i++) {
- if (groupCheckboxes[i].checked) {
- activeGroups.push(groupCheckboxes[i].value);
- }
- }
- self.filters.activeGroups = activeGroups;
+ const groupCheckboxes = $(".Groups input.filter-checkbox");
+ self.filters.activeGroups = Array.prototype.map.call(groupCheckboxes, (box)=> {
+ if (box.checked) {return box.value;}
+ });
// Set Tag filters
- var activeTags = [];
- var tagCheckboxes = $(".Tags input.filter-checkbox");
- for (var i = 0; i < tagCheckboxes.length; i++) {
- if (tagCheckboxes[i].checked) {
- activeTags.push(tagCheckboxes[i].value);
- }
- }
- self.filters.activeTags = activeTags;
+ const tagCheckboxes = $(".Tags input.filter-checkbox");
+ self.filters.activeTags = Array.prototype.map.call(tagCheckboxes, (box)=> {
+ if(box.checked) {return box.value;}
+ });
+
if ($("#tag-options").length > 0) {
if ($("#tag-option-any")[0].checked) {
self.filters.tagOptions = "any";
@@ -1106,14 +1034,8 @@ class TimeExplorer {
* @param {string} text - the text to be made into a slug.
*/
slugify(text) {
- if (typeof(text) == "string") {
- var output = text.trim()
- var pattern = /[\s~!@$%^&*()+=,./';:"?><[\] \\{}|`#]+/g
- output = output.replace(pattern,'_')
- return output
- } else {
- return "";
- }
+ const pattern = /[\s~!@$%^&*()+=,./';:"?><[\] \\{}|`#]+/g;
+ return typeof(text) == "string" ? text.trim().replace(pattern,'_') : "";
}
/**
diff --git a/js/TimeExplorerSpec.js b/js/TimeExplorerSpec.js
new file mode 100644
index 0000000..9447a28
--- /dev/null
+++ b/js/TimeExplorerSpec.js
@@ -0,0 +1,201 @@
+describe('Testing the functions of the TimeExplorer file', ()=> {
+
+ it('zip_arrays should zip two arrays of equal length', ()=> {
+ const arr1 = ["Joan", "Bill", "Bob"];
+ const arr2 = [1,2,3];
+ expect(zip_arrays(arr1,arr2)).toEqual({"Joan": 1, "Bill": 2, "Bob": 3});
+ })
+
+ it('timeParse should parse AM time', ()=> {
+ const times = [
+ "10:20",
+ "10:20am",
+ "10:20AM",
+ "10:20aM"
+ ];
+ times.forEach( time => expect(timeParse(time)).toEqual([10,20]));
+ })
+
+ it('timeParse should parse PM time', ()=> {
+ const times = [
+ "10:20pm",
+ "10:20PM",
+ "10:20pM"
+ ];
+ times.forEach( time => expect(timeParse(time)).toEqual([22,20]));
+ })
+
+ it('padToNDigit should pad a given number by given digits', ()=> {
+ const digits = 4;
+ expect(padToNDigit(6, digits)).toEqual("0006");
+ expect(padToNDigit(-6, digits)).toEqual("-0006");
+ expect(padToNDigit(1987, digits)).toEqual("1987");
+ expect(padToNDigit(-1987, digits)).toEqual("-1987");
+ })
+
+ it('plainId should return id string without preceding #', ()=> {
+ const ids = ["m1id1sgreat", "#m1id1sgreat"];
+ ids.forEach( id => expect(plainId(id)).toEqual("m1id1sgreat"));
+ })
+
+ it('GetDisplayTitle should return html with title', ()=> {
+ const row1 = {
+ 'Headline': "HEADLINE",
+ }
+ const displayDate = "July 6th";
+ expect(GetDisplayTitle(row1, displayDate)).toBe('');
+ })
+
+ it('GetDisplayDate should return a formated timeframe', ()=> {
+ const row1 = {
+ 'Month': 11,
+ 'Day': 10,
+ 'Year': 1987,
+ 'Time': "10:30am",
+ 'End Month': 11,
+ 'End Day': 10,
+ 'End Year': 2001,
+ 'End Time': "10:30am"
+ }
+ const row2 = {
+ 'Month': 11,
+ 'Day': 10,
+ 'Year': 1987,
+ 'Time': "10:30am"
+ }
+ let end = new Date("June 6 2019 2:30");
+ let start = new Date("September 9 1987 12:30");
+ expect(GetDisplayDate(row1, start, end)).toEqual("September 9, 1987 at 12:30 - June 6, 2019 at 2:30");
+ expect(GetDisplayDate(row2, start, end)).toEqual("September 9, 1987 at 12:30 - ");
+ expect(GetDisplayDate(row1, start)).toEqual("September 9, 1987 at 12:30");
+ expect(GetDisplayDate(row2, start)).toEqual("September 9, 1987 at 12:30");
+ })
+
+})
+
+describe('Testing the TimeExplorer class', () => {
+ let el;
+ let div;
+ let explorer;
+ const api_key = "AIzaSyCA8GIsjw-QL-CC1v6fgDWmDyyhRM_ZESE";
+ const new_explorer = () => {
+ return new TimeExplorer(api_key);
+ }
+
+ beforeEach( ()=> {
+ el = document.createElement('html');
+ div = document.createElement('div');
+ div.setAttribute("id", "timeline");
+ document.body.appendChild(el);
+ el.appendChild(div);
+ explorer = new_explorer()
+ });
+
+ afterEach( ()=> {
+ div.remove();
+ div = null;
+ el.remove();
+ el = null;
+ });
+
+ it('TimeExplorer should have options after initialization', ()=> {
+ expect(explorer.options.timelineOptions.height).toEqual(window.innerHeight);
+ })
+
+ it('TimeExplorer.get_tag_col() should return "Tags"', ()=> {
+ const tags = explorer.get_tag_col();
+ expect(tags).toEqual('Tags');
+ })
+
+ it('TimeExplorer.create_timeline() should create a timeline', ()=> {
+ const timeline = explorer.create_timeline(explorer.options);
+ expect(Object.keys(timeline)).toContain('itemsData');
+ })
+
+ it('TimeExplorer.get_sheet_data() should return a promise', ()=> {
+ const sheetData = explorer.get_sheet_data(api_key);
+ expect(Object.keys(sheetData)).toContain("promise");
+ })
+
+ it('TimeExplorer.set_options() should extend options', ()=> {
+ const r = explorer.set_options(["Joe"])
+ expect(r.timelineOptions['0']).toEqual("Joe");
+ })
+
+ it('TimeExplorer.slugify() should return a valid slug', ()=> {
+ const slug = explorer.slugify("Let's make a slug");
+ expect(slug).toEqual("Let_s_make_a_slug");
+ })
+
+ it('TimeExplorer.set_tags() return all tags', ()=> {
+ explorer.items = [{'tags': ["Joe"]}, {'tags': ["Mary", "Liam"]}];
+ const tag_return = explorer.set_tags(explorer)
+ expect(tag_return).toEqual([ 'Joe', 'Liam', 'Mary' ]);
+ })
+
+ it('TimeExplorer.set_groups() return all groups', ()=> {
+ explorer.items = [{'sheet_group': 1}, {'sheet_group': 2}];
+ const group_return = explorer.set_groups(explorer)
+ expect(group_return).toEqual([1,2]);
+ })
+
+ it('TimeExplorer.set_filters() set filters some things checked', ()=> {
+ // groups
+ group = addTestElement('div', {'class':'Groups'});
+ inp1 = addTestElement('input', {'class':'filter-checkbox', 'value':'Event', 'checked':true});
+ inp2 = addTestElement('input', {'class':'filter-checkbox', 'value':'Thing', 'checked':true});
+ group.appendChild(inp1);
+ group.appendChild(inp2);
+
+ //tags
+ tag = addTestElement('div', {'class':'Tags'});
+ inp3 = addTestElement('input', {'class':'filter-checkbox', 'value':'TAG', 'checked':true});
+ inp4 = addTestElement('input', {'class':'filter-checkbox', 'value':'Another', 'checked':true});
+ tag.appendChild(inp3);
+ tag.appendChild(inp4);
+
+ // attach them to the html
+ el.appendChild(group);
+ el.appendChild(tag);
+
+ explorer.set_filters('none', explorer)
+ expect(explorer.filters.tagOptions).toBe('any');
+ expect(explorer.filters.activeGroups).toEqual([ 'Event', 'Thing' ]);
+ expect(explorer.filters.activeTags).toEqual([ 'TAG', 'Another' ]);
+
+ // remove it all
+ inp1.remove();
+ inp2.remove();
+ inp3.remove();
+ inp4.remove();
+ group.remove();
+ tag.remove();
+ })
+
+ it('TimeExplorer.constructDate() constructs a date.', () =>{
+ let constructedDate = explorer.constructDate();
+ expect(constructedDate).toEqual(null);
+ constructedDate = explorer.constructDate(year=6);
+ let date = new Date("0006-01-01T00:00");
+ expect(constructedDate).toEqual(date);
+ constructedDate = explorer.constructDate(year=-6);
+ date = new Date("0000-01-01T00:00");
+ date.setFullYear(-6);
+ expect(constructedDate).toEqual(date);
+ constructedDate = explorer.constructDate(year=1987);
+ date = new Date("1987-01-01T00:00");
+ expect(constructedDate).toEqual(date);
+ })
+
+
+})
+
+
+// Helper function to set up an element to add for testing
+const addTestElement = (elem, attrs) => {
+ item = document.createElement(elem);
+ Object.keys(attrs).forEach( (attribute) => {
+ item.setAttribute(attribute, attrs[attribute]);
+ });
+ return item;
+}