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 { describe, it, expect, vi, afterEach } from 'vitest';
|
||||||
import { formatDmTimestamp } from './dmFormatters';
|
import { formatDmTimestamp, formatDmPreview, formatDmSidebarPreview } from './dmFormatters';
|
||||||
import { formatDmPreview } 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. */
|
/** 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 {
|
function localTs(year: number, month: number, day: number, hour = 12, minute = 0): number {
|
||||||
@@ -180,3 +180,85 @@ describe('formatDmPreview', () => {
|
|||||||
})).toBe('check this 🎬');
|
})).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;
|
newOwnerId?: unknown;
|
||||||
newOwnerDisplayName?: unknown;
|
newOwnerDisplayName?: unknown;
|
||||||
reason?: unknown;
|
reason?: unknown;
|
||||||
|
// name_changed payload
|
||||||
|
oldName?: unknown;
|
||||||
|
newName?: unknown;
|
||||||
// space_invite payload
|
// space_invite payload
|
||||||
snapshot?: { spaceName?: unknown };
|
snapshot?: { spaceName?: unknown };
|
||||||
}
|
}
|
||||||
@@ -149,6 +152,16 @@ function formatSystemPreview(content: string | null, actor: User | null | undefi
|
|||||||
const newOwner = asString(data.newOwnerDisplayName) ?? 'A member';
|
const newOwner = asString(data.newOwnerDisplayName) ?? 'A member';
|
||||||
return `${newOwner} is now the group owner`;
|
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:
|
default:
|
||||||
return 'System message';
|
return 'System message';
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user