Skip to content

Commit 00f1ee7

Browse files
committed
fix: tests for LocalizationUtils. for times we should use math.trunk instead of floor
1 parent 239cf81 commit 00f1ee7

File tree

3 files changed

+89
-31
lines changed

3 files changed

+89
-31
lines changed

docs/API-Reference/utils/LocalizationUtils.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ Formats a given date object into a locale-aware date and time string.
3434

3535
<a name="dateTimeFromNow"></a>
3636

37-
## dateTimeFromNow([date], [lang]) ⇒ <code>string</code>
37+
## dateTimeFromNow([date], [lang], [fromDate]) ⇒ <code>string</code>
3838
Returns a relative time string (e.g., "2 days ago", "in 3 hours") based on the difference between the given date and now.
3939

4040
**Kind**: global function
@@ -44,6 +44,7 @@ Returns a relative time string (e.g., "2 days ago", "in 3 hours") based on the d
4444
| --- | --- | --- |
4545
| [date] | <code>Date</code> | The date to compare with the current date and time. If not given, defaults to now. |
4646
| [lang] | <code>string</code> | Optional language code to use for formatting (e.g., 'en', 'fr'). If not provided, defaults to the application locale or 'en'. |
47+
| [fromDate] | <code>string</code> | Optional date to use instead of now to compute the relative dateTime from. |
4748

4849
<a name="dateTimeFromNowFriendly"></a>
4950

