Skip to content

Commit 1f6b6c0

Browse files
committed
feat: Additional inputs to avatar #260
1 parent 582b4d6 commit 1f6b6c0

20 files changed

+287
-87
lines changed

projects/stream-chat-angular/src/lib/avatar-placeholder/avatar-placeholder.component.html

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,19 @@
33
let-name="name"
44
let-imageUrl="imageUrl"
55
let-size="size"
6+
let-type="type"
7+
let-channel="channel"
8+
let-user="user"
9+
let-location="location"
610
>
711
<stream-avatar
812
[name]="name"
913
[imageUrl]="imageUrl"
1014
[size]="size"
15+
[type]="type"
16+
[channel]="channel"
17+
[user]="user"
18+
[location]="location"
1119
></stream-avatar>
1220
</ng-template>
1321
<ng-container

projects/stream-chat-angular/src/lib/avatar-placeholder/avatar-placeholder.component.spec.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { ComponentFixture, TestBed } from '@angular/core/testing';
22
import { By } from '@angular/platform-browser';
3+
import { Channel } from 'stream-chat';
34
import { AvatarComponent } from '../avatar/avatar.component';
5+
import { DefaultStreamChatGenerics } from '../types';
46

57
import { AvatarPlaceholderComponent } from './avatar-placeholder.component';
68

@@ -33,10 +35,25 @@ describe('AvatarPlaceholderComponent', () => {
3335
component.imageUrl = 'imageUrl';
3436
component.name = 'name';
3537
component.size = 5;
38+
component.type = 'user';
39+
component.location = 'autocomplete-item';
40+
const user = { id: 'user-id' };
41+
component.user = user;
3642
fixture.detectChanges();
3743

3844
expect(avatar.imageUrl).toBe('imageUrl');
3945
expect(avatar.name).toBe('name');
4046
expect(avatar.size).toBe(5);
47+
expect(avatar.type).toBe('user');
48+
expect(avatar.location).toBe('autocomplete-item');
49+
expect(avatar.user).toBe(user);
50+
51+
component.type = 'channel';
52+
const channel = { id: 'channel-id' } as Channel<DefaultStreamChatGenerics>;
53+
component.channel = channel;
54+
fixture.detectChanges();
55+
56+
expect(avatar.type).toEqual('channel');
57+
expect(avatar.channel).toEqual(channel);
4158
});
4259
});

projects/stream-chat-angular/src/lib/avatar-placeholder/avatar-placeholder.component.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import { Component, Input } from '@angular/core';
2+
import { Channel, User } from 'stream-chat';
23
import { CustomTemplatesService } from '../custom-templates.service';
3-
import { AvatarContext } from '../types';
4+
import {
5+
AvatarContext,
6+
AvatarLocation,
7+
AvatarType,
8+
DefaultStreamChatGenerics,
9+
} from '../types';
410

511
/**
612
* The `AvatarPlaceholder` component displays the [default avatar](./AvatarComponent.mdx) unless a [custom template](../services/CustomTemplatesService.mdx) is provided. This componet is used by the SDK internally, you likely won't need to use it.
@@ -23,13 +29,33 @@ export class AvatarPlaceholderComponent {
2329
* The size in pixels of the avatar image.
2430
*/
2531
@Input() size = 32;
32+
/**
33+
* The location the avatar will be displayed in
34+
*/
35+
@Input() location: AvatarLocation | undefined;
36+
/**
37+
* The channel the avatar belongs to (if avatar of a channel is displayed)
38+
*/
39+
@Input() channel?: Channel<DefaultStreamChatGenerics>;
40+
/**
41+
* The user the avatar belongs to (if avatar of a user is displayed)
42+
*/
43+
@Input() user?: User<DefaultStreamChatGenerics>;
44+
/**
45+
* The type of the avatar: channel if channel avatar is displayed, user if user avatar is displayed
46+
*/
47+
@Input() type: AvatarType | undefined;
2648
constructor(public customTemplatesService: CustomTemplatesService) {}
2749

