feat: paginate heatmap + calculate streaks on client (#38318)
Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
This commit is contained in:
@ -1,3 +0,0 @@
|
|||||||
module.exports = async () => {
|
|
||||||
process.env.TZ = 'UTC';
|
|
||||||
};
|
|
@ -12,7 +12,6 @@ module.exports = {
|
|||||||
globals: {
|
globals: {
|
||||||
__PATH_PREFIX__: ''
|
__PATH_PREFIX__: ''
|
||||||
},
|
},
|
||||||
globalSetup: './jest-timezone-setup.js',
|
|
||||||
verbose: true,
|
verbose: true,
|
||||||
transform: {
|
transform: {
|
||||||
'^.+\\.js$': '<rootDir>/jest.transform.js'
|
'^.+\\.js$': '<rootDir>/jest.transform.js'
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
/* global expect */
|
|
||||||
describe('Timezones', () => {
|
|
||||||
it('should always be UTC', () => {
|
|
||||||
expect(new Date().getTimezoneOffset()).toBe(0);
|
|
||||||
});
|
|
||||||
});
|
|
74
client/package-lock.json
generated
74
client/package-lock.json
generated
@ -2918,6 +2918,40 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@freecodecamp/react-calendar-heatmap": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@freecodecamp/react-calendar-heatmap/-/react-calendar-heatmap-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-+bqI/VEVHiuvD+Ca17e9os4eQ8MG5xv/tXjyWYjK5zfo81FiCPF10P3LbAkHnttRatxxeudTDCmJjCR2kSM0xQ==",
|
||||||
|
"requires": {
|
||||||
|
"memoize-one": "^5.0.0",
|
||||||
|
"prop-types": "^15.6.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"loose-envify": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||||
|
"requires": {
|
||||||
|
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"prop-types": {
|
||||||
|
"version": "15.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
|
||||||
|
"integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
|
||||||
|
"requires": {
|
||||||
|
"loose-envify": "^1.4.0",
|
||||||
|
"object-assign": "^4.1.1",
|
||||||
|
"react-is": "^16.8.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"react-is": {
|
||||||
|
"version": "16.13.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"@hapi/address": {
|
"@hapi/address": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.2.tgz",
|
||||||
@ -19748,9 +19782,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"memoize-one": {
|
"memoize-one": {
|
||||||
"version": "5.0.5",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.1.1.tgz",
|
||||||
"integrity": "sha512-ey6EpYv0tEaIbM/nTDOpHciXUvd+ackQrJgEzBwemhZZIWZjcyodqEcrmqDy2BKRTM3a65kKBV4WtLXJDt26SQ=="
|
"integrity": "sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA=="
|
||||||
},
|
},
|
||||||
"memory-fs": {
|
"memory-fs": {
|
||||||
"version": "0.4.1",
|
"version": "0.4.1",
|
||||||
@ -22348,40 +22382,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"react-calendar-heatmap": {
|
|
||||||
"version": "1.8.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-calendar-heatmap/-/react-calendar-heatmap-1.8.1.tgz",
|
|
||||||
"integrity": "sha512-4Hbq/pDMJoCPzZnyIWFfHgokLlLXzKyGsDcMgNhYpi7zcKHcvsK9soLEPvhW2dBBqgDrQOSp/uG4wtifaDg4eQ==",
|
|
||||||
"requires": {
|
|
||||||
"memoize-one": "^5.0.0",
|
|
||||||
"prop-types": "^15.6.2"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"loose-envify": {
|
|
||||||
"version": "1.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
|
||||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
|
||||||
"requires": {
|
|
||||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"prop-types": {
|
|
||||||
"version": "15.7.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
|
|
||||||
"integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
|
|
||||||
"requires": {
|
|
||||||
"loose-envify": "^1.4.0",
|
|
||||||
"object-assign": "^4.1.1",
|
|
||||||
"react-is": "^16.8.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"react-is": {
|
|
||||||
"version": "16.9.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.9.0.tgz",
|
|
||||||
"integrity": "sha512-tJBzzzIgnnRfEm046qRcURvwQnZVXmuCbscxUO5RWrGTXpon2d4c8mI0D8WE6ydVIm29JiLB6+RslkIvym9Rjw=="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"react-dev-utils": {
|
"react-dev-utils": {
|
||||||
"version": "4.2.3",
|
"version": "4.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-4.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-4.2.3.tgz",
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
"@fortawesome/react-fontawesome": "^0.1.4",
|
"@fortawesome/react-fontawesome": "^0.1.4",
|
||||||
"@freecodecamp/loop-protect": "^2.2.1",
|
"@freecodecamp/loop-protect": "^2.2.1",
|
||||||
"@freecodecamp/react-bootstrap": "^0.32.3",
|
"@freecodecamp/react-bootstrap": "^0.32.3",
|
||||||
|
"@freecodecamp/react-calendar-heatmap": "^1.0.0",
|
||||||
"@reach/router": "^1.2.1",
|
"@reach/router": "^1.2.1",
|
||||||
"algoliasearch": "^3.35.1",
|
"algoliasearch": "^3.35.1",
|
||||||
"axios": "^0.19.0",
|
"axios": "^0.19.0",
|
||||||
@ -48,7 +49,6 @@
|
|||||||
"prismjs": "^1.17.1",
|
"prismjs": "^1.17.1",
|
||||||
"query-string": "^6.8.3",
|
"query-string": "^6.8.3",
|
||||||
"react": "^16.10.2",
|
"react": "^16.10.2",
|
||||||
"react-calendar-heatmap": "^1.8.1",
|
|
||||||
"react-dom": "^16.10.2",
|
"react-dom": "^16.10.2",
|
||||||
"react-final-form": "^6.3.0",
|
"react-final-form": "^6.3.0",
|
||||||
"react-ga": "^2.7.0",
|
"react-ga": "^2.7.0",
|
||||||
|
@ -29,10 +29,6 @@ const propTypes = {
|
|||||||
showTimeLine: PropTypes.bool
|
showTimeLine: PropTypes.bool
|
||||||
}),
|
}),
|
||||||
calendar: PropTypes.object,
|
calendar: PropTypes.object,
|
||||||
streak: PropTypes.shape({
|
|
||||||
current: PropTypes.number,
|
|
||||||
longest: PropTypes.number
|
|
||||||
}),
|
|
||||||
completedChallenges: PropTypes.array,
|
completedChallenges: PropTypes.array,
|
||||||
portfolio: PropTypes.array,
|
portfolio: PropTypes.array,
|
||||||
about: PropTypes.string,
|
about: PropTypes.string,
|
||||||
@ -108,7 +104,6 @@ function renderProfile(user) {
|
|||||||
},
|
},
|
||||||
calendar,
|
calendar,
|
||||||
completedChallenges,
|
completedChallenges,
|
||||||
streak,
|
|
||||||
githubProfile,
|
githubProfile,
|
||||||
isLinkedIn,
|
isLinkedIn,
|
||||||
isGithub,
|
isGithub,
|
||||||
@ -150,7 +145,7 @@ function renderProfile(user) {
|
|||||||
website={website}
|
website={website}
|
||||||
yearsTopContributor={yearsTopContributor}
|
yearsTopContributor={yearsTopContributor}
|
||||||
/>
|
/>
|
||||||
{showHeatMap ? <HeatMap calendar={calendar} streak={streak} /> : null}
|
{showHeatMap ? <HeatMap calendar={calendar} /> : null}
|
||||||
{showCerts ? <Certifications username={username} /> : null}
|
{showCerts ? <Certifications username={username} /> : null}
|
||||||
{showPortfolio ? <Portfolio portfolio={portfolio} /> : null}
|
{showPortfolio ? <Portfolio portfolio={portfolio} /> : null}
|
||||||
{showTimeLine ? (
|
{showTimeLine ? (
|
||||||
|
@ -110,11 +110,6 @@ function Camper({
|
|||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{about && <p className='bio text-center'>{about}</p>}
|
{about && <p className='bio text-center'>{about}</p>}
|
||||||
{typeof points === 'number' ? (
|
|
||||||
<p className='text-center points'>
|
|
||||||
{`${points} ${pluralise('point', points !== 1)}`}
|
|
||||||
</p>
|
|
||||||
) : null}
|
|
||||||
{yearsTopContributor.filter(Boolean).length > 0 && (
|
{yearsTopContributor.filter(Boolean).length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<br />
|
<br />
|
||||||
@ -125,6 +120,11 @@ function Camper({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<br />
|
<br />
|
||||||
|
{typeof points === 'number' ? (
|
||||||
|
<p className='text-center points'>
|
||||||
|
{`${points} ${pluralise('total point', points !== 1)}`}
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import CalendarHeatMap from 'react-calendar-heatmap';
|
import CalendarHeatMap from '@freecodecamp/react-calendar-heatmap';
|
||||||
|
import { Row } from '@freecodecamp/react-bootstrap';
|
||||||
import ReactTooltip from 'react-tooltip';
|
import ReactTooltip from 'react-tooltip';
|
||||||
import addDays from 'date-fns/add_days';
|
import addDays from 'date-fns/add_days';
|
||||||
import addMonths from 'date-fns/add_months';
|
import addMonths from 'date-fns/add_months';
|
||||||
@ -10,52 +11,92 @@ import isEqual from 'date-fns/is_equal';
|
|||||||
import FullWidthRow from '../../helpers/FullWidthRow';
|
import FullWidthRow from '../../helpers/FullWidthRow';
|
||||||
import Spacer from '../../helpers/Spacer';
|
import Spacer from '../../helpers/Spacer';
|
||||||
|
|
||||||
import 'react-calendar-heatmap/dist/styles.css';
|
import '@freecodecamp/react-calendar-heatmap/dist/styles.css';
|
||||||
import './heatmap.css';
|
import './heatmap.css';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
calendar: PropTypes.object,
|
calendar: PropTypes.object
|
||||||
streak: PropTypes.shape({
|
|
||||||
current: PropTypes.number,
|
|
||||||
longest: PropTypes.number
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function HeatMap({ calendar, streak }) {
|
const innerPropTypes = {
|
||||||
const endOfCalendar = startOfDay(Date.now());
|
calendarData: PropTypes.array,
|
||||||
const startOfCalendar = addMonths(endOfCalendar, -6);
|
currentStreak: PropTypes.number,
|
||||||
|
longestStreak: PropTypes.number,
|
||||||
|
pages: PropTypes.array,
|
||||||
|
points: PropTypes.number
|
||||||
|
};
|
||||||
|
|
||||||
let calendarData = [];
|
class HeatMapInner extends Component {
|
||||||
let dayCounter = startOfCalendar;
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
// create a data point for each day of the calendar period (six months)
|
this.state = {
|
||||||
while (dayCounter <= endOfCalendar) {
|
pageIndex: this.props.pages.length - 1
|
||||||
// this is the format needed for react-calendar-heatmap
|
|
||||||
const newDay = {
|
|
||||||
date: startOfDay(dayCounter),
|
|
||||||
count: 0
|
|
||||||
};
|
};
|
||||||
|
|
||||||
calendarData.push(newDay);
|
this.prevPage = this.prevPage.bind(this);
|
||||||
dayCounter = addDays(dayCounter, 1);
|
this.nextPage = this.nextPage.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let timestamp of Object.keys(calendar)) {
|
prevPage() {
|
||||||
timestamp = Number(timestamp * 1000) || null;
|
this.setState(
|
||||||
if (timestamp) {
|
{
|
||||||
const index = calendarData.findIndex(day =>
|
pageIndex: this.state.pageIndex - 1
|
||||||
isEqual(day.date, startOfDay(timestamp))
|
},
|
||||||
);
|
() => ReactTooltip.rebuild()
|
||||||
|
);
|
||||||
if (index >= 0) {
|
|
||||||
calendarData[index].count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
nextPage() {
|
||||||
<FullWidthRow>
|
this.setState(
|
||||||
|
{
|
||||||
|
pageIndex: this.state.pageIndex + 1
|
||||||
|
},
|
||||||
|
() => ReactTooltip.rebuild()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { calendarData, currentStreak, longestStreak, pages } = this.props;
|
||||||
|
const { startOfCalendar, endOfCalendar } = pages[this.state.pageIndex];
|
||||||
|
const title = `${startOfCalendar.toLocaleDateString('en-US', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'short'
|
||||||
|
})} - ${endOfCalendar.toLocaleDateString('en-US', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'short'
|
||||||
|
})}`;
|
||||||
|
const dataToDisplay = calendarData.filter(
|
||||||
|
data => data.date >= startOfCalendar && data.date <= endOfCalendar
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
<FullWidthRow>
|
<FullWidthRow>
|
||||||
|
<Row className='heatmap-nav'>
|
||||||
|
<button
|
||||||
|
className='heatmap-nav-btn'
|
||||||
|
disabled={!pages[this.state.pageIndex - 1]}
|
||||||
|
onClick={this.prevPage}
|
||||||
|
style={{
|
||||||
|
visibility: pages[this.state.pageIndex - 1] ? 'unset' : 'hidden'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<
|
||||||
|
</button>
|
||||||
|
<span>{title}</span>
|
||||||
|
<button
|
||||||
|
className='heatmap-nav-btn'
|
||||||
|
disabled={!pages[this.state.pageIndex + 1]}
|
||||||
|
onClick={this.nextPage}
|
||||||
|
style={{
|
||||||
|
visibility: pages[this.state.pageIndex + 1] ? 'unset' : 'hidden'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
>
|
||||||
|
</button>
|
||||||
|
</Row>
|
||||||
|
<Spacer />
|
||||||
|
|
||||||
<CalendarHeatMap
|
<CalendarHeatMap
|
||||||
classForValue={value => {
|
classForValue={value => {
|
||||||
if (!value || value.count < 1) return 'color-empty';
|
if (!value || value.count < 1) return 'color-empty';
|
||||||
@ -87,27 +128,126 @@ function HeatMap({ calendar, streak }) {
|
|||||||
'data-tip': `<b>${valueCount}</b> ${dateFormatted}`
|
'data-tip': `<b>${valueCount}</b> ${dateFormatted}`
|
||||||
};
|
};
|
||||||
}}
|
}}
|
||||||
values={calendarData}
|
values={dataToDisplay}
|
||||||
/>
|
/>
|
||||||
<ReactTooltip className='react-tooltip' effect='solid' html={true} />
|
<ReactTooltip className='react-tooltip' effect='solid' html={true} />
|
||||||
|
|
||||||
|
<Spacer />
|
||||||
|
<Row>
|
||||||
|
<div className='streak-container'>
|
||||||
|
<span className='streak' data-testid='longest-streak'>
|
||||||
|
<b>Longest Streak:</b> {longestStreak || 0}
|
||||||
|
</span>
|
||||||
|
<span className='streak' data-testid='current-streak'>
|
||||||
|
<b>Current Streak:</b> {currentStreak || 0}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</Row>
|
||||||
|
<hr />
|
||||||
</FullWidthRow>
|
</FullWidthRow>
|
||||||
<Spacer />
|
);
|
||||||
<FullWidthRow>
|
}
|
||||||
<div className='streak-container'>
|
|
||||||
<span className='streak'>
|
|
||||||
<b>Longest Streak:</b> {streak.longest || 0}
|
|
||||||
</span>
|
|
||||||
<span className='streak'>
|
|
||||||
<b>Current Streak:</b> {streak.current || 0}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</FullWidthRow>
|
|
||||||
<Spacer />
|
|
||||||
<hr />
|
|
||||||
</FullWidthRow>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HeatMapInner.propTypes = innerPropTypes;
|
||||||
|
|
||||||
|
const HeatMap = props => {
|
||||||
|
const { calendar } = props;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the following logic creates the data for the heatmap
|
||||||
|
* from the users calendar and calculates their streaks
|
||||||
|
*/
|
||||||
|
|
||||||
|
// create array of timestamps and turn into milliseconds
|
||||||
|
const timestamps = Object.keys(calendar).map(stamp => stamp * 1000);
|
||||||
|
const startOfTimestamps = startOfDay(new Date(timestamps[0]));
|
||||||
|
let endOfCalendar = startOfDay(Date.now());
|
||||||
|
let startOfCalendar;
|
||||||
|
|
||||||
|
// creates pages for heatmap
|
||||||
|
let pages = [];
|
||||||
|
|
||||||
|
do {
|
||||||
|
startOfCalendar = addDays(addMonths(endOfCalendar, -6), 1);
|
||||||
|
|
||||||
|
const newPage = {
|
||||||
|
startOfCalendar: startOfCalendar,
|
||||||
|
endOfCalendar: endOfCalendar
|
||||||
|
};
|
||||||
|
|
||||||
|
pages.push(newPage);
|
||||||
|
|
||||||
|
endOfCalendar = addDays(startOfCalendar, -1);
|
||||||
|
} while (startOfTimestamps < startOfCalendar);
|
||||||
|
|
||||||
|
pages.reverse();
|
||||||
|
|
||||||
|
let calendarData = [];
|
||||||
|
let dayCounter = pages[0].startOfCalendar;
|
||||||
|
|
||||||
|
// create an object for each day of the calendar period
|
||||||
|
while (dayCounter <= pages[pages.length - 1].endOfCalendar) {
|
||||||
|
// this is the format needed for react-calendar-heatmap
|
||||||
|
const newDay = {
|
||||||
|
date: startOfDay(dayCounter),
|
||||||
|
count: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
calendarData.push(newDay);
|
||||||
|
dayCounter = addDays(dayCounter, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let longestStreak = 0;
|
||||||
|
let currentStreak = 0;
|
||||||
|
let lastIndex = -1;
|
||||||
|
|
||||||
|
// add a point to each day with a completed timestamp and calculate streaks
|
||||||
|
timestamps.forEach(stamp => {
|
||||||
|
const index = calendarData.findIndex(day =>
|
||||||
|
isEqual(day.date, startOfDay(stamp))
|
||||||
|
);
|
||||||
|
|
||||||
|
if (index >= 0) {
|
||||||
|
// add one point for today
|
||||||
|
calendarData[index].count++;
|
||||||
|
|
||||||
|
// if timestamp is on a new day, deal with streaks
|
||||||
|
if (index !== lastIndex) {
|
||||||
|
// if yesterday has points
|
||||||
|
if (calendarData[index - 1] && calendarData[index - 1].count > 0) {
|
||||||
|
currentStreak++;
|
||||||
|
} else {
|
||||||
|
currentStreak = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentStreak > longestStreak) {
|
||||||
|
longestStreak = currentStreak;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lastIndex = index;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// if today has no points
|
||||||
|
if (
|
||||||
|
calendarData[calendarData.length - 1] &&
|
||||||
|
calendarData[calendarData.length - 1].count === 0
|
||||||
|
) {
|
||||||
|
currentStreak = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HeatMapInner
|
||||||
|
calendarData={calendarData}
|
||||||
|
currentStreak={currentStreak}
|
||||||
|
longestStreak={longestStreak}
|
||||||
|
pages={pages}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
HeatMap.displayName = 'HeatMap';
|
HeatMap.displayName = 'HeatMap';
|
||||||
HeatMap.propTypes = propTypes;
|
HeatMap.propTypes = propTypes;
|
||||||
|
|
||||||
|
@ -6,23 +6,27 @@ import { render } from '@testing-library/react';
|
|||||||
|
|
||||||
import HeatMap from './HeatMap';
|
import HeatMap from './HeatMap';
|
||||||
|
|
||||||
|
// offset is used to shift the dates so that the calendar renders (for testing
|
||||||
|
// purposes only) the same way in each timezone.
|
||||||
|
const offset = new Date().getTimezoneOffset() * 60;
|
||||||
|
const date1 = 1580497504 + offset;
|
||||||
|
const date2 = 1580597504 + offset;
|
||||||
|
const date3 = 1580729769 + offset;
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
calendar: {
|
calendar: {}
|
||||||
1580393017: 1,
|
|
||||||
1580397504: 1
|
|
||||||
},
|
|
||||||
streak: {
|
|
||||||
current: 2,
|
|
||||||
longest: 2
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
props.calendar[date1] = 1;
|
||||||
|
props.calendar[date2] = 1;
|
||||||
|
props.calendar[date3] = 1;
|
||||||
|
|
||||||
let dateNowMockFn;
|
let dateNowMockFn;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
dateNowMockFn = jest
|
dateNowMockFn = jest
|
||||||
.spyOn(Date, 'now')
|
.spyOn(Date, 'now')
|
||||||
.mockImplementation(() => 1580729769714);
|
.mockImplementation(() => 1580729769714 + offset * 1000);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -34,4 +38,18 @@ describe('<HeatMap/>', () => {
|
|||||||
const { container } = render(<HeatMap {...props} />);
|
const { container } = render(<HeatMap {...props} />);
|
||||||
expect(container).toMatchSnapshot();
|
expect(container).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('calculates the correct longest streak', () => {
|
||||||
|
const { getByTestId } = render(<HeatMap {...props} />);
|
||||||
|
expect(getByTestId('longest-streak').textContent).toContain(
|
||||||
|
'Longest Streak: 2'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calculates the correct current streak', () => {
|
||||||
|
const { getByTestId } = render(<HeatMap {...props} />);
|
||||||
|
expect(getByTestId('current-streak').textContent).toContain(
|
||||||
|
'Current Streak: 1'
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -6,8 +6,16 @@
|
|||||||
color: var(--primary-color);
|
color: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.heatmap-nav {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heatmap-nav-btn {
|
||||||
|
margin: 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.react-calendar-heatmap-month-label {
|
.react-calendar-heatmap-month-label {
|
||||||
color: var(--primary-color);
|
fill: var(--gray-45) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-calendar-heatmap .color-empty {
|
.react-calendar-heatmap .color-empty {
|
||||||
|
Reference in New Issue
Block a user