diff --git a/client/less/challenge.less b/client/less/challenge.less index f9c1b566a9..f50db78725 100644 --- a/client/less/challenge.less +++ b/client/less/challenge.less @@ -1,6 +1,5 @@ .challenge-step-description { font-size: 1.5em; - text-align: center; } .challenge-step-counter { font-size: 20px; diff --git a/client/less/lib/bootstrap/variables.less b/client/less/lib/bootstrap/variables.less index 49ecd0c331..66adcfd9d4 100755 --- a/client/less/lib/bootstrap/variables.less +++ b/client/less/lib/bootstrap/variables.less @@ -289,7 +289,7 @@ // Medium screen / desktop //** Deprecated `@screen-md` as of v3.0.1 -@screen-md: 992px; +@screen-md: 1031px; @screen-md-min: @screen-md; //** Deprecated `@screen-desktop` as of v3.0.1 @screen-desktop: @screen-md-min; diff --git a/client/main.js b/client/main.js index 1e6bc8c99c..6a7ffb86cc 100644 --- a/client/main.js +++ b/client/main.js @@ -96,7 +96,7 @@ main = (function(main, global) { }); - $('#nav-chat-btn').on('click', showMainChat); + $('#nav-chat-btn').on('click', toggleMainChat); function showMainChat() { if (!main.chat.isOpen) { @@ -106,10 +106,10 @@ main = (function(main, global) { function collapseMainChat() { $('#chat-embed-main').addClass('is-collapsed'); + document.activeElement.blur(); } - // keyboard shortcuts: open main chat - Mousetrap.bind('g c', function() { + function toggleMainChat() { var isCollapsed = $('#chat-embed-main').hasClass('is-collapsed'); if (isCollapsed) { @@ -117,7 +117,10 @@ main = (function(main, global) { } else { collapseMainChat(); } - }); + } + + // keyboard shortcuts: open main chat + Mousetrap.bind('g c', toggleMainChat); }); return main; @@ -344,7 +347,7 @@ $(document).ready(function() { var mapFilter = $('#map-filter'); var mapShowAll = $('#showAll'); - $('#nav-map-btn').on('click', showMap); + $('#nav-map-btn').on('click', toggleMap); $('.map-aside-action-collapse').on('click', collapseMap); @@ -360,6 +363,17 @@ $(document).ready(function() { function collapseMap() { $('.map-aside').addClass('is-collapsed'); + document.activeElement.blur(); + } + + function toggleMap() { + var isCollapsed = $('.map-aside').hasClass('is-collapsed'); + + if (isCollapsed) { + showMap(); + } else { + collapseMap(); + } } $('#accordion').on('show.bs.collapse', function(e) { @@ -480,13 +494,5 @@ $(document).ready(function() { window.Mousetrap.bind('esc', clearMapFilter); // keyboard shortcuts: open map - window.Mousetrap.bind('g m', function() { - var isCollapsed = $('.map-aside').hasClass('is-collapsed'); - - if (isCollapsed) { - showMap(); - } else { - collapseMap(); - } - }); + window.Mousetrap.bind('g m', toggleMap); }); diff --git a/common/app/components/Nav/links.json b/common/app/components/Nav/links.json index 41f00e8353..63589fe55c 100644 --- a/common/app/components/Nav/links.json +++ b/common/app/components/Nav/links.json @@ -17,6 +17,10 @@ "content": "Jobs", "link": "/jobs", "react": true +},{ + "content": "Shop", + "link": "/shop", + "target": "_blank" },{ "content": "About", "link": "/about" diff --git a/package.json b/package.json index 61822de8e1..f499e3becc 100644 --- a/package.json +++ b/package.json @@ -148,6 +148,7 @@ "loopback-component-explorer": "^2.1.1", "loopback-testing": "^1.1.0", "mocha": "^2.3.3", + "sinon": "^1.17.3", "tap-spec": "^4.1.1", "tape": "^4.2.2" } diff --git a/seed/challenges/01-front-end-development-certification/advanced-bonfires.json b/seed/challenges/01-front-end-development-certification/advanced-bonfires.json index 7a098352be..d79b88d1c5 100644 --- a/seed/challenges/01-front-end-development-certification/advanced-bonfires.json +++ b/seed/challenges/01-front-end-development-certification/advanced-bonfires.json @@ -81,8 +81,8 @@ "id": "a3f503de51cf954ede28891d", "title": "Symmetric Difference", "description": [ - "Create a function that takes two or more arrays and returns an array of the symmetric difference of the provided arrays.", - "The mathematical term symmetric difference refers to the elements in two sets that are in either the first or second set, but not in both.", + "Create a function that takes two or more arrays and returns an array of the symmetric difference ( or ) of the provided arrays.", + "Given two sets (for example set A = {1, 2, 3} and set B = {2, 3, 4}), the mathematical term \"symmetric difference\" of two sets is the set of elements which are in either of the two sets, but not in both (A △ B = C = {1, 4}). For every additional symmetric difference you take (say on a set D = {2, 3}), you should get the set with elements which are in either of the two the sets but not both (C △ D = {1, 4} △ {2, 3} = {1, 2, 3, 4}).", "Remember to use Read-Search-Ask if you get stuck. Try to pair program. Write your own code." ], "challengeSeed": [ @@ -225,8 +225,8 @@ "id": "a7bf700cd123b9a54eef01d5", "title": "No repeats please", "description": [ - "Return the number of total permutations of the provided string that don't have repeated consecutive letters.", - "For example, 'aab' should return 2 because it has 6 total permutations, but only 2 of them don't have the same letter (in this case 'a') repeating.", + "Return the number of total permutations of the provided string that don't have repeated consecutive letters. Assume that duplicate characters are each unique.", + "For example, aab should return 2 because it has 6 total permutations (aab, aab, aba, aba, baa, baa), but only 2 of them (aba and aba) don't have the same letter (in this case a) repeating.", "Remember to use Read-Search-Ask if you get stuck. Try to pair program. Write your own code." ], "challengeSeed": [ diff --git a/seed/challenges/01-front-end-development-certification/bootstrap.json b/seed/challenges/01-front-end-development-certification/bootstrap.json index 098ebc5885..dd5fcf5942 100644 --- a/seed/challenges/01-front-end-development-certification/bootstrap.json +++ b/seed/challenges/01-front-end-development-certification/bootstrap.json @@ -950,7 +950,7 @@ "id": "bad87fee1348bd9aede08845", "title": "Create a Custom Heading", "description": [ - "We will make a simple heading for our Cat Photo App by putting them in the same row.", + "We will make a simple heading for our Cat Photo App by putting the title and relaxing cat image in the same row.", "Remember, Bootstrap uses a responsive grid system, which makes it easy to put elements into rows and specify each element's relative width. Most of Bootstrap's classes can be applied to a div element.", "Here's a diagram of how Bootstrap's 12-column grid layout works:", "\"an", diff --git a/seed/challenges/01-front-end-development-certification/intermediate-bonfires.json b/seed/challenges/01-front-end-development-certification/intermediate-bonfires.json index dfbe2a5233..d62675c923 100644 --- a/seed/challenges/01-front-end-development-certification/intermediate-bonfires.json +++ b/seed/challenges/01-front-end-development-certification/intermediate-bonfires.json @@ -48,7 +48,7 @@ "id": "a5de63ebea8dbee56860f4f2", "title": "Diff Two Arrays", "description": [ - "Compare two arrays and return a new array with any items only found in one of the original arrays.", + "Compare two arrays and return a new array with any items only found in one of the two given arrays, but not both. In other words, return the symmetric difference of the two arrays.", "Remember to use Read-Search-Ask if you get stuck. Try to pair program. Write your own code." ], "challengeSeed": [ diff --git a/server/boot/user.js b/server/boot/user.js index 5bf0fafaec..1b1eddc3ea 100644 --- a/server/boot/user.js +++ b/server/boot/user.js @@ -14,7 +14,11 @@ import certTypes from '../utils/certTypes.json'; import { ifNoUser401, ifNoUserRedirectTo } from '../utils/middleware'; import { observeQuery } from '../utils/rx'; -import { calcCurrentStreak, calcLongestStreak } from '../utils/user-stats'; +import { + prepUniqueDays, + calcCurrentStreak, + calcLongestStreak +} from '../utils/user-stats'; const debug = debugFactory('freecc:boot:user'); const sendNonUserToMap = ifNoUserRedirectTo('/map'); @@ -192,17 +196,18 @@ module.exports = function(app) { const timezone = req.user && req.user.timezone ? req.user.timezone : 'UTC'; - var cals = profileUser + const timestamps = profileUser .progressTimestamps .map(objOrNum => { return typeof objOrNum === 'number' ? objOrNum : objOrNum.timestamp; - }) - .sort(); + }); - profileUser.currentStreak = calcCurrentStreak(cals, timezone); - profileUser.longestStreak = calcLongestStreak(cals, timezone); + const uniqueDays = prepUniqueDays(timestamps, timezone); + + profileUser.currentStreak = calcCurrentStreak(uniqueDays, timezone); + profileUser.longestStreak = calcLongestStreak(uniqueDays, timezone); const data = profileUser .progressTimestamps diff --git a/server/production-start.js b/server/production-start.js index ad01710561..53266e0747 100644 --- a/server/production-start.js +++ b/server/production-start.js @@ -26,6 +26,6 @@ var timeoutHandler = setTimeout(function() { // purposely shutdown server // pm2 should restart this in production throw new Error(message); -}, 5000); +}, 15000); app.dataSources.db.on('connected', onConnect); diff --git a/server/utils/date-utils.js b/server/utils/date-utils.js index ff62b7a619..a22de80015 100644 --- a/server/utils/date-utils.js +++ b/server/utils/date-utils.js @@ -3,7 +3,7 @@ import moment from 'moment-timezone'; // day count between two epochs (inclusive) export function dayCount([head, tail], timezone = 'UTC') { return Math.ceil( - moment(moment(head).tz(timezone).endOf('day')).tz(timezone).diff( + moment(moment(head).tz(timezone).endOf('day')).diff( moment(tail).tz(timezone).startOf('day'), 'days', true) diff --git a/server/utils/user-stats.js b/server/utils/user-stats.js index e7ac94dc27..228e81ee79 100644 --- a/server/utils/user-stats.js +++ b/server/utils/user-stats.js @@ -1,46 +1,54 @@ +import _ from 'lodash'; import moment from 'moment-timezone'; import { dayCount } from '../utils/date-utils'; const daysBetween = 1.5; -export function calcCurrentStreak(cals, timezone = 'UTC') { - const revCals = cals.slice().reverse(); +export function prepUniqueDays(cals, tz = 'UTC') { - if (dayCount([moment().tz(timezone), revCals[0]], timezone) > daysBetween) { - return 0; - } - - let streakBroken = false; - const lastDayInStreak = revCals - .reduce((current, cal, index) => { - const before = revCals[index === 0 ? 0 : index - 1]; - if ( - !streakBroken && - moment(before).tz(timezone).diff(cal, 'days', true) < daysBetween - ) { - return index; - } - streakBroken = true; - return current; - }, 0); - - const lastTimestamp = revCals[lastDayInStreak]; - return dayCount([moment().tz(timezone), lastTimestamp], timezone); + return _(cals) + .map(ts => moment(ts).tz(tz).startOf('day').valueOf()) + .uniq() + .sort() + .value(); } -export function calcLongestStreak(cals, timezone = 'UTC') { +export function calcCurrentStreak(cals, tz = 'UTC') { + + let prev = _.last(cals); + if (moment().tz(tz).startOf('day').diff(prev, 'days') > daysBetween) { + return 0; + } + let currentStreak = 0; + let streakContinues = true; + _.forEachRight(cals, cur => { + if (moment(prev).diff(cur, 'days') < daysBetween) { + prev = cur; + currentStreak++; + } else { + // current streak found + streakContinues = false; + } + return streakContinues; + }); + + return currentStreak; +} + +export function calcLongestStreak(cals, tz = 'UTC') { + let tail = cals[0]; const longest = cals.reduce((longest, head, index) => { const last = cals[index === 0 ? 0 : index - 1]; // is streak broken - if (moment(head).tz(timezone).diff(last, 'days', true) > daysBetween) { + if (moment(head).tz(tz).diff(moment(last).tz(tz), 'days') > daysBetween) { tail = head; } - if (dayCount(longest, timezone) < dayCount([head, tail], timezone)) { + if (dayCount(longest, tz) < dayCount([head, tail], tz)) { return [head, tail]; } return longest; }, [cals[0], cals[0]]); - return dayCount(longest, timezone); + return dayCount(longest, tz); } diff --git a/server/views/resources/shop.jade b/server/views/resources/shop.jade index cfe2d770af..7245fe1983 100644 --- a/server/views/resources/shop.jade +++ b/server/views/resources/shop.jade @@ -12,5 +12,5 @@ block content h4 100% Cotton. Unisex. $20 shipped. h4 Only available until February 15. h5 Are you outside the US? Ships from the EU   - a(href='#') here + a(href='https://teespring.com/free-code-camp-t-shirt-eu-shop' target='_blank') here | . \ No newline at end of file diff --git a/test/server/utils/date-utils-test.js b/test/server/utils/date-utils-test.js index 0a2366a150..ad3ec46fea 100644 --- a/test/server/utils/date-utils-test.js +++ b/test/server/utils/date-utils-test.js @@ -3,6 +3,7 @@ import moment from 'moment-timezone'; import { dayCount } from '../../../server/utils/date-utils'; let test = require('tape'); +const PST = 'America/Los_Angeles'; test('Day count between two epochs (inclusive) calculation', function (t) { t.plan(7); @@ -25,18 +26,18 @@ test('Day count between two epochs (inclusive) calculation', function (t) { t.equal(dayCount([ moment.utc("8/4/2015 1:00", "M/D/YYYY H:mm").valueOf(), moment.utc("8/3/2015 23:00", "M/D/YYYY H:mm").valueOf() - ]), 2, "should return 2 days when the diff is less than 24h but days are different in default timezone UTC"); + ]), 2, "should return 2 days when the diff is less than 24h but days are different in UTC"); t.equal(dayCount([ moment.utc("8/4/2015 1:00", "M/D/YYYY H:mm").valueOf(), moment.utc("8/3/2015 23:00", "M/D/YYYY H:mm").valueOf() - ], 'America/Los_Angeles'), 1, "should return 1 day when the diff is less than 24h and days are different in UTC, but given 'America/Los_Angeles' timezone"); + ], PST), 1, "should return 1 day when the diff is less than 24h and days are different in UTC, but given PST"); t.equal(dayCount([ moment.utc("10/27/2015 1:00", "M/D/YYYY H:mm").valueOf(), moment.utc("5/12/1982 1:00", "M/D/YYYY H:mm").valueOf() ]), 12222, "should return correct count when there is very big period"); - + t.equal(dayCount([ moment.utc("8/4/2015 2:00", "M/D/YYYY H:mm").valueOf(), moment.utc("8/3/2015 2:00", "M/D/YYYY H:mm").valueOf() diff --git a/test/server/utils/user-stats-test.js b/test/server/utils/user-stats-test.js index 5dcba8d4c9..8c0cafd1fe 100644 --- a/test/server/utils/user-stats-test.js +++ b/test/server/utils/user-stats-test.js @@ -1,31 +1,78 @@ import moment from 'moment-timezone'; +import sinon from 'sinon'; -import { calcCurrentStreak, calcLongestStreak } from '../../../server/utils/user-stats'; +import { + prepUniqueDays, + calcCurrentStreak, + calcLongestStreak +} from '../../../server/utils/user-stats'; let test = require('tape'); +let clock = sinon.useFakeTimers(1454526000000); // setting now to 2016-02-03T11:00:00 (PST) +const PST = 'America/Los_Angeles'; + +test('Prepare calendar items', function (t) { + + t.plan(5); + + t.deepEqual(prepUniqueDays([ + moment.utc("8/3/2015 2:00", "M/D/YYYY H:mm").valueOf(), + moment.utc("8/3/2015 14:00", "M/D/YYYY H:mm").valueOf(), + moment.utc("8/3/2015 20:00", "M/D/YYYY H:mm").valueOf() + ]), [1438560000000], "should return correct epoch when all entries fall into one day in UTC"); + + t.deepEqual(prepUniqueDays([ + moment.utc("8/3/2015 2:00", "M/D/YYYY H:mm").valueOf(), + moment.utc("8/3/2015 2:00", "M/D/YYYY H:mm").valueOf() + ]), [1438560000000], "should return correct epoch when given two identical dates"); + + + t.deepEqual(prepUniqueDays([ + moment.utc("8/3/2015 2:00", "M/D/YYYY H:mm").valueOf(), // 8/2/2015 in America/Los_Angeles + moment.utc("8/3/2015 14:00", "M/D/YYYY H:mm").valueOf(), + moment.utc("8/3/2015 20:00", "M/D/YYYY H:mm").valueOf() + ], PST), [1438498800000, 1438585200000], "should return 2 epochs when dates fall into two days in PST"); + + t.deepEqual(prepUniqueDays([ + moment.utc("8/1/2015 2:00", "M/D/YYYY H:mm").valueOf(), + moment.utc("3/3/2015 14:00", "M/D/YYYY H:mm").valueOf(), + moment.utc("9/30/2014 20:00", "M/D/YYYY H:mm").valueOf() + ]), [1412035200000, 1425340800000, 1438387200000], "should return 3 epochs when dates fall into three days"); + + t.deepEqual(prepUniqueDays([ + 1438387200000, 1425340800000, 1412035200000 + ]), [1412035200000, 1425340800000, 1438387200000], "should return same but sorted array if all input dates are start of day"); + +}); test('Current streak calculation', function (t) { - t.plan(9); - t.equal(calcCurrentStreak([ + t.plan(11); + + t.equal(calcCurrentStreak( + prepUniqueDays([ moment.utc(moment.utc().subtract(1, 'hours')).valueOf() - ]), 1, "should return 1 day when today one challenge was completed"); + ])), 1, "should return 1 day when today one challenge was completed"); - t.equal(calcCurrentStreak([ + t.equal(calcCurrentStreak( + prepUniqueDays([ moment.utc(moment.utc().subtract(1, 'hours')).valueOf(), moment.utc(moment.utc().subtract(1, 'hours')).valueOf() - ]), 1, "should return 1 day when today more than one challenge was completed"); + ])), 1, "should return 1 day when today more than one challenge was completed"); - t.equal(calcCurrentStreak([ + t.equal(calcCurrentStreak( + prepUniqueDays([ moment.utc("9/11/2015 4:00", "M/D/YYYY H:mm").valueOf() - ]), 0, "should return 0 day when today 0 challenges were completed"); + ])), 0, "should return 0 day when today 0 challenges were completed"); - t.equal(calcCurrentStreak([ + t.equal(calcCurrentStreak( + prepUniqueDays([ moment.utc(moment.utc().subtract(1, 'days')).valueOf(), moment.utc(moment.utc().subtract(1, 'hours')).valueOf() - ]), 2, "should return 2 days when today and yesterday challenges were completed"); + ])), 2, "should return 2 days when today and yesterday challenges were completed"); - t.equal(calcCurrentStreak([ + t.equal(calcCurrentStreak( + prepUniqueDays([ moment.utc("8/3/2015 2:00", "M/D/YYYY H:mm").valueOf(), moment.utc("9/11/2015 4:00", "M/D/YYYY H:mm").valueOf(), moment.utc("9/12/2015 1:00", "M/D/YYYY H:mm").valueOf(), @@ -37,54 +84,81 @@ test('Current streak calculation', function (t) { moment.utc(moment.utc().subtract(2, 'days')).valueOf(), moment.utc(moment.utc().subtract(1, 'days')).valueOf(), moment.utc(moment.utc().subtract(1, 'hours')).valueOf() - ]), 3, "should return 3 when today and for two days before user was activity"); + ])), 3, "should return 3 when today and for two days before user was activity"); - t.equal(calcCurrentStreak([ - moment.utc(moment.utc().subtract(37, 'hours')).valueOf(), + t.equal(calcCurrentStreak( + prepUniqueDays([ + moment.utc(moment.utc().subtract(47, 'hours')).valueOf(), + moment.utc(moment.utc().subtract(11, 'hours')).valueOf() + ])), 1, "should return 1 when there is 1.5 days long break and dates fall into two days separated by third"); + + t.equal(calcCurrentStreak( + prepUniqueDays([ + moment.utc(moment.utc().subtract(40, 'hours')).valueOf(), moment.utc(moment.utc().subtract(1, 'hours')).valueOf() - ]), 1, "should return 1 when between todays challenge completion and yesterdays there is a 1.5 day (36 hours) long break"); + ])), 2, "should return 2 when the break is more than 1.5 days but dates fall into two consecutive days"); - t.ok(calcCurrentStreak([ - moment.utc(moment.utc().subtract(35, 'hours')).valueOf(), - moment.utc(moment.utc().subtract(1, 'hours')).valueOf() - ]) >= 2, "should return not less than 2 days when between todays challenge completion and yesterdays there is less than 1.5 day (36 hours) long break"); - - t.equal(calcCurrentStreak([ + t.equal(calcCurrentStreak( + prepUniqueDays([ moment.utc(moment.utc().subtract(1, 'hours')).valueOf(), moment.utc(moment.utc().subtract(1, 'hours')).valueOf() - ], undefined), 1, "should return correct count in default timezone UTC given 'undefined' timezone"); - - t.equal(calcCurrentStreak([ + ]), undefined), 1, "should return correct count in default timezone UTC given 'undefined' timezone"); + + t.equal(calcCurrentStreak( + prepUniqueDays([ moment.utc(moment.utc().subtract(1, 'days')).valueOf(), moment.utc(moment.utc().subtract(1, 'hours')).valueOf() - ], 'America/Los_Angeles'), 2, "should return 2 days when today and yesterday challenges were completed given 'America/Los_Angeles' timezone"); + ], PST), PST), 2, "should return 2 days when today and yesterday challenges were completed given PST"); + + t.equal(calcCurrentStreak( + prepUniqueDays([ + 1453174506164, 1453175436725, 1453252466853, 1453294968225, 1453383782844, + 1453431903117, 1453471373080, 1453594733026, 1453645014058, 1453746762747, + 1453747659197, 1453748029416, 1453818029213, 1453951796007, 1453988570615, + 1454069704441, 1454203673979, 1454294055498, 1454333545125, 1454415163903, + 1454519128123, moment.tz(PST).valueOf() + ], PST), PST), 17, "should return 17 when there is no break in given timezone (but would be the break if in UTC)"); + + t.equal(calcCurrentStreak( + prepUniqueDays([ + 1453174506164, 1453175436725, 1453252466853, 1453294968225, 1453383782844, + 1453431903117, 1453471373080, 1453594733026, 1453645014058, 1453746762747, + 1453747659197, 1453748029416, 1453818029213, 1453951796007, 1453988570615, + 1454069704441, 1454203673979, 1454294055498, 1454333545125, 1454415163903, + 1454519128123, moment.utc().valueOf() + ])), 4, "should return 4 when there is a break in UTC (but would be no break in PST)"); }); test('Longest streak calculation', function (t) { - t.plan(12); + t.plan(14); - t.equal(calcLongestStreak([ + t.equal(calcLongestStreak( + prepUniqueDays([ moment.utc("9/12/2015 4:00", "M/D/YYYY H:mm").valueOf() - ]), 1, "should return 1 when there is the only one one-day-long streak available"); + ])), 1, "should return 1 when there is the only one one-day-long streak available"); - t.equal(calcLongestStreak([ + t.equal(calcLongestStreak( + prepUniqueDays([ moment.utc("9/11/2015 4:00", "M/D/YYYY H:mm").valueOf(), moment.utc("9/12/2015 2:00", "M/D/YYYY H:mm").valueOf(), moment.utc("9/13/2015 3:00", "M/D/YYYY H:mm").valueOf(), moment.utc("9/14/2015 1:00", "M/D/YYYY H:mm").valueOf() - ]), 4, "should return 4 when there is the only one more-than-one-days-long streak available"); + ])), 4, "should return 4 when there is the only one more-than-one-days-long streak available"); - t.equal(calcLongestStreak([ + t.equal(calcLongestStreak( + prepUniqueDays([ moment.utc(moment.utc().subtract(1, 'hours')).valueOf() - ]), 1, "should return 1 when there is only one one-day-long streak and it is today"); + ])), 1, "should return 1 when there is only one one-day-long streak and it is today"); - t.equal(calcLongestStreak([ + t.equal(calcLongestStreak( + prepUniqueDays([ moment.utc(moment.utc().subtract(1, 'days')).valueOf(), moment.utc(moment.utc().subtract(1, 'hours')).valueOf() - ]), 2, "should return 2 when yesterday and today makes longest streak"); + ])), 2, "should return 2 when yesterday and today makes longest streak"); - t.equal(calcLongestStreak([ + t.equal(calcLongestStreak( + prepUniqueDays([ moment.utc("8/3/2015 2:00", "M/D/YYYY H:mm").valueOf(), moment.utc("9/11/2015 4:00", "M/D/YYYY H:mm").valueOf(), moment.utc("9/12/2015 2:00", "M/D/YYYY H:mm").valueOf(), @@ -93,19 +167,21 @@ test('Longest streak calculation', function (t) { moment.utc("10/6/2015 4:00", "M/D/YYYY H:mm").valueOf(), moment.utc("10/7/2015 5:00", "M/D/YYYY H:mm").valueOf(), moment.utc("11/3/2015 2:00", "M/D/YYYY H:mm").valueOf() - ]), 4, "should return 4 when there is a month long break"); + ])), 4, "should return 4 when there is a month long break"); - t.equal(calcLongestStreak([ + t.equal(calcLongestStreak( + prepUniqueDays([ moment.utc("8/3/2015 2:00", "M/D/YYYY H:mm").valueOf(), moment.utc("9/11/2015 4:00", "M/D/YYYY H:mm").valueOf(), - moment.utc("9/12/2015 1:00", "M/D/YYYY H:mm").valueOf(), - moment.utc(moment.utc("9/12/2015 1:00", "M/D/YYYY H:mm").add(37, 'hours')).valueOf(), + moment.utc("9/12/2015 15:30", "M/D/YYYY H:mm").valueOf(), + moment.utc(moment.utc("9/12/2015 15:30", "M/D/YYYY H:mm").add(37, 'hours')).valueOf(), moment.utc("9/14/2015 22:00", "M/D/YYYY H:mm").valueOf(), moment.utc("9/15/2015 4:00", "M/D/YYYY H:mm").valueOf(), moment.utc("10/3/2015 2:00", "M/D/YYYY H:mm").valueOf() - ]), 3, "should return 3 when there is a more than 1.5 days long break of (36 hours)"); + ])), 2, "should return 2 when there is a more than 1.5 days long break of (36 hours)"); - t.equal(calcLongestStreak([ + t.equal(calcLongestStreak( + prepUniqueDays([ moment.utc("8/3/2015 2:00", "M/D/YYYY H:mm").valueOf(), moment.utc("9/11/2015 4:00", "M/D/YYYY H:mm").valueOf(), moment.utc("9/12/2015 1:00", "M/D/YYYY H:mm").valueOf(), @@ -117,9 +193,10 @@ test('Longest streak calculation', function (t) { moment.utc(moment.utc().subtract(2, 'days')).valueOf(), moment.utc(moment.utc().subtract(1, 'days')).valueOf(), moment.utc().valueOf() - ]), 4, "should return 4 when the longest streak consist of several same day timestamps"); + ])), 4, "should return 4 when the longest streak consist of several same day timestamps"); - t.equal(calcLongestStreak([ + t.equal(calcLongestStreak( + prepUniqueDays([ moment.utc("8/3/2015 2:00", "M/D/YYYY H:mm").valueOf(), moment.utc("9/11/2015 4:00", "M/D/YYYY H:mm").valueOf(), moment.utc("9/12/2015 1:00", "M/D/YYYY H:mm").valueOf(), @@ -129,32 +206,57 @@ test('Longest streak calculation', function (t) { moment.utc("10/12/2015 1:00", "M/D/YYYY H:mm").valueOf(), moment.utc("10/13/2015 4:00", "M/D/YYYY H:mm").valueOf(), moment.utc("10/14/2015 5:00", "M/D/YYYY H:mm").valueOf() - ]), 4, "should return 4 when there are several longest streaks (same length)"); + ])), 4, "should return 4 when there are several longest streaks (same length)"); let cals = []; const n = 100; for (var i = 0; i < n; i++) { cals.push(moment.utc(moment.utc().subtract(i, 'days')).valueOf()); } - cals.sort(); - t.equal(calcLongestStreak(cals), n, "should return correct longest streak when there is a very long period"); + t.equal(calcLongestStreak(prepUniqueDays(cals)), n, "should return correct longest streak when there is a very long period"); - t.equal(calcLongestStreak([ + t.equal(calcLongestStreak( + prepUniqueDays([ moment.utc(moment.utc().subtract(1, 'days')).valueOf(), moment.utc(moment.utc().subtract(1, 'hours')).valueOf() - ], undefined), 2, "should return correct longest streak in default timezone UTC given 'undefined' timezone"); + ]), undefined), 2, "should return correct longest streak in default timezone UTC given 'undefined' timezone"); - t.equal(calcLongestStreak([ + t.equal(calcLongestStreak( + prepUniqueDays([ moment.utc("9/11/2015 4:00", "M/D/YYYY H:mm").valueOf(), moment.utc("9/12/2015 2:00", "M/D/YYYY H:mm").valueOf(), moment.utc("9/13/2015 3:00", "M/D/YYYY H:mm").valueOf(), moment.utc("9/14/2015 1:00", "M/D/YYYY H:mm").valueOf() - ], 'America/Los_Angeles'), 4, "should return 4 when there is the only one more-than-one-days-long streak available given 'America/Los_Angeles' timezone"); + ]), PST), 4, "should return 4 when there is the only one more-than-one-days-long streak available given PST"); - t.equal(calcLongestStreak([ + t.equal(calcLongestStreak( + prepUniqueDays([ moment.utc("9/11/2015 23:00", "M/D/YYYY H:mm").valueOf(), moment.utc("9/12/2015 2:00", "M/D/YYYY H:mm").valueOf(), moment.utc("9/13/2015 2:00", "M/D/YYYY H:mm").valueOf(), - moment.utc("9/14/2015 1:00", "M/D/YYYY H:mm").valueOf() - ], 'America/Los_Angeles'), 3, "should return 3 when longest streak is 3 in given 'America/Los_Angeles' timezone (but would be different in default timezone UTC)"); + moment.utc("9/14/2015 6:00", "M/D/YYYY H:mm").valueOf() + ], PST), PST), 3, "should return 3 when longest streak is 3 in PST (but would be different in default timezone UTC)"); + + t.equal(calcLongestStreak( + prepUniqueDays([ + 1453174506164, 1453175436725, 1453252466853, 1453294968225, 1453383782844, + 1453431903117, 1453471373080, 1453594733026, 1453645014058, 1453746762747, + 1453747659197, 1453748029416, 1453818029213, 1453951796007, 1453988570615, + 1454069704441, 1454203673979, 1454294055498, 1454333545125, 1454415163903, + 1454519128123, moment.tz(PST).valueOf() + ], PST), PST), 17, "should return 17 when there is no break in PST (but would be break in UTC) and it is current"); + + t.equal(calcLongestStreak( + prepUniqueDays([ + 1453174506164, 1453175436725, 1453252466853, 1453294968225, 1453383782844, + 1453431903117, 1453471373080, 1453594733026, 1453645014058, 1453746762747, + 1453747659197, 1453748029416, 1453818029213, 1453951796007, 1453988570615, + 1454069704441, 1454203673979, 1454294055498, 1454333545125, 1454415163903, + 1454519128123, moment.utc().valueOf() + ])), 4, "should return 4 when there is a break in UTC (but no break in PST)"); + +}); + +test.onFinish(() => { + clock.restore(); });