Skip to content

Commit e4821b5

Browse files
authored
fix: cp-7.58.0 formatTimeRemaining to correctly display time remaining (#21843)
## **Description** Right now the rewards component that displays the number of time left is incorrectly showing the time left if the hours value returned by `getTimeDifferenceFromNow` is 0. (it will only show minutes left in this case) This will happen if the end date of the season versus the current time on the app is X day(s) 0 hours and X minute(s). ## **Changelog** CHANGELOG entry: fix rewards season time remaining ## **Related issues** Fixes: #21842 ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <img width="654" height="206" alt="image" src="https://github.com/user-attachments/assets/353da666-7a10-4433-b6fd-1e08bf88309d" /> ### **After** <img width="656" height="273" alt="image" src="https://github.com/user-attachments/assets/3a36bfa3-6c26-4966-b4ab-c9c09f865fc4" /> ## **Pre-merge author checklist** - [x] I’ve followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [x] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [x] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Adjust time-remaining formatting to include days/hours/minutes with stricter null conditions and update tests, plus locale checks for rewards date formatting. > > - **Utilities**: > - Update `formatTimeRemaining` to compose output from `days`, `hours`, and `minutes`, trim trailing space, and return `null` only when all are non-positive. > - **Tests**: > - Revise and expand `formatUtils.test.ts` for new `formatTimeRemaining` behavior (e.g., days+minutes, hours-only, minutes-only, large diffs, zero/negative cases, trimming). > - Add locale-aware checks for `formatRewardsDate` (default and custom locales). > - Maintain number formatting fallback test using mocked `getIntlNumberFormatter`. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit a87305a. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 6a58e30 commit e4821b5

File tree

2 files changed

+79
-30
lines changed

2 files changed

+79
-30
lines changed

app/components/UI/Rewards/utils/formatUtils.test.ts

Lines changed: 74 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ describe('formatUtils', () => {
8888
});
8989

9090
describe('formatTimeRemaining', () => {
91-
it('should return formatted time with days and hours when hours > 0', () => {
91+
it('returns formatted time with days, hours, and minutes when all are positive', () => {
9292
// Given: 2 days, 5 hours, 30 minutes remaining
9393
mockGetTimeDifferenceFromNow.mockReturnValue({
9494
days: 2,
@@ -101,14 +101,14 @@ describe('formatUtils', () => {
101101
// When: formatting time remaining
102102
const result = formatTimeRemaining(endDate);
103103

104-
// Then: should return days and hours format
105-
expect(result).toBe('2d 5h');
104+
// Then: should return days, hours, and minutes format
105+
expect(result).toBe('2d 5h 30m');
106106
expect(mockGetTimeDifferenceFromNow).toHaveBeenCalledWith(
107107
endDate.getTime(),
108108
);
109109
});
110110

111-
it('should return formatted time with only minutes when hours = 0 and minutes > 0', () => {
111+
it('returns formatted time with only minutes when hours and days are zero', () => {
112112
// Given: 0 hours, 45 minutes remaining
113113
mockGetTimeDifferenceFromNow.mockReturnValue({
114114
days: 0,
@@ -125,7 +125,7 @@ describe('formatUtils', () => {
125125
expect(result).toBe('45m');
126126
});
127127

128-
it('should return null when both hours and minutes are 0', () => {
128+
it('returns null when days, hours, and minutes are all zero', () => {
129129
// Given: 0 hours, 0 minutes remaining
130130
mockGetTimeDifferenceFromNow.mockReturnValue({
131131
days: 0,
@@ -142,7 +142,7 @@ describe('formatUtils', () => {
142142
expect(result).toBeNull();
143143
});
144144

145-
it('should return null when minutes are negative (past date)', () => {
145+
it('returns null when time values are negative for past date', () => {
146146
// Given: negative minutes (past date)
147147
mockGetTimeDifferenceFromNow.mockReturnValue({
148148
days: 0,
@@ -159,7 +159,7 @@ describe('formatUtils', () => {
159159
expect(result).toBeNull();
160160
});
161161

162-
it('should handle edge case with 0 days, 1 hour, 0 minutes', () => {
162+
it('returns only hours when days and minutes are zero', () => {
163163
// Given: exactly 1 hour remaining
164164
mockGetTimeDifferenceFromNow.mockReturnValue({
165165
days: 0,
@@ -172,11 +172,11 @@ describe('formatUtils', () => {
172172
// When: formatting time remaining
173173
const result = formatTimeRemaining(endDate);
174174

175-
// Then: should return days and hours format
176-
expect(result).toBe('0d 1h');
175+
// Then: should return hours format only
176+
expect(result).toBe('1h');
177177
});
178178

179-
it('should handle large time differences correctly', () => {
179+
it('returns days, hours, and minutes for large time differences', () => {
180180
// Given: 365 days, 23 hours, 59 minutes remaining
181181
mockGetTimeDifferenceFromNow.mockReturnValue({
182182
days: 365,
@@ -189,11 +189,11 @@ describe('formatUtils', () => {
189189
// When: formatting time remaining
190190
const result = formatTimeRemaining(endDate);
191191

192-
// Then: should return days and hours format
193-
expect(result).toBe('365d 23h');
192+
// Then: should return days, hours, and minutes format
193+
expect(result).toBe('365d 23h 59m');
194194
});
195195

196-
it('should handle single digit values correctly', () => {
196+
it('returns single digit values without padding', () => {
197197
// Given: 1 day, 1 hour, 1 minute remaining
198198
mockGetTimeDifferenceFromNow.mockReturnValue({
199199
days: 1,
@@ -206,11 +206,11 @@ describe('formatUtils', () => {
206206
// When: formatting time remaining
207207
const result = formatTimeRemaining(endDate);
208208

209-
// Then: should return days and hours format without padding
210-
expect(result).toBe('1d 1h');
209+
// Then: should return days, hours, and minutes format without padding
210+
expect(result).toBe('1d 1h 1m');
211211
});
212212

213-
it('should prioritize hours over minutes when hours > 0', () => {
213+
it('returns hours and minutes when days are zero', () => {
214214
// Given: 0 days, 2 hours, 59 minutes remaining
215215
mockGetTimeDifferenceFromNow.mockReturnValue({
216216
days: 0,
@@ -223,11 +223,11 @@ describe('formatUtils', () => {
223223
// When: formatting time remaining
224224
const result = formatTimeRemaining(endDate);
225225

226-
// Then: should return days and hours format (ignoring minutes)
227-
expect(result).toBe('0d 2h');
226+
// Then: should return hours and minutes format
227+
expect(result).toBe('2h 59m');
228228
});
229229

230-
it('should handle exactly 1 minute remaining', () => {
230+
it('returns exactly 1 minute when only minutes remain', () => {
231231
// Given: exactly 1 minute remaining
232232
mockGetTimeDifferenceFromNow.mockReturnValue({
233233
days: 0,
@@ -244,7 +244,7 @@ describe('formatUtils', () => {
244244
expect(result).toBe('1m');
245245
});
246246

247-
it('should handle zero days with hours correctly', () => {
247+
it('returns hours and minutes when days are zero', () => {
248248
// Given: 0 days, 12 hours, 30 minutes remaining
249249
mockGetTimeDifferenceFromNow.mockReturnValue({
250250
days: 0,
@@ -257,11 +257,11 @@ describe('formatUtils', () => {
257257
// When: formatting time remaining
258258
const result = formatTimeRemaining(endDate);
259259

260-
// Then: should return days and hours format with 0 days
261-
expect(result).toBe('0d 12h');
260+
// Then: should return hours and minutes format
261+
expect(result).toBe('12h 30m');
262262
});
263263

264-
it('should call getTimeDifferenceFromNow with correct timestamp', () => {
264+
it('calls getTimeDifferenceFromNow with correct timestamp', () => {
265265
// Given: a specific end date
266266
const endDate = new Date('2024-06-15T10:30:00Z');
267267
const expectedTimestamp = endDate.getTime();
@@ -281,6 +281,57 @@ describe('formatUtils', () => {
281281
);
282282
expect(mockGetTimeDifferenceFromNow).toHaveBeenCalledTimes(1);
283283
});
284+
285+
it('returns days and minutes when hours are zero', () => {
286+
// Given: 3 days, 0 hours, 15 minutes remaining
287+
mockGetTimeDifferenceFromNow.mockReturnValue({
288+
days: 3,
289+
hours: 0,
290+
minutes: 15,
291+
});
292+
293+
const endDate = new Date('2024-01-04T12:15:00Z');
294+
295+
// When: formatting time remaining
296+
const result = formatTimeRemaining(endDate);
297+
298+
// Then: should return days and minutes format
299+
expect(result).toBe('3d 15m');
300+
});
301+
302+
it('returns only days when hours and minutes are zero', () => {
303+
// Given: 5 days, 0 hours, 0 minutes remaining
304+
mockGetTimeDifferenceFromNow.mockReturnValue({
305+
days: 5,
306+
hours: 0,
307+
minutes: 0,
308+
});
309+
310+
const endDate = new Date('2024-01-06T12:00:00Z');
311+
312+
// When: formatting time remaining
313+
const result = formatTimeRemaining(endDate);
314+
315+
// Then: should return days format only
316+
expect(result).toBe('5d');
317+
});
318+
319+
it('trims trailing space when minutes are zero', () => {
320+
// Given: 2 days, 3 hours, 0 minutes remaining
321+
mockGetTimeDifferenceFromNow.mockReturnValue({
322+
days: 2,
323+
hours: 3,
324+
minutes: 0,
325+
});
326+
327+
const endDate = new Date('2024-12-31T15:00:00Z');
328+
329+
// When: formatting time remaining
330+
const result = formatTimeRemaining(endDate);
331+
332+
// Then: should return days and hours format without trailing space
333+
expect(result).toBe('2d 3h');
334+
});
284335
});
285336

286337
describe('formatNumber', () => {

app/components/UI/Rewards/utils/formatUtils.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,15 @@ export const formatTimeRemaining = (endDate: Date): string | null => {
4545
const { days, hours, minutes } = getTimeDifferenceFromNow(endDate.getTime());
4646

4747
// No time remaining
48-
if (hours <= 0 && minutes <= 0) {
48+
if (hours <= 0 && minutes <= 0 && days <= 0) {
4949
return null;
5050
}
5151

52-
// Only minutes remaining
53-
if (hours <= 0) {
54-
return `${minutes}m`;
55-
}
52+
const dayString = days > 0 ? `${days}d ` : '';
53+
const hourString = hours > 0 ? `${hours}h ` : '';
54+
const minuteString = minutes > 0 ? `${minutes}m` : '';
5655

57-
// Hours and days remaining
58-
return `${days}d ${hours}h`;
56+
return `${dayString}${hourString}${minuteString}`?.trim();
5957
};
6058

6159
// Get icon name with fallback to Star if invalid

0 commit comments

Comments
 (0)