From 4bef15371fa194435ecd6e55d44a35fd3eae3eb0 Mon Sep 17 00:00:00 2001 From: ZL Asica <40444637+ZL-Asica@users.noreply.github.com> Date: Thu, 7 Nov 2024 19:19:48 -0600 Subject: [PATCH] fix(streak-count): Fix streak count calculation. - count will no longer be store in user.streaks, but will be calculated on the fly. - Update streakUtils to calculate streak count on the fly. - Update Streak component to use streakUtils to calculate streak count. - Update test accordingly. - Update UserContext'a type declaration accordingly. --- src/contexts/UserContext.jsx | 1 - src/hooks/useGoalsUpdater.test.js | 40 +++--------------- src/pages/Streak.jsx | 6 +-- src/utils/streakUtils.js | 69 +++++++++++++++++-------------- 4 files changed, 46 insertions(+), 70 deletions(-) diff --git a/src/contexts/UserContext.jsx b/src/contexts/UserContext.jsx index aab96d2..8705261 100644 --- a/src/contexts/UserContext.jsx +++ b/src/contexts/UserContext.jsx @@ -46,7 +46,6 @@ import { createContext, useContext, useEffect, useState } from 'react' /** * @typedef {Object} Streak * @property {Object.} completedDays - Map of dates (as strings) to their completion counts. - * @property {number} count - Current streak count. */ /** diff --git a/src/hooks/useGoalsUpdater.test.js b/src/hooks/useGoalsUpdater.test.js index 626fafc..db3b6be 100644 --- a/src/hooks/useGoalsUpdater.test.js +++ b/src/hooks/useGoalsUpdater.test.js @@ -21,7 +21,7 @@ describe('useGoalsUpdater', () => { profilePic: 'test-pic-url', name: 'Test User', goals: [], // Start with an empty goals array - streak: { count: 0, completedDays: [] }, + streak: [], } updateProfile = vi.fn(async (updates) => { @@ -105,6 +105,9 @@ describe('useGoalsUpdater', () => { ], }) + // should be empty Array + expect(user.streak).toEqual([]) + await goalsUpdater.toggleTaskCompletion( goalIndex, microGoalIndex, @@ -115,39 +118,8 @@ describe('useGoalsUpdater', () => { user.goals[goalIndex].microgoals[microGoalIndex].tasks[taskIndex] expect(task.completed).toBe(true) - expect(updateProfile).toHaveBeenCalledWith( - expect.objectContaining({ - goals: user.goals, - streak: expect.objectContaining({ - count: 1, - completedDays: expect.objectContaining({ - [new Date().toISOString().split('T')[0]]: 1, // Check today's date in YYYY-MM-DD format - }), - }), - }) - ) - }) - - it('should toggle microgoal expansion', async () => { - const goalIndex = 0 - const microGoalIndex = 0 - - // Add initial goal and microgoal to user data - user.goals.push({ - name: 'Goal 1', - category: '#000000', - microgoals: [{ name: 'MicroGoal 1', expanded: false, tasks: [] }], - }) - - await goalsUpdater.toggleExpansion(goalIndex, microGoalIndex) - - expect(user.goals[goalIndex].microgoals[microGoalIndex].expanded).toBe(true) - - expect(updateProfile).toHaveBeenCalledWith( - expect.objectContaining({ - goals: user.goals, - }) - ) + // check streak is not empty + expect(user.streak).not.toEqual([]) }) it('should delete a specified task', async () => { diff --git a/src/pages/Streak.jsx b/src/pages/Streak.jsx index 6e52415..f723e3c 100644 --- a/src/pages/Streak.jsx +++ b/src/pages/Streak.jsx @@ -1,6 +1,6 @@ import { useUser } from '@/contexts/UserContext' import '@/styles/StreakPage.css' -import { getChicagoDate } from '@/utils/streakUtils' +import { calculateStreakCount } from '@/utils/streakUtils' import FireIcon from '@mui/icons-material/Whatshot' import { Box, Typography } from '@mui/material' import { useMemo } from 'react' @@ -9,10 +9,8 @@ import 'react-calendar/dist/Calendar.css' const Streak = () => { const { user } = useUser() - - const streakCount = user.streak?.count || 0 const completedDays = user.streak?.completedDays || {} - const today = getChicagoDate() + const { today, streakCount } = calculateStreakCount(completedDays) // Cache the completed dates const completedDatesSet = useMemo( diff --git a/src/utils/streakUtils.js b/src/utils/streakUtils.js index 2ca396b..3753f69 100644 --- a/src/utils/streakUtils.js +++ b/src/utils/streakUtils.js @@ -1,4 +1,5 @@ // @ts-check +import dayjs from 'dayjs' /** * Gets the current date in Chicago timezone (formatted as YYYY-MM-DD). @@ -13,49 +14,55 @@ export const getChicagoDate = () => { } /** - * Streak data type for a user. - * @typedef {Object} Streak - * @property {Object.} completedDays - Map of date strings to their completion counts. - * @property {number} count - Current streak count. + * Completed days map with date-count pairs. + * @typedef {Object} completedDays + * @property {string} date - Date in YYYY-MM-DD format. + * @property {number} count - Number of completions for the date. */ /** - * User data type. - * @typedef {Object} User - * @property {Streak} streak - User's streak data. + * Updates the completed days for a user based on the current date. + * @param {completedDays} completedDays - The completed days map. + * @param {number} countChange - 1 or -1 to increment or decrement the count for the current date. + * @returns {completedDays} - Updated completedDays as a map of date-count pairs. */ - -/** - * Updates the streak count and completed days for a user. - * @param {User} user - The user object containing streak information. - * @param {number} countChange - The change to apply to the completion count for the current date. - * @returns {{completedDays: Object., count: number}} - Updated completedDays as a map of date-count pairs and the new streak count. - */ -export const updateStreakDays = (user, countChange) => { +export const updateStreakDays = (completedDays, countChange) => { const currentDate = getChicagoDate() - // { completedDays: { '2024-11-01': 1, '2024-11-02': 0 }, count: 1 } - const completedDays = user.streak?.completedDays || {} - const count = user.streak?.count || 0 - // Get the current count for the current date or initialize it to 0 const currentCount = completedDays[currentDate] || 0 // Update the count for the current date completedDays[currentDate] = Math.max(0, currentCount + countChange) - // Adjust the streak count based on changes to the current day's count - let newCount = count - if (currentCount === 0 && countChange > 0) { - // Increment streak if new positive count for the day - newCount++ - } else if (currentCount > 0 && completedDays[currentDate] === 0) { - // Decrement streak if current day count goes to 0 - newCount = Math.max(0, newCount - 1) - } + return completedDays +} + +/** + * Calculates the current streak count based on completedDays. + * @param {completedDays} completedDays - The map of date strings to completion counts. + * @returns {{ today: string, streakCount: number }} - The current date and streak count. + */ +export const calculateStreakCount = (completedDays) => { + const today = getChicagoDate() + const yesterday = dayjs(today).subtract(1, 'day').format('YYYY-MM-DD') + + // Sort dates in descending order, so we can check from yesterday backward + const dates = Object.keys(completedDays).sort( + (a, b) => new Date(b).getTime() - new Date(a).getTime() + ) - return { - completedDays, - count: newCount, + let streakCount = 0 + + for (const date of dates) { + if (date === today) continue // Skip today, only count until yesterday + + if (completedDays[date] > 0 && date <= yesterday) { + streakCount++ + } else { + break // Stop counting if a non-completed day is found + } } + + return { today, streakCount } }