feat(client): sidebar previews for name_changed + icon_changed
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { describe, it, expect, vi, afterEach } from 'vitest';
|
||||
import { formatDmTimestamp } from './dmFormatters';
|
||||
import { formatDmPreview } from './dmFormatters';
|
||||
import { formatDmTimestamp, formatDmPreview, formatDmSidebarPreview } from './dmFormatters';
|
||||
import type { DmChannel, DmLastMessagePreview, User } from '@backspace/shared';
|
||||
|
||||
/** Build a local-time Date: new Date(year, month-1, day, hour, minute) as a timestamp. */
|
||||
function localTs(year: number, month: number, day: number, hour = 12, minute = 0): number {
|
||||
@@ -180,3 +180,85 @@ describe('formatDmPreview', () => {
|
||||
})).toBe('check this 🎬');
|
||||
});
|
||||
});
|
||||
|
||||
// ─── System message previews — exercised via formatDmSidebarPreview ──────────
|
||||
|
||||
const actor: User = {
|
||||
id: 'U1',
|
||||
username: 'jannis',
|
||||
displayName: 'Jannis',
|
||||
avatarColor: 'mint',
|
||||
avatar: null,
|
||||
bio: null,
|
||||
banner: null,
|
||||
accentColor: null,
|
||||
homeUserId: null,
|
||||
homeInstance: null,
|
||||
status: 'online',
|
||||
customStatus: null,
|
||||
isAdmin: false,
|
||||
createdAt: 0,
|
||||
replicatedInstances: [],
|
||||
};
|
||||
|
||||
function makeGroupDm(lastMessage: DmLastMessagePreview): Pick<DmChannel, 'lastMessage' | 'ownerId' | 'members'> {
|
||||
return {
|
||||
ownerId: 'U1', // makes it a group DM
|
||||
members: [actor],
|
||||
lastMessage,
|
||||
};
|
||||
}
|
||||
|
||||
describe('formatDmSidebarPreview — name_changed system message', () => {
|
||||
it('happy path with newName → "<actor> renamed the group"', () => {
|
||||
const dm = makeGroupDm({
|
||||
type: 'system',
|
||||
userId: 'U1',
|
||||
content: JSON.stringify({ event: 'name_changed', oldName: null, newName: 'Cool Group' }),
|
||||
createdAt: 1,
|
||||
});
|
||||
expect(formatDmSidebarPreview(dm, { id: 'OTHER', username: 'other' })).toBe('Jannis renamed the group');
|
||||
});
|
||||
|
||||
it('newName=null (cleared) → "<actor> cleared the group name"', () => {
|
||||
const dm = makeGroupDm({
|
||||
type: 'system',
|
||||
userId: 'U1',
|
||||
content: JSON.stringify({ event: 'name_changed', oldName: 'Old', newName: null }),
|
||||
createdAt: 1,
|
||||
});
|
||||
expect(formatDmSidebarPreview(dm, { id: 'OTHER', username: 'other' })).toBe('Jannis cleared the group name');
|
||||
});
|
||||
|
||||
it('unresolvable actor → "Unknown renamed the group"', () => {
|
||||
const dm = makeGroupDm({
|
||||
type: 'system',
|
||||
userId: 'GHOST', // not in members roster
|
||||
content: JSON.stringify({ event: 'name_changed', oldName: null, newName: 'X' }),
|
||||
createdAt: 1,
|
||||
});
|
||||
expect(formatDmSidebarPreview(dm, { id: 'OTHER', username: 'other' })).toBe('Unknown renamed the group');
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatDmSidebarPreview — icon_changed system message', () => {
|
||||
it('happy path → "<actor> updated the group icon"', () => {
|
||||
const dm = makeGroupDm({
|
||||
type: 'system',
|
||||
userId: 'U1',
|
||||
content: JSON.stringify({ event: 'icon_changed' }),
|
||||
createdAt: 1,
|
||||
});
|
||||
expect(formatDmSidebarPreview(dm, { id: 'OTHER', username: 'other' })).toBe('Jannis updated the group icon');
|
||||
});
|
||||
|
||||
it('unresolvable actor → "Unknown updated the group icon"', () => {
|
||||
const dm = makeGroupDm({
|
||||
type: 'system',
|
||||
userId: 'GHOST',
|
||||
content: JSON.stringify({ event: 'icon_changed' }),
|
||||
createdAt: 1,
|
||||
});
|
||||
expect(formatDmSidebarPreview(dm, { id: 'OTHER', username: 'other' })).toBe('Unknown updated the group icon');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -101,6 +101,9 @@ interface SystemEventPayload {
|
||||
newOwnerId?: unknown;
|
||||
newOwnerDisplayName?: unknown;
|
||||
reason?: unknown;
|
||||
// name_changed payload
|
||||
oldName?: unknown;
|
||||
newName?: unknown;
|
||||
// space_invite payload
|
||||
snapshot?: { spaceName?: unknown };
|
||||
}
|
||||
@@ -149,6 +152,16 @@ function formatSystemPreview(content: string | null, actor: User | null | undefi
|
||||
const newOwner = asString(data.newOwnerDisplayName) ?? 'A member';
|
||||
return `${newOwner} is now the group owner`;
|
||||
}
|
||||
case 'name_changed': {
|
||||
// newName === null is a meaningful "cleared" state — distinct from a
|
||||
// missing field — so we check for a non-empty string explicitly.
|
||||
return asString(data.newName)
|
||||
? `${actorName} renamed the group`
|
||||
: `${actorName} cleared the group name`;
|
||||
}
|
||||
case 'icon_changed': {
|
||||
return `${actorName} updated the group icon`;
|
||||
}
|
||||
default:
|
||||
return 'System message';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user