import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http' import { provideHttpClientTesting } from '@angular/common/http/testing' import { ComponentFixture, TestBed } from '@angular/core/testing' import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons' import { Subject } from 'rxjs' import { Toast, ToastLevel, ToastService } from 'src/app/services/toast.service' import { ToastsDropdownComponent } from './toasts-dropdown.component' const toasts = [ { id: 'abc-123', content: 'foo bar', delay: 5000, }, { id: 'def-123', content: 'Error 1 content', delay: 5000, error: 'Error 1 string', }, { id: 'ghi-123', content: 'Error 2 content', delay: 5000, error: { url: 'https://example.com', status: 500, statusText: 'Internal Server Error', message: 'Internal server error 500 message', error: { detail: 'Error 2 message details' }, }, }, ] describe('ToastsDropdownComponent', () => { let component: ToastsDropdownComponent let fixture: ComponentFixture let toastService: ToastService let toastsSubject: Subject let getToastsSpy: jest.SpyInstance beforeEach(async () => { TestBed.configureTestingModule({ imports: [ ToastsDropdownComponent, NgxBootstrapIconsModule.pick(allIcons), ], providers: [ provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting(), ], }).compileComponents() toastService = TestBed.inject(ToastService) toastsSubject = new Subject() getToastsSpy = jest .spyOn(toastService, 'getToasts') .mockReturnValue(toastsSubject) fixture = TestBed.createComponent(ToastsDropdownComponent) component = fixture.componentInstance fixture.detectChanges() }) it('should call getToasts and return toasts', () => { toastsSubject.next(toasts) fixture.detectChanges() expect(getToastsSpy).toHaveBeenCalled() expect(component.toasts()).toContainEqual({ id: 'abc-123', content: 'foo bar', delay: 5000, }) }) it('should show a toast', () => { toastsSubject.next(toasts) fixture.detectChanges() expect(fixture.nativeElement.textContent).toContain('foo bar') }) it('should toggle suppressPopupToasts', () => { fixture.detectChanges() toastsSubject.next(toasts) const spy = jest.spyOn(toastService, 'suppressPopupToasts', 'set') component.onOpenChange(true) expect(spy).toHaveBeenCalledWith(true) }) it('should badge unread toasts, red when one is an error', () => { toastsSubject.next([ { id: 'a', content: 'Read', delay: 5000, read: true }, { id: 'b', content: 'Unread', delay: 5000, read: false }, ]) fixture.detectChanges() let badge: HTMLElement = fixture.nativeElement.querySelector( '.notification-count' ) expect(badge.textContent.trim()).toEqual('1') expect(badge.classList).toContain('bg-secondary') toastsSubject.next([ { id: 'c', content: 'Failed', delay: 5000, level: ToastLevel.Error }, ]) fixture.detectChanges() badge = fixture.nativeElement.querySelector('.notification-count') expect(badge.classList).toContain('bg-danger') toastsSubject.next([{ id: 'd', content: 'Seen', delay: 5000, read: true }]) fixture.detectChanges() expect( fixture.nativeElement.querySelector('.notification-count') ).toBeNull() }) it('should mark all toasts read when opened', () => { const markSpy = jest.spyOn(toastService, 'markAllRead') component.onOpenChange(false) expect(markSpy).not.toHaveBeenCalled() component.onOpenChange(true) expect(markSpy).toHaveBeenCalled() }) it('should tick the clock only while open', () => { jest.useFakeTimers() component.onOpenChange(true) const opened = component.now() jest.advanceTimersByTime(30000) const ticked = component.now() expect(ticked).not.toBe(opened) component.onOpenChange(false) jest.advanceTimersByTime(60000) expect(component.now()).toBe(ticked) jest.useRealTimers() }) it('should mark toasts that were unread when opened as new', () => { toastsSubject.next([ { id: 'a', content: 'Seen', delay: 5000, read: true }, { id: 'b', content: 'Unseen', delay: 5000, read: false }, ]) fixture.detectChanges() component.onOpenChange(true) expect([...component.newIds()]).toEqual(['b']) component.onOpenChange(false) expect(component.newIds().size).toEqual(0) }) it('should clear the toast and close the menu when an action is used', () => { const toast = { id: 'a', content: 'Moved', delay: 5000 } const closeToastSpy = jest.spyOn(toastService, 'closeToast') const closeSpy = jest.spyOn(component.dropdown, 'close') component.onActioned(toast) expect(closeToastSpy).toHaveBeenCalledWith(toast) expect(closeSpy).toHaveBeenCalled() }) })