Revert the automated member-ordering pass that broke Angular field init (TS2729) and disable that rule until a safe reorder strategy exists. Fix modal/confirm dialog i18n defaults via template fallbacks, search all active endpoints (including offline), register foreign rooms with actor owner IDs, sync profile display names from avatar summaries, and guard dm-chat when a private call converts to a group conversation. Co-authored-by: Cursor <cursoragent@cursor.com>
167 lines
4.4 KiB
TypeScript
167 lines
4.4 KiB
TypeScript
import { User } from '../../shared-kernel';
|
|
import {
|
|
buildProfileUpsertFromAvatarSummary,
|
|
buildUserAvatarSummary,
|
|
shouldApplyAvatarTransfer,
|
|
shouldRequestAvatarData
|
|
} from './user-avatar.effects';
|
|
import { UsersActions } from './users.actions';
|
|
|
|
function createUser(overrides: Partial<User> = {}): 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);
|
|
});
|
|
});
|