Skip to content

Commit c8ed36d

Browse files
author
Konstantin Dinev
committed
test(chat): increasing test coverage
1 parent c950cdf commit c8ed36d

1 file changed

Lines changed: 213 additions & 2 deletions

File tree

projects/igniteui-angular/chat/src/chat.spec.ts

Lines changed: 213 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'
22
import { IgxChatComponent, IgxChatMessageContextDirective, type IgxChatTemplates } from './chat.component'
3-
import { Component, signal, TemplateRef, viewChild } from '@angular/core';
4-
import type { IgcChatComponent, IgcChatMessage, IgcTextareaComponent } from 'igniteui-webcomponents';
3+
import { Component, signal, TemplateRef, ViewRef, viewChild } from '@angular/core';
4+
import type { IgcChatComponent, IgcChatMessage, IgcChatMessageAttachment, IgcTextareaComponent } from 'igniteui-webcomponents';
55

66
describe('Chat wrapper', () => {
77

@@ -116,6 +116,217 @@ describe('Chat dynamic templates binding', () => {
116116
});
117117

118118

119+
describe('Chat _createTemplateRenderer context dispatch', () => {
120+
let fixture: ComponentFixture<IgxChatComponent>;
121+
let component: IgxChatComponent;
122+
let mockViewRef: { destroy: jasmine.Spy; rootNodes: Node[] };
123+
let mockTemplateRef: TemplateRef<any>;
124+
let createEmbeddedViewSpy: jasmine.Spy;
125+
126+
beforeEach(waitForAsync(() => {
127+
TestBed.configureTestingModule({
128+
imports: [IgxChatComponent]
129+
}).compileComponents();
130+
}));
131+
132+
beforeEach(() => {
133+
fixture = TestBed.createComponent(IgxChatComponent);
134+
component = fixture.componentInstance;
135+
fixture.detectChanges();
136+
137+
mockViewRef = { destroy: jasmine.createSpy('destroy'), rootNodes: ['mock-node'] as any };
138+
mockTemplateRef = {} as TemplateRef<any>;
139+
140+
createEmbeddedViewSpy = spyOn((component as any)._view, 'createEmbeddedView')
141+
.and.returnValue(mockViewRef as unknown as ViewRef);
142+
});
143+
144+
it('maps attachment render context (message + attachment) to attachment $implicit', () => {
145+
const attachment: IgcChatMessageAttachment = { id: 'a1', name: 'file.pdf' };
146+
const message: IgcChatMessage = { id: 'm1', sender: 'user', text: 'Hello' };
147+
148+
const renderer = (component as any)._createTemplateRenderer(mockTemplateRef);
149+
renderer({ message, attachment, instance: {} });
150+
151+
expect(createEmbeddedViewSpy).toHaveBeenCalledWith(
152+
mockTemplateRef,
153+
{ $implicit: attachment }
154+
);
155+
});
156+
157+
it('maps message render context (message only) to message $implicit', () => {
158+
const message: IgcChatMessage = { id: 'm1', sender: 'user', text: 'Hello' };
159+
160+
const renderer = (component as any)._createTemplateRenderer(mockTemplateRef);
161+
renderer({ message, instance: {} });
162+
163+
expect(createEmbeddedViewSpy).toHaveBeenCalledWith(
164+
mockTemplateRef,
165+
{ $implicit: message }
166+
);
167+
});
168+
169+
it('maps input render context (value) to value $implicit and attachments', () => {
170+
const value = 'typed text';
171+
const attachments: IgcChatMessageAttachment[] = [{ id: 'a1', name: 'image.png' }];
172+
173+
const renderer = (component as any)._createTemplateRenderer(mockTemplateRef);
174+
renderer({ value, attachments, instance: {} });
175+
176+
expect(createEmbeddedViewSpy).toHaveBeenCalledWith(
177+
mockTemplateRef,
178+
{ $implicit: value, attachments }
179+
);
180+
});
181+
182+
it('maps general render context (instance only) to instance $implicit', () => {
183+
const instance = {} as any;
184+
185+
const renderer = (component as any)._createTemplateRenderer(mockTemplateRef);
186+
renderer({ instance });
187+
188+
expect(createEmbeddedViewSpy).toHaveBeenCalledWith(
189+
mockTemplateRef,
190+
{ $implicit: { instance } }
191+
);
192+
});
193+
194+
it('returns root nodes from the created embedded view', () => {
195+
const message: IgcChatMessage = { id: 'm1', sender: 'user', text: 'Hello' };
196+
197+
const renderer = (component as any)._createTemplateRenderer(mockTemplateRef);
198+
const result = renderer({ message, instance: {} });
199+
200+
expect(result).toBe(mockViewRef.rootNodes);
201+
});
202+
203+
it('tracks created view refs in the internal refs map', () => {
204+
const message: IgcChatMessage = { id: 'm1', sender: 'user', text: 'Hello' };
205+
206+
const renderer = (component as any)._createTemplateRenderer(mockTemplateRef);
207+
renderer({ message, instance: {} });
208+
209+
const viewSet: Set<ViewRef> = (component as any)._templateViewRefs.get(mockTemplateRef);
210+
expect(viewSet).toBeDefined();
211+
expect(viewSet.has(mockViewRef as unknown as ViewRef)).toBeTrue();
212+
});
213+
214+
it('reuses the existing view set when the same template ref is passed again', () => {
215+
// First call creates a new Set and registers it in _templateViewRefs
216+
(component as any)._createTemplateRenderer(mockTemplateRef);
217+
const initialSet: Set<ViewRef> = (component as any)._templateViewRefs.get(mockTemplateRef);
218+
219+
// Second call with the same ref must reuse the existing Set, not create a new one
220+
(component as any)._createTemplateRenderer(mockTemplateRef);
221+
const reusedSet: Set<ViewRef> = (component as any)._templateViewRefs.get(mockTemplateRef);
222+
223+
expect(reusedSet).toBe(initialSet);
224+
});
225+
});
226+
227+
describe('Chat view lifecycle (_setTemplates)', () => {
228+
let fixture: ComponentFixture<IgxChatComponent>;
229+
let component: IgxChatComponent;
230+
let mockViewRef: { destroy: jasmine.Spy; rootNodes: Node[] };
231+
let mockTemplateRefA: TemplateRef<any>;
232+
let mockTemplateRefB: TemplateRef<any>;
233+
234+
beforeEach(waitForAsync(() => {
235+
TestBed.configureTestingModule({
236+
imports: [IgxChatComponent]
237+
}).compileComponents();
238+
}));
239+
240+
beforeEach(() => {
241+
fixture = TestBed.createComponent(IgxChatComponent);
242+
component = fixture.componentInstance;
243+
fixture.detectChanges();
244+
245+
mockViewRef = { destroy: jasmine.createSpy('destroy'), rootNodes: [] as any };
246+
mockTemplateRefA = {} as TemplateRef<any>;
247+
mockTemplateRefB = {} as TemplateRef<any>;
248+
249+
spyOn((component as any)._view, 'createEmbeddedView')
250+
.and.returnValue(mockViewRef as unknown as ViewRef);
251+
});
252+
253+
it('destroys old view refs when a template ref is replaced', () => {
254+
(component as any)._setTemplates({ messageContent: mockTemplateRefA });
255+
256+
const renderer = (component as any)._transformedTemplates().messageContent;
257+
renderer({ message: { id: '1', sender: 'user', text: 'Hi' }, instance: {} });
258+
259+
(component as any)._setTemplates({ messageContent: mockTemplateRefB });
260+
261+
expect(mockViewRef.destroy).toHaveBeenCalledTimes(1);
262+
});
263+
264+
it('does not destroy view refs when the same template ref is reused', () => {
265+
(component as any)._setTemplates({ messageContent: mockTemplateRefA });
266+
267+
const renderer = (component as any)._transformedTemplates().messageContent;
268+
renderer({ message: { id: '1', sender: 'user', text: 'Hi' }, instance: {} });
269+
270+
(component as any)._setTemplates({ messageContent: mockTemplateRefA });
271+
272+
expect(mockViewRef.destroy).not.toHaveBeenCalled();
273+
});
274+
275+
it('destroys view refs when a template key is removed from the new templates', () => {
276+
(component as any)._setTemplates({ messageContent: mockTemplateRefA });
277+
278+
const renderer = (component as any)._transformedTemplates().messageContent;
279+
renderer({ message: { id: '1', sender: 'user', text: 'Hi' }, instance: {} });
280+
281+
(component as any)._setTemplates({});
282+
283+
expect(mockViewRef.destroy).toHaveBeenCalledTimes(1);
284+
});
285+
286+
it('removes replaced template ref from the internal view refs map', () => {
287+
(component as any)._setTemplates({ messageContent: mockTemplateRefA });
288+
289+
const renderer = (component as any)._transformedTemplates().messageContent;
290+
renderer({ message: { id: '1', sender: 'user', text: 'Hi' }, instance: {} });
291+
292+
(component as any)._setTemplates({ messageContent: mockTemplateRefB });
293+
294+
expect((component as any)._templateViewRefs.has(mockTemplateRefA)).toBeFalse();
295+
});
296+
297+
it('does not create renderers for falsy template ref values', () => {
298+
(component as any)._setTemplates({ messageContent: undefined });
299+
300+
const transformedTemplates = (component as any)._transformedTemplates();
301+
expect(transformedTemplates.messageContent).toBeUndefined();
302+
});
303+
304+
it('sets transformed templates to empty object when given empty templates', () => {
305+
(component as any)._setTemplates({});
306+
307+
expect((component as any)._transformedTemplates()).toEqual({});
308+
});
309+
310+
it('destroys all tracked view refs on ngOnDestroy', () => {
311+
(component as any)._setTemplates({ messageContent: mockTemplateRefA });
312+
313+
const renderer = (component as any)._transformedTemplates().messageContent;
314+
renderer({ message: { id: '1', sender: 'user', text: 'Hi' }, instance: {} });
315+
316+
component.ngOnDestroy();
317+
318+
expect(mockViewRef.destroy).toHaveBeenCalled();
319+
});
320+
321+
it('clears the internal view refs map on ngOnDestroy', () => {
322+
(component as any)._setTemplates({ messageContent: mockTemplateRefA });
323+
324+
component.ngOnDestroy();
325+
326+
expect((component as any)._templateViewRefs.size).toBe(0);
327+
});
328+
});
329+
119330
@Component({
120331
template: `
121332
<igx-chat [messages]="messages()" [templates]="{messageContent: messageTemplate()}"/>

0 commit comments

Comments
 (0)