import { User } from '../../shared-kernel'; import { buildProfileUpsertFromAvatarSummary, buildUserAvatarSummary, shouldApplyAvatarTransfer, shouldRequestAvatarData } from './user-avatar.effects'; import { UsersActions } from './users.actions'; function createUser(overrides: Partial = {}): User { return { id: 'user-1', oderId: 'oder-1', username: 'alice', displayName: 'Alice', status: 'online', role: 'member', joinedAt: Date.now(), ...overrides }; } describe('user avatar sync helpers', () => { it('requests avatar data when the remote version is newer', () => { const existingUser = createUser({ avatarUrl: 'data:image/webp;base64,older', avatarHash: 'hash-1', avatarUpdatedAt: 100 }); expect(shouldRequestAvatarData(existingUser, { avatarHash: 'hash-2', avatarUpdatedAt: 200 })).toBe(true); }); it('requests avatar data when metadata matches but the local payload is missing', () => { const existingUser = createUser({ avatarHash: 'hash-1', avatarMime: 'image/gif', avatarUpdatedAt: 200 }); expect(shouldRequestAvatarData(existingUser, { avatarHash: 'hash-1', avatarUpdatedAt: 200 })).toBe(true); }); it('does not request avatar data when the same payload is already present', () => { const existingUser = createUser({ avatarUrl: 'data:image/gif;base64,current', avatarHash: 'hash-1', avatarMime: 'image/gif', avatarUpdatedAt: 200 }); expect(shouldRequestAvatarData(existingUser, { avatarHash: 'hash-1', avatarUpdatedAt: 200 })).toBe(false); }); it('applies equal-version transfers when the local payload is missing', () => { const existingUser = createUser({ avatarHash: 'hash-1', avatarUpdatedAt: 200 }); expect(shouldApplyAvatarTransfer(existingUser, { hash: 'hash-1', updatedAt: 200 })).toBe(true); }); it('rejects older avatar transfers', () => { const existingUser = createUser({ avatarUrl: 'data:image/gif;base64,current', avatarHash: 'hash-2', avatarUpdatedAt: 200 }); expect(shouldApplyAvatarTransfer(existingUser, { hash: 'hash-1', updatedAt: 100 })).toBe(false); }); it('requests data when only the remote profile text is newer and no avatar exists', () => { const existingUser = createUser({ displayName: 'Alice', profileUpdatedAt: 100 }); expect(shouldRequestAvatarData(existingUser, { avatarUpdatedAt: 0, profileUpdatedAt: 200 })).toBe(true); }); it('does not request profile data when the local profile is the same or newer', () => { const existingUser = createUser({ profileUpdatedAt: 200 }); expect(shouldRequestAvatarData(existingUser, { avatarUpdatedAt: 0, profileUpdatedAt: 200 })).toBe(false); }); it('includes profile text in avatar summary broadcasts', () => { const summary = buildUserAvatarSummary(createUser({ displayName: 'Alice Two', description: 'Updated bio', profileUpdatedAt: 300 })); expect(summary).toMatchObject({ type: 'user-avatar-summary', displayName: 'Alice Two', description: 'Updated bio', profileUpdatedAt: 300 }); }); it('builds a profile upsert action from a newer avatar summary', () => { const existingUser = createUser({ displayName: 'Alice', profileUpdatedAt: 100 }); const action = buildProfileUpsertFromAvatarSummary({ oderId: 'oder-1', displayName: 'Alice Two', description: 'Updated bio', profileUpdatedAt: 200, avatarUpdatedAt: 0 }, existingUser); expect(action).toEqual(UsersActions.upsertRemoteUserAvatar({ user: { id: 'user-1', oderId: 'oder-1', username: 'alice', displayName: 'Alice Two', description: 'Updated bio', profileUpdatedAt: 200, avatarHash: undefined, avatarMime: undefined, avatarUpdatedAt: undefined, avatarUrl: undefined } })); }); it('applies profile-only transfers when the remote profile is newer', () => { const existingUser = createUser({ displayName: 'Alice', profileUpdatedAt: 100 }); expect(shouldApplyAvatarTransfer(existingUser, { hash: undefined, updatedAt: 0, profileUpdatedAt: 200 })).toBe(true); }); });