mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-11-03 19:17:13 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			363 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			363 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import { CdkDragDrop, DragDropModule } from '@angular/cdk/drag-drop'
 | 
						|
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
 | 
						|
import {
 | 
						|
  HttpTestingController,
 | 
						|
  provideHttpClientTesting,
 | 
						|
} from '@angular/common/http/testing'
 | 
						|
import {
 | 
						|
  ComponentFixture,
 | 
						|
  TestBed,
 | 
						|
  fakeAsync,
 | 
						|
  tick,
 | 
						|
} from '@angular/core/testing'
 | 
						|
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
 | 
						|
import { BrowserModule } from '@angular/platform-browser'
 | 
						|
import { ActivatedRoute, Router } from '@angular/router'
 | 
						|
import { RouterTestingModule } from '@angular/router/testing'
 | 
						|
import { NgbModal, NgbModalModule, NgbModule } from '@ng-bootstrap/ng-bootstrap'
 | 
						|
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
 | 
						|
import { of, throwError } from 'rxjs'
 | 
						|
import { routes } from 'src/app/app-routing.module'
 | 
						|
import { SavedView } from 'src/app/data/saved-view'
 | 
						|
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
 | 
						|
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
 | 
						|
import { PermissionsGuard } from 'src/app/guards/permissions.guard'
 | 
						|
import {
 | 
						|
  DjangoMessageLevel,
 | 
						|
  DjangoMessagesService,
 | 
						|
} from 'src/app/services/django-messages.service'
 | 
						|
import { OpenDocumentsService } from 'src/app/services/open-documents.service'
 | 
						|
import { PermissionsService } from 'src/app/services/permissions.service'
 | 
						|
import { RemoteVersionService } from 'src/app/services/rest/remote-version.service'
 | 
						|
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
 | 
						|
import { SearchService } from 'src/app/services/rest/search.service'
 | 
						|
import { SettingsService } from 'src/app/services/settings.service'
 | 
						|
import { ToastService } from 'src/app/services/toast.service'
 | 
						|
import { environment } from 'src/environments/environment'
 | 
						|
import { ProfileEditDialogComponent } from '../common/profile-edit-dialog/profile-edit-dialog.component'
 | 
						|
import { DocumentDetailComponent } from '../document-detail/document-detail.component'
 | 
						|
import { AppFrameComponent } from './app-frame.component'
 | 
						|
import { GlobalSearchComponent } from './global-search/global-search.component'
 | 
						|
 | 
						|
const saved_views = [
 | 
						|
  {
 | 
						|
    name: 'Saved View 0',
 | 
						|
    id: 0,
 | 
						|
    show_on_dashboard: true,
 | 
						|
    show_in_sidebar: true,
 | 
						|
    sort_field: 'name',
 | 
						|
    sort_reverse: true,
 | 
						|
    filter_rules: [],
 | 
						|
  },
 | 
						|
  {
 | 
						|
    name: 'Saved View 1',
 | 
						|
    id: 1,
 | 
						|
    show_on_dashboard: false,
 | 
						|
    show_in_sidebar: false,
 | 
						|
    sort_field: 'name',
 | 
						|
    sort_reverse: true,
 | 
						|
    filter_rules: [],
 | 
						|
  },
 | 
						|
  {
 | 
						|
    name: 'Saved View 2',
 | 
						|
    id: 2,
 | 
						|
    show_on_dashboard: true,
 | 
						|
    show_in_sidebar: true,
 | 
						|
    sort_field: 'name',
 | 
						|
    sort_reverse: true,
 | 
						|
    filter_rules: [],
 | 
						|
  },
 | 
						|
  {
 | 
						|
    name: 'Saved View 3',
 | 
						|
    id: 3,
 | 
						|
    show_on_dashboard: true,
 | 
						|
    show_in_sidebar: true,
 | 
						|
    sort_field: 'name',
 | 
						|
    sort_reverse: true,
 | 
						|
    filter_rules: [],
 | 
						|
  },
 | 
						|
]
 | 
						|
