shamoon 06def8c11e frontend unit tests
toasts component testing

conditional import of angular setup-jest for vscode-jest support

Update jest.config.js

Create open-documents.service.spec.ts

Add unit tests for all REST services

settings service test

Remove component from settings service test

Create permissions.service.spec.ts

upload documents service tests

Update package.json

Create toast.service.spec.ts

Tasks service test

Statistics widget component tests

Update permissions.service.ts

Create app.component.spec.ts

settings component testing

tasks component unit testing

Management list component generic tests

Some management component tests

document notes component unit tests

Create document-list.component.spec.ts

Create save-view-config-dialog.component.spec.ts

Create filter-editor.component.spec.ts

small and large document cards unit testing

Create bulk-editor.component.spec.ts

document detail unit tests

saving work on documentdetail component spec

Create document-asn.component.spec.ts

dashboard & widgets unit testing

Fix ResizeObserver mock

common component unit tests

fix some merge errors

Update app-frame.component.spec.ts

Create page-header.component.spec.ts

input component unit tests

FilterableDropdownComponent unit testing

and found minor errors

update taskservice unit tests

Edit dialogs unit tests

Create date-dropdown.component.spec.ts

Remove selectors from guard tests

confirm dialog component tests

app frame component test

Miscellaneous component tests

Update document-list-view.service.spec.ts

directives unit tests

Remove unused resizeobserver mock

guard unit tests

Update query-params.spec.ts

try to fix flaky playwright

filter rules utils & testing

Interceptor unit tests

Pipes unit testing

Utils unit tests

Update upload-documents.service.spec.ts

consumer status service tests

Update setup-jest.ts

Create document-list-view.service.spec.ts

Update app-routing.module.ts
2023-06-15 23:53:04 -07:00

592 lines
21 KiB
TypeScript

