feat(client): sidebar previews for name_changed + icon_changed

This commit is contained in:
Jannis Braun
2026-05-10 19:43:31 +02:00
parent d508fecfb0
commit 91aa06b48b
2 changed files with 97 additions and 2 deletions
+84 -2
View File
@@ -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');
});
});
+13
View File
@@ -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';
}