const document = { id: 2, title: 'Hello world' }
 | 
						|
 | 
						|
describe('AppFrameComponent', () => {
 | 
						|
  let component: AppFrameComponent
 | 
						|
  let fixture: ComponentFixture<AppFrameComponent>
 | 
						|
  let httpTestingController: HttpTestingController
 | 
						|
  let settingsService: SettingsService
 | 
						|
  let permissionsService: PermissionsService
 | 
						|
  let remoteVersionService: RemoteVersionService
 | 
						|
  let toastService: ToastService
 | 
						|
  let messagesService: DjangoMessagesService
 | 
						|
  let openDocumentsService: OpenDocumentsService
 | 
						|
  let router: Router
 | 
						|
  let savedViewSpy
 | 
						|
  let modalService: NgbModal
 | 
						|
 | 
						|
  beforeEach(async () => {
 | 
						|
    TestBed.configureTestingModule({
 | 
						|
      imports: [
 | 
						|
        BrowserModule,
 | 
						|
        RouterTestingModule.withRoutes(routes),
 | 
						|
        NgbModule,
 | 
						|
        FormsModule,
 | 
						|
        ReactiveFormsModule,
 | 
						|
        DragDropModule,
 | 
						|
        NgbModalModule,
 | 
						|
        NgxBootstrapIconsModule.pick(allIcons),
 | 
						|
        AppFrameComponent,
 | 
						|
        IfPermissionsDirective,
 | 
						|
        GlobalSearchComponent,
 | 
						|
      ],
 | 
						|
      providers: [
 | 
						|
        SettingsService,
 | 
						|
        {
 | 
						|
          provide: SavedViewService,
 | 
						|
          useValue: {
 | 
						|
            reload: () => {},
 | 
						|
            listAll: () =>
 | 
						|
              of({
 | 
						|
                all: [saved_views.map((v) => v.id)],
 | 
						|
                count: saved_views.length,
 | 
						|
                results: saved_views,
 | 
						|
              }),
 | 
						|
            sidebarViews: saved_views.filter((v) => v.show_in_sidebar),
 | 
						|
          },
 | 
						|
        },
 | 
						|
        PermissionsService,
 | 
						|
        RemoteVersionService,
 | 
						|
        IfPermissionsDirective,
 | 
						|
        ToastService,
 | 
						|
        DjangoMessagesService,
 | 
						|
        OpenDocumentsService,
 | 
						|
        SearchService,
 | 
						|
        NgbModal,
 | 
						|
        {
 | 
						|
          provide: ActivatedRoute,
 | 
						|
          useValue: {
 | 
						|
            firstChild: {
 | 
						|
              component: DocumentDetailComponent,
 | 
						|
            },
 | 
						|
            snapshot: {
 | 
						|
              firstChild: {
 | 
						|
                component: DocumentDetailComponent,
 | 
						|
                params: {
 | 
						|
                  id: document.id,
 | 
						|
                },
 | 
						|
              },
 | 
						|
            },
 | 
						|
          },
 | 
						|
        },
 | 
						|
        PermissionsGuard,
 | 
						|
        provideHttpClient(withInterceptorsFromDi()),
 | 
						|
        provideHttpClientTesting(),
 | 
						|
      ],
 | 
						|
    }).compileComponents()
 | 
						|
 | 
						|
    settingsService = TestBed.inject(SettingsService)
 | 
						|
    const savedViewService = TestBed.inject(SavedViewService)
 | 
						|
    permissionsService = TestBed.inject(PermissionsService)
 | 
						|
    remoteVersionService = TestBed.inject(RemoteVersionService)
 | 
						|
    toastService = TestBed.inject(ToastService)
 | 
						|
    messagesService = TestBed.inject(DjangoMessagesService)
 | 
						|
    openDocumentsService = TestBed.inject(OpenDocumentsService)
 | 
						|
    modalService = TestBed.inject(NgbModal)
 | 
						|
    router = TestBed.inject(Router)
 | 
						|
 | 
						|
    jest
 | 
						|
      .spyOn(settingsService, 'displayName', 'get')
 | 
						|
      .mockReturnValue('Hello World')
 | 
						|
    jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
 | 
						|
 | 
						|
    savedViewSpy = jest.spyOn(savedViewService, 'reload')
 | 
						|
 | 
						|
    fixture = TestBed.createComponent(AppFrameComponent)
 | 
						|
    component = fixture.componentInstance
 | 
						|
 | 
						|
    httpTestingController = TestBed.inject(HttpTestingController)
 | 
						|
 | 
						|
    fixture.detectChanges()
 | 
						|
  })
 | 
						|
 | 
						|
  it('should initialize the saved view service', () => {
 | 
						|
    expect(savedViewSpy).toHaveBeenCalled()
 | 
						|
  })
 | 
						|
 | 
						|
  it('should check for update if enabled', () => {
 | 
						|
    const updateCheckSpy = jest.spyOn(remoteVersionService, 'checkForUpdates')
 | 
						|
    updateCheckSpy.mockImplementation(() => {
 | 
						|
      return of({
 | 
						|
        version: 'v100.0',
 | 
						|
        update_available: true,
 | 
						|
      })
 | 
						|
    })
 | 
						|
    settingsService.set(SETTINGS_KEYS.UPDATE_CHECKING_ENABLED, true)
 | 
						|
    component.ngOnInit()
 | 
						|
    expect(updateCheckSpy).toHaveBeenCalled()
 | 
						|
    fixture.detectChanges()
 | 
						|
    expect(fixture.nativeElement.textContent).toContain('Update available')
 | 
						|
  })
 | 
						|
 | 
						|
  it('should check not for update if disabled', () => {
 | 
						|
    const updateCheckSpy = jest.spyOn(remoteVersionService, 'checkForUpdates')
 | 
						|
    settingsService.set(SETTINGS_KEYS.UPDATE_CHECKING_ENABLED, false)
 | 
						|
    component.ngOnInit()
 | 
						|
    fixture.detectChanges()
 | 
						|
    expect(updateCheckSpy).not.toHaveBeenCalled()
 | 
						|
    expect(fixture.nativeElement.textContent).not.toContain('Update available')
 | 
						|
  })
 | 
						|
 | 
						|
  it('should check for update if was disabled and then enabled', () => {
 | 
						|
    const updateCheckSpy = jest.spyOn(remoteVersionService, 'checkForUpdates')
 | 
						|
    settingsService.set(SETTINGS_KEYS.UPDATE_CHECKING_ENABLED, false)
 | 
						|
    component.setUpdateChecking(true)
 | 
						|
    fixture.detectChanges()
 | 
						|
    expect(updateCheckSpy).toHaveBeenCalled()
 | 
						|
  })
 | 
						|
 | 
						|
  it('should show error on toggle update checking if store settings fails', () => {
 | 
						|
    jest.spyOn(console, 'warn').mockImplementation(() => {})
 | 
						|
    const toastSpy = jest.spyOn(toastService, 'showError')
 | 
						|
    settingsService.set(SETTINGS_KEYS.UPDATE_CHECKING_ENABLED, false)
 | 
						|
    component.setUpdateChecking(true)
 | 
						|
    httpTestingController
 | 
						|
      .expectOne(`${environment.apiBaseUrl}ui_settings/`)
 | 
						|
      .flush('error', {
 | 
						|
        status: 500,
 | 
						|
        statusText: 'error',
 | 
						|
      })
 | 
						|
    expect(toastSpy).toHaveBeenCalled()
 | 
						|
  })
 | 
						|
 | 
						|
  it('should support toggling slim sidebar and saving', fakeAsync(() => {
 | 
						|
    const saveSettingSpy = jest.spyOn(settingsService, 'set')
 | 
						|
    expect(component.slimSidebarEnabled).toBeFalsy()
 | 
						|
    expect(component.slimSidebarAnimating).toBeFalsy()
 | 
						|
    component.toggleSlimSidebar()
 | 
						|
    expect(component.slimSidebarAnimating).toBeTruthy()
 | 
						|
    tick(200)
 | 
						|
    expect(component.slimSidebarAnimating).toBeFalsy()
 | 
						|
    expect(component.slimSidebarEnabled).toBeTruthy()
 | 
						|
    expect(saveSettingSpy).toHaveBeenCalledWith(
 | 
						|
      SETTINGS_KEYS.SLIM_SIDEBAR,
 | 
						|
      true
 | 
						|
    )
 | 
						|
  }))
 | 
						|
 | 
						|
  it('should show error on toggle slim sidebar if store settings fails', () => {
 | 
						|
    jest.spyOn(console, 'warn').mockImplementation(() => {})
 | 
						|
    const toastSpy = jest.spyOn(toastService, 'showError')
 | 
						|
    component.toggleSlimSidebar()
 | 
						|
    httpTestingController
 | 
						|
      .expectOne(`${environment.apiBaseUrl}ui_settings/`)
 | 
						|
      .flush('error', {
 | 
						|
        status: 500,
 | 
						|
        statusText: 'error',
 | 
						|
      })
 | 
						|
    expect(toastSpy).toHaveBeenCalled()
 | 
						|
  })
 | 
						|
 | 
						|
  it('should support collapsible menu', () => {
 | 
						|
    const button: HTMLButtonElement = (
 | 
						|
      fixture.nativeElement as HTMLDivElement
 | 
						|
    ).querySelector('button[data-toggle=collapse]')
 | 
						|
    button.dispatchEvent(new MouseEvent('click'))
 | 
						|
    expect(component.isMenuCollapsed).toBeFalsy()
 | 
						|
    component.closeMenu()
 | 
						|
    expect(component.isMenuCollapsed).toBeTruthy()
 | 
						|
  })
 | 
						|
 | 
						|
  it('should support close document & navigate on close current doc', () => {
 | 
						|
    const closeSpy = jest.spyOn(openDocumentsService, 'closeDocument')
 | 
						|
    closeSpy.mockReturnValue(of(true))
 | 
						|
    const routerSpy = jest.spyOn(router, 'navigate')
 | 
						|
    component.closeDocument(document)
 | 
						|
    expect(closeSpy).toHaveBeenCalledWith(document)
 | 
						|
    expect(routerSpy).toHaveBeenCalled()
 | 
						|
  })
 | 
						|
 | 
						|
  it('should support close all documents & navigate on close current doc', () => {
 | 
						|
    const closeAllSpy = jest.spyOn(openDocumentsService, 'closeAll')
 | 
						|
    closeAllSpy.mockReturnValue(of(true))
 | 
						|
    const routerSpy = jest.spyOn(router, 'navigate')
 | 
						|
    component.closeAll()
 | 
						|
    expect(closeAllSpy).toHaveBeenCalled()
 | 
						|
    expect(routerSpy).toHaveBeenCalled()
 | 
						|
  })
 | 
						|
 | 
						|
  it('should close all documents on logout', () => {
 | 
						|
    const closeAllSpy = jest.spyOn(openDocumentsService, 'closeAll')
 | 
						|
    component.onLogout()
 | 
						|
    expect(closeAllSpy).toHaveBeenCalled()
 | 
						|
  })
 | 
						|
 | 
						|
  it('should warn before close if dirty documents', () => {
 | 
						|
    jest.spyOn(openDocumentsService, 'hasDirty').mockReturnValue(true)
 | 
						|
    expect(component.canDeactivate()).toBeFalsy()
 | 
						|
  })
 | 
						|
 | 
						|
  it('should disable global dropzone on start drag + drop, re-enable after', () => {
 | 
						|
    expect(settingsService.globalDropzoneEnabled).toBeTruthy()
 | 
						|
    component.onDragStart(null)
 | 
						|
    expect(settingsService.globalDropzoneEnabled).toBeFalsy()
 | 
						|
    component.onDragEnd(null)
 | 
						|
    expect(settingsService.globalDropzoneEnabled).toBeTruthy()
 | 
						|
  })
 | 
						|
 | 
						|
  it('should update saved view sorting on drag + drop, show info', () => {
 | 
						|
    const settingsSpy = jest.spyOn(settingsService, 'updateSidebarViewsSort')
 | 
						|
    const toastSpy = jest.spyOn(toastService, 'showInfo')
 | 
						|
    jest.spyOn(settingsService, 'storeSettings').mockReturnValue(of(true))
 | 
						|
    component.onDrop({ previousIndex: 0, currentIndex: 1 } as CdkDragDrop<
 | 
						|
      SavedView[]
 | 
						|
    >)
 | 
						|
    expect(settingsSpy).toHaveBeenCalledWith([
 | 
						|
      saved_views[2],
 | 
						|
      saved_views[0],
 | 
						|
      saved_views[3],
 | 
						|
    ])
 | 
						|
    expect(toastSpy).toHaveBeenCalled()
 | 
						|
  })
 | 
						|
 | 
						|
  it('should update saved view sorting on drag + drop, show error', () => {
 | 
						|
    jest.spyOn(settingsService, 'get').mockImplementation((key) => {
 | 
						|
      if (key === SETTINGS_KEYS.SIDEBAR_VIEWS_SORT_ORDER) return []
 | 
						|
    })
 | 
						|
    fixture.destroy()
 | 
						|
    fixture = TestBed.createComponent(AppFrameComponent)
 | 
						|
    component = fixture.componentInstance
 | 
						|
    fixture.detectChanges()
 | 
						|
    const toastSpy = jest.spyOn(toastService, 'showError')
 | 
						|
    jest
 | 
						|
      .spyOn(settingsService, 'storeSettings')
 | 
						|
      .mockReturnValue(throwError(() => new Error('unable to save')))
 | 
						|
    component.onDrop({ previousIndex: 0, currentIndex: 2 } as CdkDragDrop<
 | 
						|
      SavedView[]
 | 
						|
    >)
 | 
						|
    expect(toastSpy).toHaveBeenCalled()
 | 
						|
  })
 | 
						|
 | 
						|
  it('should support edit profile', () => {
 | 
						|
    const modalSpy = jest.spyOn(modalService, 'open')
 | 
						|
    component.editProfile()
 | 
						|
    expect(modalSpy).toHaveBeenCalledWith(ProfileEditDialogComponent, {
 | 
						|
      backdrop: 'static',
 | 
						|
      size: 'xl',
 | 
						|
    })
 | 
						|
  })
 | 
						|
 | 
						|
  it('should show toasts for django messages', () => {
 | 
						|
    const toastErrorSpy = jest.spyOn(toastService, 'showError')
 | 
						|
    const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
 | 
						|
    jest.spyOn(messagesService, 'get').mockReturnValue([
 | 
						|
      { level: DjangoMessageLevel.WARNING, message: 'Test warning' },
 | 
						|
      { level: DjangoMessageLevel.ERROR, message: 'Test error' },
 | 
						|
      { level: DjangoMessageLevel.SUCCESS, message: 'Test success' },
 | 
						|
      { level: DjangoMessageLevel.INFO, message: 'Test info' },
 | 
						|
      { level: DjangoMessageLevel.DEBUG, message: 'Test debug' },
 | 
						|
    ])
 | 
						|
    component.ngOnInit()
 | 
						|
    expect(toastErrorSpy).toHaveBeenCalledTimes(2)
 | 
						|
    expect(toastInfoSpy).toHaveBeenCalledTimes(3)
 | 
						|
  })
 | 
						|
})
 |