From a0b9a04d31a0fea3e65f21872ad4e68b74411ff3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 1 Mar 2026 21:37:50 +0000 Subject: [PATCH 1/2] Initial plan From 2637e50606e4f0260b0186cb755c38eeb98ee328 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 1 Mar 2026 21:41:54 +0000 Subject: [PATCH 2/2] Fix streak counter to count consecutive fully-completed days Co-authored-by: JoeProgrammer88 <7156063+JoeProgrammer88@users.noreply.github.com> --- src/storage.ts | 41 ++++++++++++++++++++++++-- tests/storage.test.ts | 68 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 106 insertions(+), 3 deletions(-) diff --git a/src/storage.ts b/src/storage.ts index e5e888e..d870247 100644 --- a/src/storage.ts +++ b/src/storage.ts @@ -326,16 +326,53 @@ export class StorageManager { timestamp: new Date().toISOString() }); - // Update habit streak + // Update habit streak based on fully-completed consecutive days const habit = data.habits.find(h => h.id === habitId); if (habit) { habit.lastCompletedDate = dateStr; - habit.streak = (habit.streak || 0) + 1; + habit.streak = this.calculateHabitStreak(habitId, habit.targetGoal, data.dailyHabitLogs); } this.saveData(data); } + calculateHabitStreak(habitId: string, targetGoal: number, logs: HabitLog[]): number { + // Count completions per date for this habit + const habitLogs = logs.filter(l => l.habitId === habitId); + const countsByDate: Record = {}; + for (const log of habitLogs) { + countsByDate[log.date] = (countsByDate[log.date] || 0) + 1; + } + + // Get dates where fully completed (>= targetGoal), sorted most recent first + const completedDates = Object.keys(countsByDate) + .filter(date => countsByDate[date] >= targetGoal) + .sort() + .reverse(); + + if (completedDates.length === 0) return 0; + + // Count consecutive days going backward from the most recent fully-completed day + let streak = 1; + let currentDate = completedDates[0]; + + for (let i = 1; i < completedDates.length; i++) { + const [year, month, day] = currentDate.split('-').map(Number); + const prevDay = new Date(year, month - 1, day); + prevDay.setDate(prevDay.getDate() - 1); + const expectedDate = this.formatDate(prevDay); + + if (completedDates[i] === expectedDate) { + streak++; + currentDate = completedDates[i]; + } else { + break; + } + } + + return streak; + } + isHabitCompletedToday(habitId: string): boolean { const data = this.getData(); const todayStr = this.formatDate(new Date()); diff --git a/tests/storage.test.ts b/tests/storage.test.ts index a4b184a..4d0fade 100644 --- a/tests/storage.test.ts +++ b/tests/storage.test.ts @@ -164,13 +164,66 @@ describe('StorageManager', () => { expect(storage.getHabits().length).toBe(0); }); - it('should log habit completion and increment streak', () => { + it('should log habit completion and set streak to 1 when targetGoal met', () => { const habit = storage.addHabit({ name: 'Exercise' }); storage.logHabitCompletion(habit.id, new Date()); const updated = storage.getHabits().find(h => h.id === habit.id); expect(updated?.streak).toBe(1); }); + it('should not increment streak when targetGoal is not yet met', () => { + const habit = storage.addHabit({ name: 'Drink Water', targetGoal: 10 }); + storage.logHabitCompletion(habit.id, new Date()); + storage.logHabitCompletion(habit.id, new Date()); + const updated = storage.getHabits().find(h => h.id === habit.id); + expect(updated?.streak).toBe(0); + }); + + it('should set streak to 1 when targetGoal is fully met', () => { + const habit = storage.addHabit({ name: 'Drink Water', targetGoal: 3 }); + storage.logHabitCompletion(habit.id, new Date()); + storage.logHabitCompletion(habit.id, new Date()); + storage.logHabitCompletion(habit.id, new Date()); + const updated = storage.getHabits().find(h => h.id === habit.id); + expect(updated?.streak).toBe(1); + }); + + it('should count consecutive fully-completed days as streak', () => { + const habit = storage.addHabit({ name: 'Exercise' }); + const day1 = new Date(2025, 0, 13); + const day2 = new Date(2025, 0, 14); + const day3 = new Date(2025, 0, 15); + storage.logHabitCompletion(habit.id, day1); + storage.logHabitCompletion(habit.id, day2); + storage.logHabitCompletion(habit.id, day3); + const updated = storage.getHabits().find(h => h.id === habit.id); + expect(updated?.streak).toBe(3); + }); + + it('should reset streak count when there is a gap between completed days', () => { + const habit = storage.addHabit({ name: 'Exercise' }); + const day1 = new Date(2025, 0, 13); + const day3 = new Date(2025, 0, 15); + storage.logHabitCompletion(habit.id, day1); + storage.logHabitCompletion(habit.id, day3); + const updated = storage.getHabits().find(h => h.id === habit.id); + expect(updated?.streak).toBe(1); + }); + + it('should extend streak when retroactively completing a missed day', () => { + const habit = storage.addHabit({ name: 'Exercise' }); + const day1 = new Date(2025, 0, 13); + const day2 = new Date(2025, 0, 14); + const day3 = new Date(2025, 0, 15); + storage.logHabitCompletion(habit.id, day1); + storage.logHabitCompletion(habit.id, day3); + // Streak is 1 (gap at day2) + storage.logHabitCompletion(habit.id, day2); + // Now day1, day2, day3 are all complete → streak = 3 + const updated = storage.getHabits().find(h => h.id === habit.id); + expect(updated?.streak).toBe(3); + }); + it('should count habit completions for today', () => { const habit = storage.addHabit({ name: 'Push-ups', targetGoal: 3 }); storage.logHabitCompletion(habit.id, new Date()); @@ -193,6 +246,19 @@ describe('StorageManager', () => { expect(storage.countHabitCompletionsForDate(habit.id, '2025-01-15')).toBe(2); expect(storage.countHabitCompletionsForDate(habit.id, '2025-01-16')).toBe(0); }); + + it('should calculate streak correctly using calculateHabitStreak', () => { + const habit = storage.addHabit({ name: 'Drink Water', targetGoal: 2 }); + const day1 = new Date(2025, 0, 13); + const day2 = new Date(2025, 0, 14); + storage.logHabitCompletion(habit.id, day1); + storage.logHabitCompletion(habit.id, day1); + storage.logHabitCompletion(habit.id, day2); + storage.logHabitCompletion(habit.id, day2); + const data = storage.getData(); + const streak = storage.calculateHabitStreak(habit.id, habit.targetGoal, data.dailyHabitLogs); + expect(streak).toBe(2); + }); }); // ========================