Add packages/common for shared utilities

Create new @reviq/common package with environment-agnostic utilities:
- Date formatting: formatDate, formatDateTime, formatLongDate,
  formatRelativeDate, formatRelativeTime
- User utilities: getUserInitials, formatRole

Consolidate date formatting from publisher-dashboard into shared package.
All utilities include comprehensive test coverage with bun:test.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
igm
2026-01-11 12:34:10 +08:00
parent 7358129802
commit b1d07626f3
25 changed files with 639 additions and 300 deletions

View File

@@ -0,0 +1,139 @@
import { describe, expect, test } from "bun:test";
import {
formatDate,
formatDateTime,
formatLongDate,
formatRelativeDate,
formatRelativeTime,
} from "./format-date.js";
describe("formatDate", () => {
test("formats a Date object", () => {
const date = new Date("2024-01-15T12:00:00Z");
expect(formatDate(date)).toBe("Jan 15, 2024");
});
test("formats a date string", () => {
expect(formatDate("2024-01-15T12:00:00Z")).toBe("Jan 15, 2024");
});
test("formats different months correctly", () => {
expect(formatDate("2024-06-01T12:00:00Z")).toBe("Jun 1, 2024");
expect(formatDate("2024-12-25T12:00:00Z")).toBe("Dec 25, 2024");
});
});
describe("formatDateTime", () => {
test("formats date with time", () => {
const date = new Date("2024-01-15T15:30:00Z");
const result = formatDateTime(date);
// Contains date parts
expect(result).toContain("Jan");
expect(result).toContain("15");
expect(result).toContain("2024");
// Contains time (format may vary by locale)
expect(result).toMatch(/\d{1,2}:\d{2}/);
});
test("formats a date string with time", () => {
const result = formatDateTime("2024-01-15T08:00:00Z");
expect(result).toContain("Jan");
expect(result).toContain("15");
expect(result).toContain("2024");
});
});
describe("formatLongDate", () => {
test("formats date in long form", () => {
const date = new Date("2024-01-15T12:00:00Z");
expect(formatLongDate(date)).toBe("January 15, 2024");
});
test("formats a date string in long form", () => {
expect(formatLongDate("2024-06-01T12:00:00Z")).toBe("June 1, 2024");
});
test("formats December correctly", () => {
expect(formatLongDate("2024-12-25T12:00:00Z")).toBe("December 25, 2024");
});
});
describe("formatRelativeDate", () => {
const now = new Date("2024-01-15T12:00:00Z");
test("returns 'Today' for same day", () => {
const today = new Date("2024-01-15T08:00:00Z");
expect(formatRelativeDate(today, { now })).toBe("Today");
});
test("returns 'Yesterday' for previous day", () => {
const yesterday = new Date("2024-01-14T12:00:00Z");
expect(formatRelativeDate(yesterday, { now })).toBe("Yesterday");
});
test("returns 'X days ago' for 2-6 days", () => {
expect(formatRelativeDate("2024-01-13T12:00:00Z", { now })).toBe(
"2 days ago",
);
expect(formatRelativeDate("2024-01-12T12:00:00Z", { now })).toBe(
"3 days ago",
);
expect(formatRelativeDate("2024-01-09T12:00:00Z", { now })).toBe(
"6 days ago",
);
});
test("returns '1 week ago' for exactly 7 days", () => {
const oneWeekAgo = new Date("2024-01-08T12:00:00Z");
expect(formatRelativeDate(oneWeekAgo, { now })).toBe("1 week ago");
});
test("returns 'X weeks ago' for 2-4 weeks", () => {
expect(formatRelativeDate("2024-01-01T12:00:00Z", { now })).toBe(
"2 weeks ago",
);
expect(formatRelativeDate("2023-12-25T12:00:00Z", { now })).toBe(
"3 weeks ago",
);
});
test("returns formatted date for older dates in same year", () => {
// Use a "now" later in the year to test same-year formatting
const laterNow = new Date("2024-06-15T12:00:00Z");
const result = formatRelativeDate("2024-01-15T12:00:00Z", { now: laterNow });
expect(result).toBe("Jan 15");
});
test("returns formatted date with year for different year", () => {
const result = formatRelativeDate("2023-06-15T12:00:00Z", { now });
expect(result).toBe("Jun 15, 2023");
});
test("accepts string input", () => {
expect(formatRelativeDate("2024-01-15T08:00:00Z", { now })).toBe("Today");
});
});
describe("formatRelativeTime", () => {
const now = new Date("2024-01-15T12:00:00Z");
test("returns 'Never' for null", () => {
expect(formatRelativeTime(null)).toBe("Never");
});
test("returns 'Never' for undefined", () => {
expect(formatRelativeTime(undefined)).toBe("Never");
});
test("returns relative date for valid input", () => {
expect(formatRelativeTime("2024-01-15T08:00:00Z", { now })).toBe("Today");
expect(formatRelativeTime("2024-01-14T12:00:00Z", { now })).toBe(
"Yesterday",
);
});
test("handles Date objects", () => {
const date = new Date("2024-01-13T12:00:00Z");
expect(formatRelativeTime(date, { now })).toBe("2 days ago");
});
});