src/utils/LocalizationUtils.js

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -77,28 +77,31 @@ define(function (require, exports, module) {
7777
* @param {Date} [date] - The date to compare with the current date and time. If not given, defaults to now.
7878
* @param {string} [lang] - Optional language code to use for formatting (e.g., 'en', 'fr').
7979
* If not provided, defaults to the application locale or 'en'.
80+
* @param {Date} [fromDate] - Optional date to use instead of now to compute the relative dateTime from.
8081
* @returns {string} - A human-readable relative time string (e.g., "2 days ago", "in 3 hours").
8182
*/
82-
function dateTimeFromNow(date, lang) {
83+
function dateTimeFromNow(date, lang, fromDate) {
8384
date = date || new Date();
84-
const now = new Date();
85-
const diffInSeconds = Math.floor((date - now) / 1000);
85+
fromDate = fromDate || new Date();
86+
const diffInSeconds = Math.floor((date - fromDate) / 1000);
8687

8788
const rtf = new Intl.RelativeTimeFormat([lang || brackets.getLocale() || "en", "en"],
8889
{ numeric: 'auto' });
89-
90-
if (Math.abs(diffInSeconds) < 60) {
90+
if (Math.abs(diffInSeconds) < 3) {
91+
// we consider diffs less than 3 seconds to be always 'now', for better UX and for unit tests stability.
92+
return rtf.format(0, 'second');
93+
} else if (Math.abs(diffInSeconds) < 60) {
9194
return rtf.format(diffInSeconds, 'second');
9295
} else if (Math.abs(diffInSeconds) < 3600) {
93-
return rtf.format(Math.floor(diffInSeconds / 60), 'minute');
96+
return rtf.format(Math.trunc(diffInSeconds / 60), 'minute');
9497
} else if (Math.abs(diffInSeconds) < 86400) {
95-
return rtf.format(Math.floor(diffInSeconds / 3600), 'hour');
98+
return rtf.format(Math.trunc(diffInSeconds / 3600), 'hour');
9699
} else if (Math.abs(diffInSeconds) < 2592000) {
97-
return rtf.format(Math.floor(diffInSeconds / 86400), 'day');
100+
return rtf.format(Math.trunc(diffInSeconds / 86400), 'day');
98101
} else if (Math.abs(diffInSeconds) < 31536000) {
99-
return rtf.format(Math.floor(diffInSeconds / 2592000), 'month');
102+
return rtf.format(Math.trunc(diffInSeconds / 2592000), 'month');
100103
} else {
101-
return rtf.format(Math.floor(diffInSeconds / 31536000), 'year');
104+
return rtf.format(Math.trunc(diffInSeconds / 31536000), 'year');
102105
}
103106
}
104107

@@ -115,7 +118,7 @@ define(function (require, exports, module) {
115118
function dateTimeFromNowFriendly(date, lang) {
116119
const now = new Date();
117120
const diffInMilliseconds = date - now;
118-
const diffInDays = Math.floor(diffInMilliseconds / (1000 * 60 * 60 * 24));
121+
const diffInDays = Math.trunc(diffInMilliseconds / (1000 * 60 * 60 * 24));
119122

120123
// If within the last 30 days or the future, use relative time
121124
if (Math.abs(diffInDays) <= 30) {

test/spec/LocalizationUtils-test.js

Lines changed: 73 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -132,64 +132,118 @@ define(function (require, exports, module) {
132132
});
133133

134134
describe("dateTimeFromNow", function () {
135-
it("should return 'now' for current time", function () {
136-
const now = new Date();
135+
// we take a reference date for tests stability. due to use of math.trunk, using date.now will
136+
// sometimes lead to valid time jumps in tests. so we just take a reference time fixed to not account for
137+
// any time taken in compute for tests.
138+
const refDate = Date.now();
139+
function referenceDate({
140+
seconds = 0,
141+
minutes = 0,
142+
hours = 0,
143+
days = 0,
144+
months = 0,
145+
years = 0
146+
} = {}) {
147+
const date = new Date(refDate); // Original reference date
148+
149+
// Add time components
150+
date.setSeconds(date.getSeconds() + seconds);
151+
date.setMinutes(date.getMinutes() + minutes);
152+
date.setHours(date.getHours() + hours);
153+
date.setDate(date.getDate() + days);
154+
date.setMonth(date.getMonth() + months);
155+
date.setFullYear(date.getFullYear() + years);
156+
157+
return date;
158+
}
159+
160+
it("should return 'now' for current time without specifying fromTime", function () {
161+
let now = new Date();
137162
let result = LocalizationUtils.dateTimeFromNow(now, "en");
138163
expect(result).toBe("now");
139164
result = LocalizationUtils.dateTimeFromNow(now, "de");
140165
expect(result).toBe("jetzt");
141166
});
142167

168+
it("should return 'now' for current time within 3 seconds of time", function () {
169+
let now = referenceDate();
170+
let result = LocalizationUtils.dateTimeFromNow(now, "en", referenceDate());
171+
expect(result).toBe("now");
172+
now = referenceDate({seconds: 1});
173+
result = LocalizationUtils.dateTimeFromNow(now, "en", referenceDate());
174+
expect(result).toBe("now");
175+
now = referenceDate({seconds: 2});
176+
result = LocalizationUtils.dateTimeFromNow(now, "en", referenceDate());
177+
expect(result).toBe("now");
178+
result = LocalizationUtils.dateTimeFromNow(now, "de", referenceDate());
179+
expect(result).toBe("jetzt");
180+
now = referenceDate({seconds: 1});
181+
result = LocalizationUtils.dateTimeFromNow(now, "de", referenceDate());
182+
expect(result).toBe("jetzt");
183+
now = referenceDate({seconds: 2});
184+
result = LocalizationUtils.dateTimeFromNow(now, "de", referenceDate());
185+
expect(result).toBe("jetzt");
186+
});
187+
143188
it("should handle future dates within seconds", function () {
144-
const futureDate = new Date(Date.now() + 30 * 1000); // 30 seconds in the future
145-
const result = LocalizationUtils.dateTimeFromNow(futureDate, "en");
189+
const futureDate = referenceDate({seconds: 30}); // 30 seconds in the future
190+
const result = LocalizationUtils.dateTimeFromNow(futureDate, "en", referenceDate());
146191
expect(result).toBe("in 30 seconds");
147192
});
148193

149194
it("should handle past dates within minutes", function () {
150-
const pastDate = new Date(Date.now() - 90 * 1000); // 90 seconds in the past
151-
const result = LocalizationUtils.dateTimeFromNow(pastDate, "en");
195+
const pastDate = referenceDate({seconds: -90}); // 90 seconds in the past
196+
const result = LocalizationUtils.dateTimeFromNow(pastDate, "en", referenceDate());
197+
expect(result).toBe("1 minute ago");
198+
});
199+
200+
it("should handle past and future dates similarly", function () {
201+
const pastDate = referenceDate({seconds: -130}); // 2 minutes and 10 secs
202+
let result = LocalizationUtils.dateTimeFromNow(pastDate, "en", referenceDate());
152203
expect(result).toBe("2 minutes ago");
204+
const futureDate = referenceDate({seconds: 130}); // 2 minutes and 10 secs
205+
result = LocalizationUtils.dateTimeFromNow(futureDate, "en", referenceDate());
206+
expect(result).toBe("in 2 minutes");
153207
});
154208

155209
it("should handle future dates within hours", function () {
156-
const futureDate = new Date(Date.now() + 2 * 60 * 60 * 1000); // 2 hours in the future
157-
const result = LocalizationUtils.dateTimeFromNow(futureDate, "en");
210+
const futureDate = referenceDate({hours: 2}); // 2 hours in the future
211+
const result = LocalizationUtils.dateTimeFromNow(futureDate, "en", referenceDate());
158212
expect(result).toBe("in 2 hours");
159213
});
160214

161215
it("should handle past dates within days", function () {
162-
const pastDate = new Date(Date.now() - 3 * 24 * 60 * 60 * 1000); // 3 days ago
163-
const result = LocalizationUtils.dateTimeFromNow(pastDate, "en");
216+
const pastDate = referenceDate({ days: -3 }); // 3 days ago
217+
const result = LocalizationUtils.dateTimeFromNow(pastDate, "en", referenceDate());
164218
expect(result).toBe("3 days ago");
165219
});
166220

167221
it("should handle future dates within months", function () {
168-
const futureDate = new Date(Date.now() + 45 * 24 * 60 * 60 * 1000); // 45 days in the future
169-
const result = LocalizationUtils.dateTimeFromNow(futureDate, "en");
222+
const futureDate = referenceDate({ days: 45 }); // 45 days in the future
223+
const result = LocalizationUtils.dateTimeFromNow(futureDate, "en", referenceDate());
170224
expect(result).toBe("next month");
171225
});
172226

173227
it("should handle past dates within years", function () {
174-
const pastDate = new Date(Date.now() - 2 * 365 * 24 * 60 * 60 * 1000); // 2 years ago
175-
const result = LocalizationUtils.dateTimeFromNow(pastDate, "en");
228+
const pastDate = referenceDate({ years: -2 }); // 2 years ago
229+
const result = LocalizationUtils.dateTimeFromNow(pastDate, "en", referenceDate());
176230
expect(result).toBe("2 years ago");
177231
});
178232

179233
it("should return relative time in French locale", function () {
180-
const pastDate = new Date(Date.now() - 3 * 24 * 60 * 60 * 1000); // 3 days ago
181-
const result = LocalizationUtils.dateTimeFromNow(pastDate, "fr");
234+
const pastDate = referenceDate({ days: -3 }); // 3 days ago
235+
const result = LocalizationUtils.dateTimeFromNow(pastDate, "fr", referenceDate());
182236
expect(result).toBe("il y a 3 jours");
183237
});
184238

185239
it("should fallback to default locale if an invalid locale is specified", function () {
186-
const futureDate = new Date(Date.now() + 2 * 60 * 60 * 1000); // 2 hours in the future
187-
const result = LocalizationUtils.dateTimeFromNow(futureDate, "invalid-locale");
240+
const futureDate = referenceDate({ hours: 2 }); // 2 hours in the future
241+
const result = LocalizationUtils.dateTimeFromNow(futureDate, "invalid-locale", referenceDate());
188242
expect(result).toBe("in 2 hours");
189243
});
190244

191245
it("should handle default date input (now) gracefully", function () {
192-
const result = LocalizationUtils.dateTimeFromNow(undefined, "en");
246+
const result = LocalizationUtils.dateTimeFromNow();
193247
expect(result).toBe("now");
194248
});
195249
});

0 commit comments

Comments
 (0)