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 = "

"+row['Headline']+"

"; - output += "

"+displayDate+"

" - return output; + return "

" + row['Headline'] + "

"+displayDate+"

"; } /** @@ -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('

HEADLINE

July 6th

'); + }) + + 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; +}