Skip to content

Commit

Permalink
fix: tests for LocalizationUtils. for times we should use math.trunk …
Browse files Browse the repository at this point in the history
…instead of floor
  • Loading branch information
abose committed Dec 30, 2024
1 parent 239cf81 commit 00f1ee7
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 31 deletions.
3 changes: 2 additions & 1 deletion docs/API-Reference/utils/LocalizationUtils.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Formats a given date object into a locale-aware date and time string.

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

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

**Kind**: global function
Expand All @@ -44,6 +44,7 @@ Returns a relative time string (e.g., "2 days ago", "in 3 hours") based on the d
| --- | --- | --- |
| [date] | <code>Date</code> | The date to compare with the current date and time. If not given, defaults to now. |
| [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'. |
| [fromDate] | <code>string</code> | Optional date to use instead of now to compute the relative dateTime from. |

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

Expand Down
25 changes: 14 additions & 11 deletions src/utils/LocalizationUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,28 +77,31 @@ define(function (require, exports, module) {
* @param {Date} [date] - The date to compare with the current date and time. If not given, defaults to now.
* @param {string} [lang] - Optional language code to use for formatting (e.g., 'en', 'fr').
* If not provided, defaults to the application locale or 'en'.
* @param {Date} [fromDate] - Optional date to use instead of now to compute the relative dateTime from.
* @returns {string} - A human-readable relative time string (e.g., "2 days ago", "in 3 hours").
*/
function dateTimeFromNow(date, lang) {
function dateTimeFromNow(date, lang, fromDate) {
date = date || new Date();
const now = new Date();
const diffInSeconds = Math.floor((date - now) / 1000);
fromDate = fromDate || new Date();
const diffInSeconds = Math.floor((date - fromDate) / 1000);

const rtf = new Intl.RelativeTimeFormat([lang || brackets.getLocale() || "en", "en"],
{ numeric: 'auto' });

if (Math.abs(diffInSeconds) < 60) {
if (Math.abs(diffInSeconds) < 3) {
// we consider diffs less than 3 seconds to be always 'now', for better UX and for unit tests stability.
return rtf.format(0, 'second');
} else if (Math.abs(diffInSeconds) < 60) {
return rtf.format(diffInSeconds, 'second');
} else if (Math.abs(diffInSeconds) < 3600) {
return rtf.format(Math.floor(diffInSeconds / 60), 'minute');
return rtf.format(Math.trunc(diffInSeconds / 60), 'minute');
} else if (Math.abs(diffInSeconds) < 86400) {
return rtf.format(Math.floor(diffInSeconds / 3600), 'hour');
return rtf.format(Math.trunc(diffInSeconds / 3600), 'hour');
} else if (Math.abs(diffInSeconds) < 2592000) {
return rtf.format(Math.floor(diffInSeconds / 86400), 'day');
return rtf.format(Math.trunc(diffInSeconds / 86400), 'day');
} else if (Math.abs(diffInSeconds) < 31536000) {
return rtf.format(Math.floor(diffInSeconds / 2592000), 'month');
return rtf.format(Math.trunc(diffInSeconds / 2592000), 'month');
} else {
return rtf.format(Math.floor(diffInSeconds / 31536000), 'year');
return rtf.format(Math.trunc(diffInSeconds / 31536000), 'year');
}
}

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

// If within the last 30 days or the future, use relative time
if (Math.abs(diffInDays) <= 30) {
Expand Down
92 changes: 73 additions & 19 deletions test/spec/LocalizationUtils-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,64 +132,118 @@ define(function (require, exports, module) {
});

describe("dateTimeFromNow", function () {
it("should return 'now' for current time", function () {
const now = new Date();
// we take a reference date for tests stability. due to use of math.trunk, using date.now will
// sometimes lead to valid time jumps in tests. so we just take a reference time fixed to not account for
// any time taken in compute for tests.
const refDate = Date.now();
function referenceDate({
seconds = 0,
minutes = 0,
hours = 0,
days = 0,
months = 0,
years = 0
} = {}) {
const date = new Date(refDate); // Original reference date

// Add time components
date.setSeconds(date.getSeconds() + seconds);
date.setMinutes(date.getMinutes() + minutes);
date.setHours(date.getHours() + hours);
date.setDate(date.getDate() + days);
date.setMonth(date.getMonth() + months);
date.setFullYear(date.getFullYear() + years);

return date;
}

it("should return 'now' for current time without specifying fromTime", function () {
let now = new Date();
let result = LocalizationUtils.dateTimeFromNow(now, "en");
expect(result).toBe("now");
result = LocalizationUtils.dateTimeFromNow(now, "de");
expect(result).toBe("jetzt");
});

it("should return 'now' for current time within 3 seconds of time", function () {
let now = referenceDate();
let result = LocalizationUtils.dateTimeFromNow(now, "en", referenceDate());
expect(result).toBe("now");
now = referenceDate({seconds: 1});
result = LocalizationUtils.dateTimeFromNow(now, "en", referenceDate());
expect(result).toBe("now");
now = referenceDate({seconds: 2});
result = LocalizationUtils.dateTimeFromNow(now, "en", referenceDate());
expect(result).toBe("now");
result = LocalizationUtils.dateTimeFromNow(now, "de", referenceDate());
expect(result).toBe("jetzt");
now = referenceDate({seconds: 1});
result = LocalizationUtils.dateTimeFromNow(now, "de", referenceDate());
expect(result).toBe("jetzt");
now = referenceDate({seconds: 2});
result = LocalizationUtils.dateTimeFromNow(now, "de", referenceDate());
expect(result).toBe("jetzt");
});

it("should handle future dates within seconds", function () {
const futureDate = new Date(Date.now() + 30 * 1000); // 30 seconds in the future
const result = LocalizationUtils.dateTimeFromNow(futureDate, "en");
const futureDate = referenceDate({seconds: 30}); // 30 seconds in the future
const result = LocalizationUtils.dateTimeFromNow(futureDate, "en", referenceDate());
expect(result).toBe("in 30 seconds");
});

it("should handle past dates within minutes", function () {
const pastDate = new Date(Date.now() - 90 * 1000); // 90 seconds in the past
const result = LocalizationUtils.dateTimeFromNow(pastDate, "en");
const pastDate = referenceDate({seconds: -90}); // 90 seconds in the past
const result = LocalizationUtils.dateTimeFromNow(pastDate, "en", referenceDate());
expect(result).toBe("1 minute ago");
});

it("should handle past and future dates similarly", function () {
const pastDate = referenceDate({seconds: -130}); // 2 minutes and 10 secs
let result = LocalizationUtils.dateTimeFromNow(pastDate, "en", referenceDate());
expect(result).toBe("2 minutes ago");
const futureDate = referenceDate({seconds: 130}); // 2 minutes and 10 secs
result = LocalizationUtils.dateTimeFromNow(futureDate, "en", referenceDate());
expect(result).toBe("in 2 minutes");
});

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

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

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

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

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

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

it("should handle default date input (now) gracefully", function () {
const result = LocalizationUtils.dateTimeFromNow(undefined, "en");
const result = LocalizationUtils.dateTimeFromNow();
expect(result).toBe("now");
});
});
Expand Down

0 comments on commit 00f1ee7

Please sign in to comment.