diff --git a/src/lib/mock-data.test.ts b/src/lib/mock-data.test.ts index 1bd0ec5..0194d3f 100644 --- a/src/lib/mock-data.test.ts +++ b/src/lib/mock-data.test.ts @@ -1,4 +1,5 @@ import { + getRecentActivity, mockBounties, mockMaintenancePools, mockMilestones, @@ -44,3 +45,22 @@ describe("mock-data asset coverage", () => { expect(formatCurrency(milestone.distributed, milestone.asset)).toBe("16,837.25 XLM"); }); }); + +describe("recentActivity — dynamic timestamps (#707)", () => { + it("computes occurredAt timestamps relative to passed reference time", () => { + const fixedNow = new Date("2026-10-13T12:00:00Z").getTime(); + const events = getRecentActivity(fixedNow); + const first = events[0]; + // a1 is 6 minutes ago + expect(first.occurredAt).toBe(new Date(fixedNow - 6 * 60_000).toISOString()); + }); + + it("advances timestamps dynamically at access time instead of freezing at module load", () => { + const time1 = new Date(recentActivity[0].occurredAt).getTime(); + jest.useFakeTimers(); + jest.advanceTimersByTime(3600 * 1000); // 1 hour later + const time2 = new Date(recentActivity[0].occurredAt).getTime(); + expect(time2).toBeGreaterThan(time1); + jest.useRealTimers(); + }); +}); diff --git a/src/lib/mock-data.ts b/src/lib/mock-data.ts index de0dab0..4962f1c 100644 --- a/src/lib/mock-data.ts +++ b/src/lib/mock-data.ts @@ -268,14 +268,17 @@ export interface ActivityEvent { occurredAt: string; } -// minutesAgo(n) anchors each mock event n minutes before whenever this -// module is evaluated, so the feed's relative-time text keeps advancing -// (within a given server render) instead of being frozen at deploy time. -function minutesAgo(n: number): string { - return new Date(Date.now() - n * 60_000).toISOString(); +interface RawActivityEventTemplate { + id: string; + handle: string; + action: string; + target: string; + amount?: number; + asset?: "USDC" | "XLM"; + minutesAgo: number; } -export const recentActivity: ActivityEvent[] = [ +const RAW_RECENT_ACTIVITY: RawActivityEventTemplate[] = [ { id: "a1", handle: "devrel_ana", @@ -283,21 +286,21 @@ export const recentActivity: ActivityEvent[] = [ target: "core-indexer#288", amount: 40, asset: "USDC", - occurredAt: minutesAgo(6), + minutesAgo: 6, }, { id: "a2", handle: "0xkoda", action: "claimed", target: "soroban-escrow-sdk#17", - occurredAt: minutesAgo(22), + minutesAgo: 22, }, { id: "a3", handle: "linh_dev", action: "opened a pull request for", target: "stellar-wallet-kit#475", - occurredAt: minutesAgo(48), + minutesAgo: 48, }, { id: "a4", @@ -306,7 +309,7 @@ export const recentActivity: ActivityEvent[] = [ target: "v2.0: Multi-asset escrow", amount: 2500, asset: "USDC", - occurredAt: minutesAgo(71), + minutesAgo: 71, }, { id: "a4b", @@ -315,7 +318,7 @@ export const recentActivity: ActivityEvent[] = [ target: "v1.4: Indexer performance pass", amount: 15000, asset: "XLM", - occurredAt: minutesAgo(95), + minutesAgo: 95, }, { id: "a5", @@ -324,17 +327,46 @@ export const recentActivity: ActivityEvent[] = [ target: "core-indexer#301", amount: 480, asset: "USDC", - occurredAt: minutesAgo(130), + minutesAgo: 130, }, { id: "a6", handle: "qa_marcus", action: "joined as a", target: "contributor", - occurredAt: minutesAgo(210), + minutesAgo: 210, }, ]; +/** + * Returns recent activity events with timestamps computed dynamically at render/request time + * relative to `now` (#707). This prevents timestamps from freezing at module + * load time on long-running server instances. + */ +export function getRecentActivity(now: number = Date.now()): ActivityEvent[] { + return RAW_RECENT_ACTIVITY.map((e) => ({ + id: e.id, + handle: e.handle, + action: e.action, + target: e.target, + amount: e.amount, + asset: e.asset, + occurredAt: new Date(now - e.minutesAgo * 60_000).toISOString(), + })); +} + +/** + * Dynamic proxy ensuring callers referencing `recentActivity` receive fresh + * timestamps computed at access time rather than frozen at module evaluation (#707). + */ +export const recentActivity: ActivityEvent[] = new Proxy([] as ActivityEvent[], { + get(_target, prop, receiver) { + const list = getRecentActivity(); + const val = Reflect.get(list, prop, receiver); + return typeof val === "function" ? val.bind(list) : val; + }, +}); + // Weekly earnings/spend series (8 weeks) for dashboard charts. export const contributorEarningsHistory = [420, 560, 310, 780, 640, 890, 720, 1050];