Skip to content

Commit a5ba9dc

Browse files
authored
Merge branch 'master' into simeonoff/theme-fixes
2 parents 376b9d4 + 5463c24 commit a5ba9dc

1 file changed

Lines changed: 217 additions & 4 deletions

File tree

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

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

0 commit comments

Comments
 (0)