import { ComponentFixture, TestBed } from '@angular/core/testing'
import { DocumentListComponent } from './document-list.component'
import { HttpClientTestingModule } from '@angular/common/http/testing'
import { RouterTestingModule } from '@angular/router/testing'
import { routes } from 'src/app/app-routing.module'
import { FilterEditorComponent } from './filter-editor/filter-editor.component'
import { PermissionsFilterDropdownComponent } from '../common/permissions-filter-dropdown/permissions-filter-dropdown.component'
import { DateDropdownComponent } from '../common/date-dropdown/date-dropdown.component'
import { FilterableDropdownComponent } from '../common/filterable-dropdown/filterable-dropdown.component'
import { PageHeaderComponent } from '../common/page-header/page-header.component'
import { BulkEditorComponent } from './bulk-editor/bulk-editor.component'
import { FilterPipe } from 'src/app/pipes/filter.pipe'
import {
NgbDatepickerModule,
NgbDropdown,
NgbDropdownItem,
NgbDropdownModule,
NgbModal,
NgbModalRef,
NgbPagination,
NgbPopoverModule,
NgbTooltipModule,
} from '@ng-bootstrap/ng-bootstrap'
import { ClearableBadgeComponent } from '../common/clearable-badge/clearable-badge.component'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { ToggleableDropdownButtonComponent } from '../common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component'
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
import { DatePipe } from '@angular/common'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import {
ConsumerStatusService,
FileStatus,
} from 'src/app/services/consumer-status.service'
import { Subject, of, throwError } from 'rxjs'
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
import { ActivatedRoute, Router, convertToParamMap } from '@angular/router'
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view'
import {
FILTER_FULLTEXT_MORELIKE,
FILTER_FULLTEXT_QUERY,
FILTER_HAS_TAGS_ANY,
} from 'src/app/data/filter-rule-type'
import { By } from '@angular/platform-browser'
import { SortableDirective } from 'src/app/directives/sortable.directive'
import { ToastService } from 'src/app/services/toast.service'
import { DocumentCardSmallComponent } from './document-card-small/document-card-small.component'
import { DocumentCardLargeComponent } from './document-card-large/document-card-large.component'
import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
import { UsernamePipe } from 'src/app/pipes/username.pipe'
import { PaperlessDocument } from 'src/app/data/paperless-document'
import {
DOCUMENT_SORT_FIELDS,
DOCUMENT_SORT_FIELDS_FULLTEXT,
DocumentService,
} from 'src/app/services/rest/document.service'
import { ConfirmDialogComponent } from '../common/confirm-dialog/confirm-dialog.component'
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
import { SaveViewConfigDialogComponent } from './save-view-config-dialog/save-view-config-dialog.component'
import { TextComponent } from '../common/input/text/text.component'
import { CheckComponent } from '../common/input/check/check.component'
import { HttpErrorResponse } from '@angular/common/http'
import { PermissionsGuard } from 'src/app/guards/permissions.guard'
import { SettingsService } from 'src/app/services/settings.service'
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
const docs: PaperlessDocument[] = [
{
id: 1,
title: 'Doc1',
notes: [],
tags$: new Subject(),
content: 'document content 1',
},
{
id: 2,
title: 'Doc2',
notes: [],
tags$: new Subject(),
content: 'document content 2',
},
{
id: 3,
title: 'Doc3',
notes: [],
tags$: new Subject(),
content: 'document content 3',
},
]
describe('DocumentListComponent', () => {
let component: DocumentListComponent
let fixture: ComponentFixture<DocumentListComponent>
let documentListService: DocumentListViewService
let documentService: DocumentService
let consumerStatusService: ConsumerStatusService
let savedViewService: SavedViewService
let router: Router
let activatedRoute: ActivatedRoute
let toastService: ToastService
let modalService: NgbModal
let settingsService: SettingsService
beforeEach(async () => {
TestBed.configureTestingModule({
declarations: [
DocumentListComponent,
PageHeaderComponent,
FilterEditorComponent,
FilterableDropdownComponent,
DateDropdownComponent,
PermissionsFilterDropdownComponent,
ToggleableDropdownButtonComponent,
BulkEditorComponent,
ClearableBadgeComponent,
DocumentCardSmallComponent,
DocumentCardLargeComponent,
ConfirmDialogComponent,
SaveViewConfigDialogComponent,
TextComponent,
CheckComponent,
IfPermissionsDirective,
FilterPipe,
CustomDatePipe,
SortableDirective,
DocumentTitlePipe,
UsernamePipe,
SafeHtmlPipe,
],
providers: [
FilterPipe,
CustomDatePipe,
DatePipe,
DocumentTitlePipe,
UsernamePipe,
SafeHtmlPipe,
PermissionsGuard,
],
imports: [
HttpClientTestingModule,
RouterTestingModule.withRoutes(routes),
FormsModule,
ReactiveFormsModule,
NgbDropdownModule,
NgbDatepickerModule,
NgbPopoverModule,
NgbTooltipModule,
],
}).compileComponents()
documentListService = TestBed.inject(DocumentListViewService)
documentService = TestBed.inject(DocumentService)
consumerStatusService = TestBed.inject(ConsumerStatusService)
savedViewService = TestBed.inject(SavedViewService)
router = TestBed.inject(Router)
activatedRoute = TestBed.inject(ActivatedRoute)
toastService = TestBed.inject(ToastService)
modalService = TestBed.inject(NgbModal)
settingsService = TestBed.inject(SettingsService)
fixture = TestBed.createComponent(DocumentListComponent)
component = fixture.componentInstance
})
it('should load display mode from local storage', () => {
window.localStorage.setItem('document-list:displayMode', 'largeCards')
fixture.detectChanges()
expect(component.displayMode).toEqual('largeCards')
component.displayMode = 'smallCards'
component.saveDisplayMode()
expect(window.localStorage.getItem('document-list:displayMode')).toEqual(
'smallCards'
)
})
it('should reload on new document consumed', () => {
const reloadSpy = jest.spyOn(documentListService, 'reload')
const fileStatusSubject = new Subject<FileStatus>()
jest
.spyOn(consumerStatusService, 'onDocumentConsumptionFinished')
.mockReturnValue(fileStatusSubject)
fixture.detectChanges()
fileStatusSubject.next(new FileStatus())
expect(reloadSpy).toHaveBeenCalled()
})
it('should show score sort fields on fulltext queries', () => {
documentListService.filterRules = [
{
rule_type: FILTER_HAS_TAGS_ANY,
value: '10',
},
]
fixture.detectChanges()
expect(component.getSortFields()).toEqual(DOCUMENT_SORT_FIELDS)
documentListService.filterRules = [
{
rule_type: FILTER_FULLTEXT_QUERY,
value: 'foo',
},
]
fixture.detectChanges()
expect(component.getSortFields()).toEqual(DOCUMENT_SORT_FIELDS_FULLTEXT)
})
it('should determine if filtered, support reset', () => {
fixture.detectChanges()
documentListService.filterRules = [
{
rule_type: FILTER_HAS_TAGS_ANY,
value: '10',
},
]
documentListService.isReloading = false
fixture.detectChanges()
expect(component.isFiltered).toBeTruthy()
expect(fixture.nativeElement.textContent.match(/Reset/g)).toHaveLength(2)
component.resetFilters()
fixture.detectChanges()
expect(fixture.nativeElement.textContent.match(/Reset/g)).toHaveLength(1)
})
it('should load saved view from URL', () => {
const view: PaperlessSavedView = {
id: 10,
sort_field: 'added',
sort_reverse: true,
filter_rules: [
{
rule_type: FILTER_HAS_TAGS_ANY,
value: '20',
},
],
}
const queryParams = { id: view.id.toString() }
const getSavedViewSpy = jest.spyOn(savedViewService, 'getCached')
getSavedViewSpy.mockReturnValue(of(view))
const activateSavedViewSpy = jest.spyOn(
documentListService,
'activateSavedViewWithQueryParams'
)
activateSavedViewSpy.mockImplementation((view, params) => {})
jest
.spyOn(activatedRoute, 'paramMap', 'get')
.mockReturnValue(of(convertToParamMap(queryParams)))
activatedRoute.snapshot.queryParams = queryParams
fixture.detectChanges()
expect(getSavedViewSpy).toHaveBeenCalledWith(view.id)
expect(activateSavedViewSpy).toHaveBeenCalledWith(
view,
convertToParamMap(queryParams)
)
})
it('should 404 on load saved view from URL if no view', () => {
jest.spyOn(savedViewService, 'getCached').mockReturnValue(of(null)) // e.g. no saved view found
jest
.spyOn(activatedRoute, 'paramMap', 'get')
.mockReturnValue(of(convertToParamMap({ id: '10' })))
const navigateSpy = jest.spyOn(router, 'navigate')
fixture.detectChanges()
expect(navigateSpy).toHaveBeenCalledWith(['404'])
})
it('should load saved view from query params', () => {
const view: PaperlessSavedView = {
id: 10,
sort_field: 'added',
sort_reverse: true,
filter_rules: [
{
rule_type: FILTER_HAS_TAGS_ANY,
value: '20',
},
],
}
const getSavedViewSpy = jest.spyOn(savedViewService, 'getCached')
getSavedViewSpy.mockReturnValue(of(view))
jest
.spyOn(activatedRoute, 'queryParamMap', 'get')
.mockReturnValue(of(convertToParamMap({ view: view.id.toString() })))
fixture.detectChanges()
expect(getSavedViewSpy).toHaveBeenCalledWith(view.id)
})
it('should support 3 different display modes', () => {
jest.spyOn(documentListService, 'documents', 'get').mockReturnValue(docs)
fixture.detectChanges()
const displayModeButtons = fixture.debugElement.queryAll(
By.css('input[type="radio"]')
)
expect(component.displayMode).toEqual('smallCards')
displayModeButtons[0].nativeElement.checked = true
displayModeButtons[0].triggerEventHandler('change')
fixture.detectChanges()
expect(component.displayMode).toEqual('details')
expect(fixture.debugElement.queryAll(By.css('tr'))).toHaveLength(3)
displayModeButtons[1].nativeElement.checked = true
displayModeButtons[1].triggerEventHandler('change')
fixture.detectChanges()
expect(component.displayMode).toEqual('smallCards')
expect(
fixture.debugElement.queryAll(By.directive(DocumentCardSmallComponent))
).toHaveLength(3)
displayModeButtons[2].nativeElement.checked = true
displayModeButtons[2].triggerEventHandler('change')
fixture.detectChanges()
expect(component.displayMode).toEqual('largeCards')
expect(
fixture.debugElement.queryAll(By.directive(DocumentCardLargeComponent))
).toHaveLength(3)
})
it('should support setting sort field', () => {
expect(documentListService.sortField).toEqual('created')
fixture.detectChanges()
const sortDropdown = fixture.debugElement.queryAll(
By.directive(NgbDropdown)
)[1]
const asnSortFieldButton = sortDropdown.query(By.directive(NgbDropdownItem))
asnSortFieldButton.triggerEventHandler('click')
fixture.detectChanges()
expect(documentListService.sortField).toEqual('archive_serial_number')
documentListService.sortField = 'created'
})
it('should support setting sort field by table head', () => {
jest.spyOn(documentListService, 'documents', 'get').mockReturnValue(docs)
fixture.detectChanges()
expect(documentListService.sortField).toEqual('created')
const detailsDisplayModeButton = fixture.debugElement.query(
By.css('input[type="radio"]')
)
detailsDisplayModeButton.nativeElement.checked = true
detailsDisplayModeButton.triggerEventHandler('change')
fixture.detectChanges()
expect(component.displayMode).toEqual('details')
const sortTh = fixture.debugElement.query(By.directive(SortableDirective))
sortTh.triggerEventHandler('click')
fixture.detectChanges()
expect(documentListService.sortField).toEqual('archive_serial_number')
documentListService.sortField = 'created'
expect(documentListService.sortReverse).toBeFalsy()
component.listSortReverse = true
expect(documentListService.sortReverse).toBeTruthy()
})
it('should support select all, none, page & range', () => {
jest.spyOn(documentListService, 'documents', 'get').mockReturnValue(docs)
jest
.spyOn(documentService, 'listAllFilteredIds')
.mockReturnValue(of(docs.map((d) => d.id)))
fixture.detectChanges()
expect(documentListService.selected.size).toEqual(0)
const docCards = fixture.debugElement.queryAll(
By.directive(DocumentCardLargeComponent)
)
const displayModeButtons = fixture.debugElement.queryAll(
By.directive(NgbDropdownItem)
)
const selectAllSpy = jest.spyOn(documentListService, 'selectAll')
displayModeButtons[2].triggerEventHandler('click')
expect(selectAllSpy).toHaveBeenCalled()
fixture.detectChanges()
expect(documentListService.selected.size).toEqual(3)
docCards.forEach((card) => {
expect(card.context.selected).toBeTruthy()
})
const selectNoneSpy = jest.spyOn(documentListService, 'selectNone')
displayModeButtons[0].triggerEventHandler('click')
expect(selectNoneSpy).toHaveBeenCalled()
fixture.detectChanges()
expect(documentListService.selected.size).toEqual(0)
docCards.forEach((card) => {
expect(card.context.selected).toBeFalsy()
})
const selectPageSpy = jest.spyOn(documentListService, 'selectPage')
displayModeButtons[1].triggerEventHandler('click')
expect(selectPageSpy).toHaveBeenCalled()
fixture.detectChanges()
expect(documentListService.selected.size).toEqual(3)
docCards.forEach((card) => {
expect(card.context.selected).toBeTruthy()
})
component.toggleSelected(docs[0], new MouseEvent('click'))
fixture.detectChanges()
expect(documentListService.selected.size).toEqual(2)
// reset
displayModeButtons[0].triggerEventHandler('click')
fixture.detectChanges()
expect(documentListService.selected.size).toEqual(0)
// select a range
component.toggleSelected(docs[0], new MouseEvent('click'))
component.toggleSelected(
docs[2],
new MouseEvent('click', { shiftKey: true })
)
fixture.detectChanges()
expect(documentListService.selected.size).toEqual(3)
})
it('should support saving an edited view', () => {
const view: PaperlessSavedView = {
id: 10,
name: 'Saved View 10',
sort_field: 'added',
sort_reverse: true,
filter_rules: [
{
rule_type: FILTER_HAS_TAGS_ANY,
value: '20',
},
],
}
jest.spyOn(savedViewService, 'getCached').mockReturnValue(of(view))
const queryParams = { view: view.id.toString() }
jest
.spyOn(activatedRoute, 'queryParamMap', 'get')
.mockReturnValue(of(convertToParamMap(queryParams)))
activatedRoute.snapshot.queryParams = queryParams
router.routerState.snapshot.url = '/view/10/'
fixture.detectChanges()
expect(documentListService.activeSavedViewId).toEqual(10)
const modifiedView = Object.assign({}, view)
delete modifiedView.name
const savedViewServicePatch = jest.spyOn(savedViewService, 'patch')
savedViewServicePatch.mockReturnValue(of(modifiedView))
const toastSpy = jest.spyOn(toastService, 'showInfo')
component.saveViewConfig()
expect(savedViewServicePatch).toHaveBeenCalledWith(modifiedView)
expect(toastSpy).toHaveBeenCalledWith(
`View "${view.name}" saved successfully.`
)
})
it('should support edited view saving as', () => {
const view: PaperlessSavedView = {
id: 10,
name: 'Saved View 10',
sort_field: 'added',
sort_reverse: true,
filter_rules: [
{
rule_type: FILTER_HAS_TAGS_ANY,
value: '20',
},
],
}
jest.spyOn(savedViewService, 'getCached').mockReturnValue(of(view))
const queryParams = { view: view.id.toString() }
jest
.spyOn(activatedRoute, 'queryParamMap', 'get')
.mockReturnValue(of(convertToParamMap(queryParams)))
activatedRoute.snapshot.queryParams = queryParams
router.routerState.snapshot.url = '/view/10/'
fixture.detectChanges()
expect(documentListService.activeSavedViewId).toEqual(10)
const modifiedView = Object.assign({}, view)
modifiedView.name = 'Foo Bar'
let openModal: NgbModalRef
modalService.activeInstances.subscribe((modal) => (openModal = modal[0]))
const modalSpy = jest.spyOn(modalService, 'open')
const toastSpy = jest.spyOn(toastService, 'showInfo')
const savedViewServiceCreate = jest.spyOn(savedViewService, 'create')
savedViewServiceCreate.mockReturnValueOnce(of(modifiedView))
component.saveViewConfigAs()
const modalCloseSpy = jest.spyOn(openModal, 'close')
openModal.componentInstance.saveClicked.next({
name: 'Foo Bar',
show_on_dashboard: true,
show_in_sidebar: true,
})
expect(savedViewServiceCreate).toHaveBeenCalled()
expect(modalSpy).toHaveBeenCalled()
expect(toastSpy).toHaveBeenCalled()
expect(modalCloseSpy).toHaveBeenCalled()
})
it('should handle error on edited view saving as', () => {
const view: PaperlessSavedView = {
id: 10,
name: 'Saved View 10',
sort_field: 'added',
sort_reverse: true,
filter_rules: [
{
rule_type: FILTER_HAS_TAGS_ANY,
value: '20',
},
],
}
jest.spyOn(savedViewService, 'getCached').mockReturnValue(of(view))
const queryParams = { view: view.id.toString() }
jest
.spyOn(activatedRoute, 'queryParamMap', 'get')
.mockReturnValue(of(convertToParamMap(queryParams)))
activatedRoute.snapshot.queryParams = queryParams
router.routerState.snapshot.url = '/view/10/'
fixture.detectChanges()
expect(documentListService.activeSavedViewId).toEqual(10)
const modifiedView = Object.assign({}, view)
modifiedView.name = 'Foo Bar'
let openModal: NgbModalRef
modalService.activeInstances.subscribe((modal) => (openModal = modal[0]))
jest.spyOn(savedViewService, 'create').mockReturnValueOnce(
throwError(
() =>
new HttpErrorResponse({
error: { filter_rules: [{ value: '11' }] },
})
)
)
component.saveViewConfigAs()
openModal.componentInstance.saveClicked.next({
name: 'Foo Bar',
show_on_dashboard: true,
show_in_sidebar: true,
})
expect(openModal.componentInstance.error).toEqual({ filter_rules: ['11'] })
})
it('should navigate to a document', () => {
fixture.detectChanges()
const routerSpy = jest.spyOn(router, 'navigate')
component.openDocumentDetail({ id: 99 })
expect(routerSpy).toHaveBeenCalledWith(['documents', 99])
})
it('should support checking if notes enabled to hide column', () => {
jest.spyOn(documentListService, 'documents', 'get').mockReturnValue(docs)
fixture.detectChanges()
expect(documentListService.sortField).toEqual('created')
const detailsDisplayModeButton = fixture.debugElement.query(
By.css('input[type="radio"]')
)
detailsDisplayModeButton.nativeElement.checked = true
detailsDisplayModeButton.triggerEventHandler('change')
fixture.detectChanges()
expect(component.displayMode).toEqual('details')
expect(
fixture.debugElement.queryAll(By.directive(SortableDirective))
).toHaveLength(9)
expect(component.notesEnabled).toBeTruthy()
settingsService.set(SETTINGS_KEYS.NOTES_ENABLED, false)
fixture.detectChanges()
expect(component.notesEnabled).toBeFalsy()
expect(
fixture.debugElement.queryAll(By.directive(SortableDirective))
).toHaveLength(8)
})
it('should support toggle on document objects', () => {
// TODO: this is just for coverage atm
fixture.detectChanges()
component.clickTag(1)
component.clickCorrespondent(2)
component.clickDocumentType(3)
component.clickStoragePath(4)
})
it('should support quick filter on document more like', () => {
fixture.detectChanges()
const qfSpy = jest.spyOn(documentListService, 'quickFilter')
component.clickMoreLike(99)
expect(qfSpy).toHaveBeenCalledWith([
{ rule_type: FILTER_FULLTEXT_MORELIKE, value: '99' },
])
})
})