From 5b73279212fe6a5154e447264ddf9727c1d3ac31 Mon Sep 17 00:00:00 2001 From: "Alister N. Mada" Date: Sat, 6 Feb 2016 15:19:27 +0700 Subject: [PATCH 01/11] Remove slide challenge text-align center --- client/less/challenge.less | 1 - 1 file changed, 1 deletion(-) 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; From 25a762f52c880fe4aff0507813fdea282068853a Mon Sep 17 00:00:00 2001 From: Quincy Larson Date: Sat, 6 Feb 2016 22:55:58 -0800 Subject: [PATCH 02/11] add link to euro tshirt campaign --- server/views/resources/shop.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 877c69255be4264d127cd5d51707126db4532c02 Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Sat, 6 Feb 2016 22:58:41 -0800 Subject: [PATCH 03/11] Increase time for DB connect in production --- server/production-start.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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); From abaeda0272ed9fd44684b79bb7d5d358130c7bfa Mon Sep 17 00:00:00 2001 From: Eric Leung Date: Sun, 7 Feb 2016 00:00:39 -0800 Subject: [PATCH 04/11] Clarify characters are each unique in permutation --- .../advanced-bonfires.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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..659b4e19b1 100644 --- a/seed/challenges/01-front-end-development-certification/advanced-bonfires.json +++ b/seed/challenges/01-front-end-development-certification/advanced-bonfires.json @@ -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": [ From a1963e7d68e95c8e367b122efde91a54dae2e42b Mon Sep 17 00:00:00 2001 From: Hallaathrad Date: Sun, 7 Feb 2016 08:12:26 -0500 Subject: [PATCH 05/11] Add shop to the react navbar Closes #6802 --- common/app/components/Nav/links.json | 4 ++++ 1 file changed, 4 insertions(+) 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" From 85a78046d17d74bc4e33cf3da79c7e23336a368a Mon Sep 17 00:00:00 2001 From: Hallaathrad Date: Sun, 7 Feb 2016 13:52:09 -0500 Subject: [PATCH 06/11] fix for navbar overflowing at 4 digits brownies --- client/less/lib/bootstrap/variables.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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; From 0465923e64c3813beb6633044c294f3e5b0b1723 Mon Sep 17 00:00:00 2001 From: "Alister N. Mada" Date: Sun, 7 Feb 2016 16:51:57 +0700 Subject: [PATCH 07/11] Map and chat buttons toggle asides --- client/main.js | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) 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); }); From fe7e7c27be7ef1a08e703bbe2994b1930f23fa28 Mon Sep 17 00:00:00 2001 From: Eric Leung Date: Mon, 8 Feb 2016 21:22:37 -0800 Subject: [PATCH 08/11] Clarify diff two arrays instructions - Clarify you want items that are in one but not both arrays - Add "symmetric difference" terminology for those familiar with it --- .../intermediate-bonfires.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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": [ From 2aebcab27395485fd3f7f661e0dcea4c9277b576 Mon Sep 17 00:00:00 2001 From: Eric Leung Date: Mon, 8 Feb 2016 22:47:06 -0800 Subject: [PATCH 09/11] Clarify symmetric difference definition + examples - Make definition of symmetric difference more precise - Add in set examples in description to facilitate learning of the concept - Put "symmetric difference" in `` tags --- .../advanced-bonfires.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 659b4e19b1..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": [ From f575222ac6738acfd5c80c4caec45b581efc6b51 Mon Sep 17 00:00:00 2001 From: Mrugesh Mohapatra Date: Tue, 9 Feb 2016 23:56:19 +0530 Subject: [PATCH 10/11] Fix text of the challenge with better wording This commit changes the into text of the challenge with better wording, as discussed in the issue thread. Tested locally. --- .../01-front-end-development-certification/bootstrap.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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", From 631c7ea319202c9dd323ebc03e99462db0b19d16 Mon Sep 17 00:00:00 2001 From: JelenaBarinova Date: Tue, 9 Feb 2016 09:10:18 -0800 Subject: [PATCH 11/11] Timestamps are converted to start of the days and filtered to b unique before calculating streaks --- package.json | 1 + server/boot/user.js | 17 ++- server/utils/date-utils.js | 2 +- server/utils/user-stats.js | 60 ++++---- test/server/utils/date-utils-test.js | 7 +- test/server/utils/user-stats-test.js | 208 ++++++++++++++++++++------- 6 files changed, 206 insertions(+), 89 deletions(-) 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/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/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/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(); });