|
|
@ -1,7 +1,14 @@ |
|
|
import { AiAgentChatResponse } from '@ghostfolio/common/interfaces'; |
|
|
import { AiAgentChatResponse } from '@ghostfolio/common/interfaces'; |
|
|
import { DataService } from '@ghostfolio/ui/services'; |
|
|
import { DataService } from '@ghostfolio/ui/services'; |
|
|
|
|
|
|
|
|
import { ComponentFixture, TestBed } from '@angular/core/testing'; |
|
|
import { OverlayContainer } from '@angular/cdk/overlay'; |
|
|
|
|
|
import { |
|
|
|
|
|
ComponentFixture, |
|
|
|
|
|
TestBed, |
|
|
|
|
|
fakeAsync, |
|
|
|
|
|
tick |
|
|
|
|
|
} from '@angular/core/testing'; |
|
|
|
|
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations'; |
|
|
import { of, throwError } from 'rxjs'; |
|
|
import { of, throwError } from 'rxjs'; |
|
|
|
|
|
|
|
|
import { GfAiChatPanelComponent } from './ai-chat-panel.component'; |
|
|
import { GfAiChatPanelComponent } from './ai-chat-panel.component'; |
|
|
@ -77,6 +84,8 @@ describe('GfAiChatPanelComponent', () => { |
|
|
postAiChat: jest.Mock; |
|
|
postAiChat: jest.Mock; |
|
|
postAiChatFeedback: jest.Mock; |
|
|
postAiChatFeedback: jest.Mock; |
|
|
}; |
|
|
}; |
|
|
|
|
|
let overlayContainer: OverlayContainer; |
|
|
|
|
|
let overlayContainerElement: HTMLElement; |
|
|
|
|
|
|
|
|
beforeEach(async () => { |
|
|
beforeEach(async () => { |
|
|
localStorage.clear(); |
|
|
localStorage.clear(); |
|
|
@ -87,10 +96,13 @@ describe('GfAiChatPanelComponent', () => { |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
await TestBed.configureTestingModule({ |
|
|
await TestBed.configureTestingModule({ |
|
|
imports: [GfAiChatPanelComponent], |
|
|
imports: [GfAiChatPanelComponent, NoopAnimationsModule], |
|
|
providers: [{ provide: DataService, useValue: dataService }] |
|
|
providers: [{ provide: DataService, useValue: dataService }] |
|
|
}).compileComponents(); |
|
|
}).compileComponents(); |
|
|
|
|
|
|
|
|
|
|
|
overlayContainer = TestBed.inject(OverlayContainer); |
|
|
|
|
|
overlayContainerElement = overlayContainer.getContainerElement(); |
|
|
|
|
|
|
|
|
fixture = TestBed.createComponent(GfAiChatPanelComponent); |
|
|
fixture = TestBed.createComponent(GfAiChatPanelComponent); |
|
|
component = fixture.componentInstance; |
|
|
component = fixture.componentInstance; |
|
|
component.hasPermissionToReadAiPrompt = true; |
|
|
component.hasPermissionToReadAiPrompt = true; |
|
|
@ -99,6 +111,7 @@ describe('GfAiChatPanelComponent', () => { |
|
|
|
|
|
|
|
|
afterEach(() => { |
|
|
afterEach(() => { |
|
|
localStorage.clear(); |
|
|
localStorage.clear(); |
|
|
|
|
|
overlayContainer.ngOnDestroy(); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
it('sends a chat query and appends assistant response', () => { |
|
|
it('sends a chat query and appends assistant response', () => { |
|
|
@ -138,6 +151,51 @@ describe('GfAiChatPanelComponent', () => { |
|
|
).toHaveLength(2); |
|
|
).toHaveLength(2); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
it('shows diagnostics in info popover instead of inline message body', fakeAsync(() => { |
|
|
|
|
|
dataService.postAiChat.mockReturnValue( |
|
|
|
|
|
of( |
|
|
|
|
|
createChatResponse({ |
|
|
|
|
|
answer: 'You are concentrated in one position.', |
|
|
|
|
|
sessionId: 'session-details', |
|
|
|
|
|
turns: 1 |
|
|
|
|
|
}) |
|
|
|
|
|
) |
|
|
|
|
|
); |
|
|
|
|
|
component.query = 'Help me diversify'; |
|
|
|
|
|
|
|
|
|
|
|
component.onSubmit(); |
|
|
|
|
|
fixture.detectChanges(); |
|
|
|
|
|
tick(); |
|
|
|
|
|
fixture.detectChanges(); |
|
|
|
|
|
|
|
|
|
|
|
const nativeElement = fixture.nativeElement as HTMLElement; |
|
|
|
|
|
const chatLogText = nativeElement.querySelector('.chat-log')?.textContent ?? ''; |
|
|
|
|
|
|
|
|
|
|
|
expect(nativeElement.querySelector('.chat-metadata')).toBeNull(); |
|
|
|
|
|
expect(chatLogText).not.toContain('Confidence'); |
|
|
|
|
|
expect(chatLogText).not.toContain('Citations'); |
|
|
|
|
|
expect(chatLogText).not.toContain('Verification'); |
|
|
|
|
|
|
|
|
|
|
|
const detailsTrigger = nativeElement.querySelector( |
|
|
|
|
|
'.chat-details-trigger' |
|
|
|
|
|
) as HTMLButtonElement | null; |
|
|
|
|
|
|
|
|
|
|
|
expect(detailsTrigger).toBeTruthy(); |
|
|
|
|
|
|
|
|
|
|
|
detailsTrigger?.click(); |
|
|
|
|
|
fixture.detectChanges(); |
|
|
|
|
|
tick(); |
|
|
|
|
|
fixture.detectChanges(); |
|
|
|
|
|
|
|
|
|
|
|
const overlayText = overlayContainerElement.textContent ?? ''; |
|
|
|
|
|
|
|
|
|
|
|
expect(overlayText).toContain('Confidence'); |
|
|
|
|
|
expect(overlayText).toContain('Citations'); |
|
|
|
|
|
expect(overlayText).toContain('Verification'); |
|
|
|
|
|
expect(overlayText).toContain('2 holdings analyzed'); |
|
|
|
|
|
expect(overlayText).toContain('market_data_coverage'); |
|
|
|
|
|
})); |
|
|
|
|
|
|
|
|
it('reuses session id across consecutive prompts', () => { |
|
|
it('reuses session id across consecutive prompts', () => { |
|
|
dataService.postAiChat |
|
|
dataService.postAiChat |
|
|
.mockReturnValueOnce( |
|
|
.mockReturnValueOnce( |
|
|
|