// What core's Team activity feed says (client/src/lib/teamActivity.js). // // The test that earns this file: a projection nobody can tell is stale, and a // feed nobody can tell is filtered, both look like complete information. Every // case below is about saying which one the reader is looking at. // // Note the wording assertions avoid core's own noun. The feed renders inside a // page a MODULE titled — Guilds today, Clans next — so "this Team" would be // core's vocabulary leaking onto a surface that deliberately does not use it. import { test } from 'node:test' import assert from 'node:assert/strict' import { activityScopeNote, freshnessNote, groupByDay, relativeTime } from '../src/lib/teamActivity.js' const NOW = new Date('2026-08-17T12:00:00Z').getTime() const ago = (ms) => new Date(NOW - ms).toISOString() test('a deployment with no provider is not stale, it is uninvolved', () => { assert.equal(freshnessNote({ configured: false }, NOW), null) }) test('never synced is a warning, and never reads as a confirmed empty shard', () => { const note = freshnessNote({ configured: true, lastSyncAt: null }, NOW) assert.equal(note.tone, 'warn') assert.match(note.text, /Not yet confirmed/) }) test('a stale projection says how old it is and that the game may have moved on', () => { const note = freshnessNote({ configured: true, lastSyncAt: ago(14 * 60_000), stale: true }, NOW) assert.equal(note.tone, 'warn') assert.equal(note.text, 'Last confirmed 14 minutes ago — the game may have moved on.') }) test('a current projection is stated quietly', () => { const note = freshnessNote({ configured: true, lastSyncAt: ago(90_000), stale: false }, NOW) assert.equal(note.tone, 'idle') assert.equal(note.text, 'Last confirmed 1 minute ago.') }) test('relative time singularises and steps through the units', () => { assert.equal(relativeTime(ago(5_000), NOW), 'just now') assert.equal(relativeTime(ago(60_000), NOW), '1 minute ago') assert.equal(relativeTime(ago(3 * 3_600_000), NOW), '3 hours ago') assert.equal(relativeTime(ago(2 * 86_400_000), NOW), '2 days ago') assert.equal(relativeTime(null, NOW), null) assert.equal(relativeTime('not a date', NOW), null) }) test('items group into days, newest day first, order kept within a day', () => { const days = groupByDay([ { id: 3, occurredAt: '2026-08-17T09:00:00' }, { id: 2, occurredAt: '2026-08-17T08:00:00' }, { id: 1, occurredAt: '2026-08-16T22:00:00' }, ], 'en-US') assert.equal(days.length, 2) assert.deepEqual(days[0].items.map((i) => i.id), [3, 2]) assert.deepEqual(days[1].items.map((i) => i.id), [1]) }) test('an unparseable timestamp is skipped rather than making a day called Invalid Date', () => { assert.deepEqual(groupByDay([{ id: 1, occurredAt: 'nonsense' }], 'en-US'), []) }) test('a caller who saw everything is told nothing', () => { assert.equal(activityScopeNote({ scope: 'members' }, true), null) }) test('a filtered feed says so, and invites an anonymous caller to sign in', () => { assert.match(activityScopeNote({ scope: 'public' }, false), /Sign in/) assert.match(activityScopeNote({ scope: 'public' }, true), /members only/) }) test('the wording never says "Team" — that is core\'s noun, not the page\'s', () => { for (const signedIn of [true, false]) { assert.doesNotMatch(activityScopeNote({ scope: 'public' }, signedIn), /Team/) } assert.doesNotMatch(freshnessNote({ configured: true, lastSyncAt: null }, NOW).text, /Team/) })