Files
Nico 4c6aa754e0 Time ago timestamps: use days instead of months when under 90 days (#48964)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #46965

Relative "time ago" timestamps switched to months at ~30 days, so a
timestamp 45 days ago read "about 2 months ago" (even 89 days showed "3
months ago"). This centralizes the day/month cutoff in a new `timeAgo`
helper and routes existing call sites through it, so anything under 90
days is shown in days.

# Checklist for submitter

- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.

## Testing

- [x] Added/updated automated tests

- [x] QA'd all new/changed functionality manually


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Relative “time ago” timestamps now keep values in **days** for items
under **90 days**, switching to **months** later for more accurate
wording.
* Improved consistency of relative time labels across status modals,
activity feeds, host details, and management screens (including “last
updated,” “uploaded,” and “added” text).
* **Tests**
* Added/updated coverage for the shared relative-time cutoff and
formatting behavior to prevent regressions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-08 15:07:48 -03:00

115 lines
3.8 KiB
TypeScript

import {
dateAgo,
monthDayYearFormat,
addedFromNow,
uploadedFromNow,
monthDayTimeFormat,
timeAgo,
} from ".";
describe("date_format utilities", () => {
beforeEach(() => {
jest.useFakeTimers().setSystemTime(new Date("2026-06-15T12:00:00Z"));
});
afterEach(() => {
jest.useRealTimers();
});
describe("uploadedFromNow util", () => {
it("returns an user friendly uploaded message", () => {
const currentDate = new Date();
currentDate.setDate(currentDate.getDate() - 2);
const twoDaysAgo = currentDate.toISOString();
expect(uploadedFromNow(twoDaysAgo)).toEqual("Uploaded 2 days ago");
});
});
describe("addedFromNow util", () => {
it("returns an user friendly added message", () => {
const currentDate = new Date();
currentDate.setDate(currentDate.getDate() - 2);
const twoDaysAgo = currentDate.toISOString();
expect(addedFromNow(twoDaysAgo)).toEqual("Added 2 days ago");
});
});
describe("monthDayYearFormat util", () => {
it("returns a date in the format of 'MonthName Date, Year' (e.g. January 01, 2024)", () => {
const date = "2024-11-29T00:00:00Z";
expect(monthDayYearFormat(date)).toEqual("November 29, 2024");
});
});
describe("dateAgo util", () => {
it("returns a user friendly date ago message from a date string", () => {
const currentDate = new Date();
currentDate.setDate(currentDate.getDate() - 2);
const twoDaysAgo = currentDate.toISOString();
expect(dateAgo(twoDaysAgo)).toEqual("2 days ago");
});
it("returns a user friendly date ago message from a Date object", () => {
const date = new Date();
date.setDate(date.getDate() - 2);
expect(dateAgo(date)).toEqual("2 days ago");
});
const daysAgo = (n: number) =>
new Date(Date.now() - n * 24 * 60 * 60 * 1000).toISOString();
it("uses days below the month threshold", () => {
expect(dateAgo(daysAgo(5))).toEqual("5 days ago");
expect(dateAgo(daysAgo(29))).toEqual("29 days ago");
expect(dateAgo(daysAgo(30))).toEqual("30 days ago");
expect(dateAgo(daysAgo(40))).toEqual("40 days ago");
expect(dateAgo(daysAgo(60))).toEqual("60 days ago");
expect(dateAgo(daysAgo(89))).toEqual("89 days ago");
});
it("uses months at or beyond 90 days", () => {
expect(dateAgo(daysAgo(90))).toEqual("3 months ago");
expect(dateAgo(daysAgo(100))).toEqual("3 months ago");
});
});
describe("timeAgo util", () => {
const daysAgo = (n: number) =>
new Date(Date.now() - n * 24 * 60 * 60 * 1000);
it("omits the `ago` suffix by default and adds it when requested", () => {
expect(timeAgo(daysAgo(40))).toEqual("40 days");
expect(timeAgo(daysAgo(40), { addSuffix: true })).toEqual("40 days ago");
expect(timeAgo(daysAgo(89), { addSuffix: true })).toEqual("89 days ago");
});
it("switches to months at 90 days", () => {
expect(timeAgo(daysAgo(90), { addSuffix: true })).toEqual("3 months ago");
});
// strict avoids the "about" prefix outside the day window.
it("supports the strict variant outside the window", () => {
expect(timeAgo(daysAgo(100), { addSuffix: true, strict: true })).toEqual(
"3 months ago"
);
});
});
describe("monthDayTimeFormat util", () => {
it("returns a formatted date string matching pattern 'Mon D, H:MM AM/PM'", () => {
const date = "2024-03-20T13:35:00Z";
const result = monthDayTimeFormat(date);
// Match pattern like "Mar 20, 1:35 PM" (exact time varies by timezone)
expect(result).toMatch(/^[A-Z][a-z]{2} \d{1,2}, \d{1,2}:\d{2} (AM|PM)$/);
});
it("returns an empty string for invalid dates", () => {
expect(monthDayTimeFormat("invalid-date")).toEqual("");
});
});
});