2850
getAvatarContext(): AvatarContext {
2951
return {
3052
name: this.name,
3153
imageUrl: this.imageUrl,
3254
size: this.size,
55+
location: this.location,
56+
type: this.type,
57+
user: this.user,
58+
channel: this.channel,
3359
};
3460
}
3561
}

projects/stream-chat-angular/src/lib/avatar/avatar.component.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<div
2-
class="str-chat__avatar str-chat__avatar--circle"
2+
class="str-chat__avatar str-chat__avatar--circle stream-chat__avatar--{{
3+
location
4+
}}"
35
title="{{ name }}"
46
[style]="{
57
flexBasis: size + 'px',

projects/stream-chat-angular/src/lib/avatar/avatar.component.spec.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
import { ChatClientService } from '../chat-client.service';
3+
import { generateMockChannels } from '../mocks';
24
import { AvatarComponent } from './avatar.component';
35

46
describe('AvatarComponent', () => {
@@ -8,10 +10,15 @@ describe('AvatarComponent', () => {
810
const imageUrl = 'https://picsum.photos/200/300';
911
let queryImg: () => HTMLImageElement | null;
1012
let queryFallbackImg: () => HTMLImageElement | null;
13+
let chatClientServiceMock: { chatClient: { user: { id: string } } };
1114

1215
beforeEach(() => {
16+
chatClientServiceMock = { chatClient: { user: { id: 'current-user' } } };
1317
TestBed.configureTestingModule({
1418
declarations: [AvatarComponent],
19+
providers: [
20+
{ provide: ChatClientService, useValue: chatClientServiceMock },
21+
],
1522
});
1623
fixture = TestBed.createComponent(AvatarComponent);
1724
component = fixture.componentInstance;
@@ -72,6 +79,7 @@ describe('AvatarComponent', () => {
7279

7380
it(`should display fallback image if #imageUrl wasn't provided`, () => {
7481
component.name = 'John Doe';
82+
component.type = 'user';
7583
fixture.detectChanges();
7684
const img = queryImg();
7785
const fallbackImg = queryFallbackImg();
@@ -83,6 +91,7 @@ describe('AvatarComponent', () => {
8391
});
8492

8593
it('should display initials correctly', () => {
94+
component.type = 'user';
8695
component.name = 'John Doe';
8796
fixture.detectChanges();
8897

@@ -97,5 +106,63 @@ describe('AvatarComponent', () => {
97106
fixture.detectChanges();
98107

99108
expect(component.initials).toBe('');
109+
110+
let channel = generateMockChannels()[0];
111+
channel.data!.name = undefined;
112+
component.channel = channel;
113+
component.type = 'channel';
114+
fixture.detectChanges();
115+
116+
expect(component.initials).toBe('#');
117+
118+
channel = generateMockChannels()[0];
119+
channel.data!.name = 'Test';
120+
channel.state.members = {
121+
otheruser: {
122+
user_id: 'otheruser',
123+
user: { id: 'otheruser', name: 'Jack' },
124+
},
125+
[chatClientServiceMock.chatClient.user.id]: {
126+
user_id: chatClientServiceMock.chatClient.user.id,
127+
user: { id: chatClientServiceMock.chatClient.user.id, name: 'Sara' },
128+
},
129+
};
130+
component.channel = channel;
131+
component.type = 'channel';
132+
fixture.detectChanges();
133+
134+
expect(component.initials).toBe('T');
135+
136+
channel = generateMockChannels()[0];
137+
channel.data!.name = undefined;
138+
channel.state.members = {
139+
otheruser: {
140+
user_id: 'otheruser',
141+
user: { id: 'otheruser', name: 'Jack' },
142+
},
143+
[chatClientServiceMock.chatClient.user.id]: {
144+
user_id: chatClientServiceMock.chatClient.user.id,
145+
user: { id: chatClientServiceMock.chatClient.user.id, name: 'Sara' },
146+
},
147+
};
148+
component.channel = channel;
149+
fixture.detectChanges();
150+
151+
expect(component.initials).toBe('J');
152+
153+
channel.state.members = {
154+
otheruser: {
155+
user_id: 'otheruser',
156+
user: { id: 'otheruser', name: 'Jack' },
157+
},
158+
otheruser2: {
159+
user_id: 'otheruser2',
160+
user: { id: 'otheruser2', name: 'Sara' },
161+
},
162+
};
163+
component.channel = channel;
164+
fixture.detectChanges();
165+
166+
expect(component.initials).toBe('#');
100167
});
101168
});

projects/stream-chat-angular/src/lib/avatar/avatar.component.ts

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11
import { Component, Input } from '@angular/core';
2+
import { Channel, User } from 'stream-chat';
3+
import { ChatClientService } from '../chat-client.service';
4+
import {
5+
AvatarLocation,
6+
AvatarType,
7+
DefaultStreamChatGenerics,
8+
} from '../types';
29

310
/**
411
* The `Avatar` component displays the provided image, with fallback to the first letter of the optional name input.
@@ -21,12 +28,49 @@ export class AvatarComponent {
2128
* The size in pixels of the avatar image.
2229
*/
2330
@Input() size = 32;
31+
/**
32+
* The location the avatar will be displayed in
33+
*/
34+
@Input() location: AvatarLocation | undefined;
35+
/**
36+
* The channel the avatar belongs to (if avatar of a channel is displayed)
37+
*/
38+
@Input() channel?: Channel<DefaultStreamChatGenerics>;
39+
/**
40+
* The user the avatar belongs to (if avatar of a user is displayed)
41+
*/
42+
@Input() user?: User<DefaultStreamChatGenerics>;
43+
/**
44+
* The type of the avatar: channel if channel avatar is displayed, user if user avatar is displayed
45+
*/
46+
@Input() type: AvatarType | undefined;
2447
isLoaded = false;
2548
isError = false;
2649

27-
constructor() {}
50+
constructor(private chatClientService: ChatClientService) {}
2851

2952
get initials() {
30-
return (this.name?.toString() || '').charAt(0);
53+
let result: string = '';
54+
if (this.type === 'user') {
55+
result = this.name?.toString() || '';
56+
} else if (this.type === 'channel') {
57+
if (this.channel?.data?.name) {
58+
result = this.channel?.data?.name;
59+
} else {
60+
const otherMembers = Object.values(
61+
this.channel?.state?.members || {}
62+
).filter(
63+
(m) => m.user_id !== this.chatClientService.chatClient.user!.id
64+
);
65+
if (otherMembers.length === 1) {
66+
result =
67+
otherMembers[0].user?.name || otherMembers[0].user?.name || '';
68+
} else {
69+
result = '#';
70+
}
71+
}
72+
}
73+
74+
return result.charAt(0) || '';
3175
}
3276
}

projects/stream-chat-angular/src/lib/channel-header/channel-header.component.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
<stream-avatar-placeholder
1010
imageUrl="{{ activeChannel?.data?.image }}"
1111
name="{{ avatarName }}"
12+
type="channel"
13+
location="channel-header"
14+
[channel]="activeChannel"
1215
></stream-avatar-placeholder>
1316
<div class="str-chat__header-livestream-left">
1417
<p data-testid="name" class="str-chat__header-livestream-left--title">

projects/stream-chat-angular/src/lib/channel-header/channel-header.component.spec.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
import { By } from '@angular/platform-browser';
23
import { TranslateModule } from '@ngx-translate/core';
34
import { Subject } from 'rxjs';
45
import { Channel, UserResponse } from 'stream-chat';
6+
import { AvatarPlaceholderComponent } from '../avatar-placeholder/avatar-placeholder.component';
57
import { ChannelService } from '../channel.service';
68
import { ChatClientService } from '../chat-client.service';
79
import { StreamI18nService } from '../stream-i18n.service';
10+
import { DefaultStreamChatGenerics } from '../types';
811
import { ChannelHeaderComponent } from './channel-header.component';
912

1013
describe('ChannelHeaderComponent', () => {
1114
let fixture: ComponentFixture<ChannelHeaderComponent>;
1215
let nativeElement: HTMLElement;
1316
let queryName: () => HTMLElement | null;
1417
let queryInfo: () => HTMLElement | null;
18+
let queryAvatar: () => AvatarPlaceholderComponent;
1519
let channelServiceMock: {
1620
activeChannel$: Subject<Channel>;
1721
};
@@ -28,7 +32,7 @@ describe('ChannelHeaderComponent', () => {
2832
};
2933
TestBed.configureTestingModule({
3034
imports: [TranslateModule.forRoot()],
31-
declarations: [ChannelHeaderComponent],
35+
declarations: [ChannelHeaderComponent, AvatarPlaceholderComponent],
3236
providers: [
3337
{ provide: ChannelService, useValue: channelServiceMock },
3438
{ provide: ChatClientService, useValue: chatClientServiceMock },
@@ -40,6 +44,9 @@ describe('ChannelHeaderComponent', () => {
4044
nativeElement = fixture.nativeElement as HTMLElement;
4145
queryName = () => nativeElement.querySelector('[data-testid="name"]');
4246
queryInfo = () => nativeElement.querySelector('[data-testid="info"]');
47+
queryAvatar = () =>
48+
fixture.debugElement.query(By.directive(AvatarPlaceholderComponent))
49+
.componentInstance as AvatarPlaceholderComponent;
4350
});
4451

4552
it('should display members count', () => {
@@ -87,4 +94,25 @@ describe('ChannelHeaderComponent', () => {
8794

8895
expect(queryInfo()?.textContent).not.toContain('5 online');
8996
});
97+
98+
it('should display avatar component', () => {
99+
const channel = {
100+
id: 'running-club',
101+
data: {
102+
name: 'Running club',
103+
image: 'url/to/img',
104+
},
105+
} as any as Channel;
106+
channelServiceMock.activeChannel$.next(channel);
107+
const avatar = queryAvatar();
108+
fixture.detectChanges();
109+
110+
expect(avatar.name).toBe('Running club');
111+
expect(avatar.imageUrl).toBe('url/to/img');
112+
expect(avatar.type).toBe('channel');
113+
expect(avatar.location).toBe('channel-header');
114+
expect(avatar.channel).toBe(
115+
channel as any as Channel<DefaultStreamChatGenerics>
116+
);
117+
});
90118
});

projects/stream-chat-angular/src/lib/channel-header/channel-header.component.ts

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,6 @@ export class ChannelHeaderComponent implements OnInit, OnDestroy {
8989
}
9090

9191
get avatarName() {
92-
if (this.activeChannel?.data?.name) {
93-
return this.activeChannel?.data?.name;
94-
}
95-
const otherMembers = Object.values(
96-
this.activeChannel?.state.members || {}
97-
).filter((m) => m.user_id !== this.chatClientService.chatClient.user!.id);
98-
if (otherMembers.length === 1) {
99-
return otherMembers[0].user?.name || otherMembers[0].user?.name;
100-
}
101-
return '#';
92+
return this.activeChannel?.data?.name;
10293
}
10394
}

projects/stream-chat-angular/src/lib/channel-preview/channel-preview.component.html

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,12 @@
77
>
88
<div class="str-chat__channel-preview-messenger--left">
99
<stream-avatar-placeholder
10-
imageUrl="{{ avatarImage }}"
1110
name="{{ avatarName }}"
11+
imageUrl="{{ avatarImage }}"
1212
[size]="40"
13+
type="channel"
14+
[channel]="channel"
15+
location="channel-preview"
1316
></stream-avatar-placeholder>
1417
</div>
1518
<div class="str-chat__channel-preview-messenger--right">

0 commit comments

Comments
 (0)