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
This commit is contained in:
shamoon
2023-05-23 15:02:54 -07:00
parent 0f9c642f0f
commit 181673c9a3
145 changed files with 14832 additions and 169 deletions

View File

@@ -0,0 +1,869 @@
import {
HttpTestingController,
HttpClientTestingModule,
} from '@angular/common/http/testing'
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { By } from '@angular/platform-browser'
import {
NgbModal,
NgbModule,
NgbModalModule,
NgbModalRef,
} from '@ng-bootstrap/ng-bootstrap'
import { of, throwError } from 'rxjs'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { FilterPipe } from 'src/app/pipes/filter.pipe'
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { PermissionsService } from 'src/app/services/permissions.service'
import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
import {
SelectionData,
DocumentService,
} from 'src/app/services/rest/document.service'
import { StoragePathService } from 'src/app/services/rest/storage-path.service'
import { TagService } from 'src/app/services/rest/tag.service'
import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
import { environment } from 'src/environments/environment'
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
import { FilterableDropdownComponent } from '../../common/filterable-dropdown/filterable-dropdown.component'
import { ToggleableDropdownButtonComponent } from '../../common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component'
import { PermissionsDialogComponent } from '../../common/permissions-dialog/permissions-dialog.component'
import { PermissionsFormComponent } from '../../common/input/permissions/permissions-form/permissions-form.component'
import { BulkEditorComponent } from './bulk-editor.component'
import { SelectComponent } from '../../common/input/select/select.component'
import { UserService } from 'src/app/services/rest/user.service'
import { PermissionsGroupComponent } from '../../common/input/permissions/permissions-group/permissions-group.component'
import { PermissionsUserComponent } from '../../common/input/permissions/permissions-user/permissions-user.component'
import { NgSelectModule } from '@ng-select/ng-select'
import { GroupService } from 'src/app/services/rest/group.service'
const selectionData: SelectionData = {
selected_tags: [
{ id: 12, document_count: 3 },
{ id: 22, document_count: 1 },
{ id: 19, document_count: 0 },
],
selected_correspondents: [{ id: 33, document_count: 1 }],
selected_document_types: [{ id: 44, document_count: 3 }],
selected_storage_paths: [
{ id: 66, document_count: 3 },
{ id: 55, document_count: 0 },
],
}
describe('BulkEditorComponent', () => {
let component: BulkEditorComponent
let fixture: ComponentFixture<BulkEditorComponent>
let permissionsService: PermissionsService
let documentListViewService: DocumentListViewService
let documentService: DocumentService
let toastService: ToastService
let modalService: NgbModal
let httpTestingController: HttpTestingController
beforeEach(async () => {
TestBed.configureTestingModule({
declarations: [
BulkEditorComponent,
IfPermissionsDirective,
FilterableDropdownComponent,
ToggleableDropdownButtonComponent,
FilterPipe,
ConfirmDialogComponent,
SafeHtmlPipe,
PermissionsDialogComponent,
PermissionsFormComponent,
SelectComponent,
PermissionsGroupComponent,
PermissionsUserComponent,
],
providers: [
PermissionsService,
{
provide: TagService,
useValue: {
listAll: () =>
of({
results: [
{ id: 12, name: 'tag12' },
{ id: 22, name: 'tag22' },
],
}),
},
},
{
provide: CorrespondentService,
useValue: {
listAll: () =>
of({
results: [{ id: 33, name: 'correspondent33' }],
}),
},
},
{
provide: DocumentTypeService,
useValue: {
listAll: () =>
of({
results: [{ id: 44, name: 'doctype44' }],
}),
},
},
{
provide: StoragePathService,
useValue: {
listAll: () =>
of({
results: [
{ id: 66, name: 'storagepath66' },
{ id: 55, name: 'storagepath55' },
],
}),
},
},
FilterPipe,
SettingsService,
{
provide: UserService,
useValue: {
listAll: () =>
of({
results: [{ id: 1, username: 'user1' }],
}),
},
},
{
provide: GroupService,
useValue: {
listAll: () =>
of({
results: [],
}),
},
},
],
imports: [
HttpClientTestingModule,
FormsModule,
ReactiveFormsModule,
NgbModule,
NgbModalModule,
NgSelectModule,
],
}).compileComponents()
permissionsService = TestBed.inject(PermissionsService)
documentListViewService = TestBed.inject(DocumentListViewService)
documentService = TestBed.inject(DocumentService)
toastService = TestBed.inject(ToastService)
modalService = TestBed.inject(NgbModal)
httpTestingController = TestBed.inject(HttpTestingController)
fixture = TestBed.createComponent(BulkEditorComponent)
component = fixture.componentInstance
})
afterEach(async () => {
httpTestingController.verify()
})
it('should apply selection data to tags menu', () => {
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
fixture.detectChanges()
expect(component.tagSelectionModel.getSelectedItems()).toHaveLength(0)
jest
.spyOn(documentListViewService, 'selected', 'get')
.mockReturnValue(new Set([3, 5, 7]))
jest
.spyOn(documentService, 'getSelectionData')
.mockReturnValue(of(selectionData))
component.openTagsDropdown()
expect(component.tagSelectionModel.selectionSize()).toEqual(1)
})
it('should apply selection data to correspondents menu', () => {
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
fixture.detectChanges()
expect(
component.correspondentSelectionModel.getSelectedItems()
).toHaveLength(0)
jest
.spyOn(documentListViewService, 'selected', 'get')
.mockReturnValue(new Set([3, 5, 7]))
jest
.spyOn(documentService, 'getSelectionData')
.mockReturnValue(of(selectionData))
component.openCorrespondentDropdown()
expect(component.correspondentSelectionModel.items).toHaveLength(2)
expect(component.correspondentSelectionModel.selectionSize()).toEqual(0)
})
it('should apply selection data to doc types menu', () => {
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
fixture.detectChanges()
expect(
component.documentTypeSelectionModel.getSelectedItems()
).toHaveLength(0)
jest
.spyOn(documentListViewService, 'selected', 'get')
.mockReturnValue(new Set([3, 5, 7]))
jest
.spyOn(documentService, 'getSelectionData')
.mockReturnValue(of(selectionData))
component.openDocumentTypeDropdown()
expect(component.documentTypeSelectionModel.selectionSize()).toEqual(1)
})
it('should apply selection data to storage path menu', () => {
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
fixture.detectChanges()
expect(
component.storagePathsSelectionModel.getSelectedItems()
).toHaveLength(0)
jest
.spyOn(documentListViewService, 'selected', 'get')
.mockReturnValue(new Set([3, 5, 7]))
jest
.spyOn(documentService, 'getSelectionData')
.mockReturnValue(of(selectionData))
component.openStoragePathDropdown()
expect(component.storagePathsSelectionModel.selectionSize()).toEqual(1)
})
it('should execute modify tags bulk operation', () => {
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
jest
.spyOn(documentListViewService, 'documents', 'get')
.mockReturnValue([{ id: 3 }, { id: 4 }])
jest
.spyOn(documentListViewService, 'selected', 'get')
.mockReturnValue(new Set([3, 4]))
jest
.spyOn(permissionsService, 'currentUserHasObjectPermissions')
.mockReturnValue(true)
component.showConfirmationDialogs = false
fixture.detectChanges()
component.setTags({
itemsToAdd: [{ id: 101 }],
itemsToRemove: [],
})
let req = httpTestingController.expectOne(
`${environment.apiBaseUrl}documents/bulk_edit/`
)
req.flush(true)
expect(req.request.body).toEqual({
documents: [3, 4],
method: 'modify_tags',
parameters: { add_tags: [101], remove_tags: [] },
})
httpTestingController.match(
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
) // list reload
httpTestingController.match(
`${environment.apiBaseUrl}documents/?page=1&page_size=100000&fields=id`
) // listAllFilteredIds
})
it('should execute modify tags bulk operation with confirmation dialog if enabled', () => {
let modal: NgbModalRef
modalService.activeInstances.subscribe((m) => (modal = m[0]))
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
jest
.spyOn(documentListViewService, 'documents', 'get')
.mockReturnValue([{ id: 3 }, { id: 4 }])
jest
.spyOn(documentListViewService, 'selected', 'get')
.mockReturnValue(new Set([3, 4]))
jest
.spyOn(permissionsService, 'currentUserHasObjectPermissions')
.mockReturnValue(true)
component.showConfirmationDialogs = true
fixture.detectChanges()
component.setTags({
itemsToAdd: [{ id: 101 }],
itemsToRemove: [],
})
expect(modal).not.toBeUndefined()
modal.componentInstance.confirm()
httpTestingController
.expectOne(`${environment.apiBaseUrl}documents/bulk_edit/`)
.flush(true)
httpTestingController.match(
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
) // list reload
httpTestingController.match(
`${environment.apiBaseUrl}documents/?page=1&page_size=100000&fields=id`
) // listAllFilteredIds
})
it('should set modal dialog text accordingly for tag edit confirmation', () => {
let modal: NgbModalRef
modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
jest
.spyOn(documentListViewService, 'documents', 'get')
.mockReturnValue([{ id: 3 }, { id: 4 }])
jest
.spyOn(documentListViewService, 'selected', 'get')
.mockReturnValue(new Set([3, 4]))
jest
.spyOn(permissionsService, 'currentUserHasObjectPermissions')
.mockReturnValue(true)
component.showConfirmationDialogs = true
fixture.detectChanges()
component.setTags({
itemsToAdd: [],
itemsToRemove: [{ id: 101, name: 'Tag 101' }],
})
expect(modal.componentInstance.message).toEqual(
'This operation will remove the tag "Tag 101" from 2 selected document(s).'
)
modal.close()
component.setTags({
itemsToAdd: [],
itemsToRemove: [
{ id: 101, name: 'Tag 101' },
{ id: 102, name: 'Tag 102' },
],
})
expect(modal.componentInstance.message).toEqual(
'This operation will remove the tags "Tag 101" and "Tag 102" from 2 selected document(s).'
)
modal.close()
component.setTags({
itemsToAdd: [
{ id: 101, name: 'Tag 101' },
{ id: 102, name: 'Tag 102' },
],
itemsToRemove: [],
})
expect(modal.componentInstance.message).toEqual(
'This operation will add the tags "Tag 101" and "Tag 102" to 2 selected document(s).'
)
modal.close()
component.setTags({
itemsToAdd: [
{ id: 101, name: 'Tag 101' },
{ id: 102, name: 'Tag 102' },
],
itemsToRemove: [{ id: 103, name: 'Tag 103' }],
})
expect(modal.componentInstance.message).toEqual(
'This operation will add the tags "Tag 101" and "Tag 102" and remove the tags "Tag 103" on 2 selected document(s).'
)
})
it('should execute modify correspondent bulk operation', () => {
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
jest
.spyOn(documentListViewService, 'documents', 'get')
.mockReturnValue([{ id: 3 }, { id: 4 }])
jest
.spyOn(documentListViewService, 'selected', 'get')
.mockReturnValue(new Set([3, 4]))
jest
.spyOn(permissionsService, 'currentUserHasObjectPermissions')
.mockReturnValue(true)
component.showConfirmationDialogs = false
fixture.detectChanges()
component.setCorrespondents({
itemsToAdd: [{ id: 101 }],
itemsToRemove: [],
})
let req = httpTestingController.expectOne(
`${environment.apiBaseUrl}documents/bulk_edit/`
)
req.flush(true)
expect(req.request.body).toEqual({
documents: [3, 4],
method: 'set_correspondent',
parameters: { correspondent: 101 },
})
httpTestingController.match(
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
) // list reload
httpTestingController.match(
`${environment.apiBaseUrl}documents/?page=1&page_size=100000&fields=id`
) // listAllFilteredIds
})
it('should execute modify correspondent bulk operation with confirmation dialog if enabled', () => {
let modal: NgbModalRef
modalService.activeInstances.subscribe((m) => (modal = m[0]))
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
jest
.spyOn(documentListViewService, 'documents', 'get')
.mockReturnValue([{ id: 3 }, { id: 4 }])
jest
.spyOn(documentListViewService, 'selected', 'get')
.mockReturnValue(new Set([3, 4]))
jest
.spyOn(permissionsService, 'currentUserHasObjectPermissions')
.mockReturnValue(true)
component.showConfirmationDialogs = true
fixture.detectChanges()
component.setCorrespondents({
itemsToAdd: [{ id: 101 }],
itemsToRemove: [],
})
expect(modal).not.toBeUndefined()
modal.componentInstance.confirm()
httpTestingController
.expectOne(`${environment.apiBaseUrl}documents/bulk_edit/`)
.flush(true)
httpTestingController.match(
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
) // list reload
httpTestingController.match(
`${environment.apiBaseUrl}documents/?page=1&page_size=100000&fields=id`
) // listAllFilteredIds
})
it('should set modal dialog text accordingly for correspondent edit confirmation', () => {
let modal: NgbModalRef
modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
jest
.spyOn(documentListViewService, 'documents', 'get')
.mockReturnValue([{ id: 3 }, { id: 4 }])
jest
.spyOn(documentListViewService, 'selected', 'get')
.mockReturnValue(new Set([3, 4]))
jest
.spyOn(permissionsService, 'currentUserHasObjectPermissions')
.mockReturnValue(true)
component.showConfirmationDialogs = true
fixture.detectChanges()
component.setCorrespondents({
itemsToAdd: [],
itemsToRemove: [{ id: 101, name: 'Correspondent 101' }],
})
expect(modal.componentInstance.message).toEqual(
'This operation will remove the correspondent from 2 selected document(s).'
)
modal.close()
component.setCorrespondents({
itemsToAdd: [{ id: 101, name: 'Correspondent 101' }],
itemsToRemove: [],
})
expect(modal.componentInstance.message).toEqual(
'This operation will assign the correspondent "Correspondent 101" to 2 selected document(s).'
)
})
it('should execute modify document type bulk operation', () => {
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
jest
.spyOn(documentListViewService, 'documents', 'get')
.mockReturnValue([{ id: 3 }, { id: 4 }])
jest
.spyOn(documentListViewService, 'selected', 'get')
.mockReturnValue(new Set([3, 4]))
jest
.spyOn(permissionsService, 'currentUserHasObjectPermissions')
.mockReturnValue(true)
component.showConfirmationDialogs = false
fixture.detectChanges()
component.setDocumentTypes({
itemsToAdd: [{ id: 101 }],
itemsToRemove: [],
})
let req = httpTestingController.expectOne(
`${environment.apiBaseUrl}documents/bulk_edit/`
)
req.flush(true)
expect(req.request.body).toEqual({
documents: [3, 4],
method: 'set_document_type',
parameters: { document_type: 101 },
})
httpTestingController.match(
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
) // list reload
httpTestingController.match(
`${environment.apiBaseUrl}documents/?page=1&page_size=100000&fields=id`
) // listAllFilteredIds
})
it('should execute modify document type bulk operation with confirmation dialog if enabled', () => {
let modal: NgbModalRef
modalService.activeInstances.subscribe((m) => (modal = m[0]))
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
jest
.spyOn(documentListViewService, 'documents', 'get')
.mockReturnValue([{ id: 3 }, { id: 4 }])
jest
.spyOn(documentListViewService, 'selected', 'get')
.mockReturnValue(new Set([3, 4]))
jest
.spyOn(permissionsService, 'currentUserHasObjectPermissions')
.mockReturnValue(true)
component.showConfirmationDialogs = true
fixture.detectChanges()
component.setDocumentTypes({
itemsToAdd: [{ id: 101 }],
itemsToRemove: [],
})
expect(modal).not.toBeUndefined()
modal.componentInstance.confirm()
httpTestingController
.expectOne(`${environment.apiBaseUrl}documents/bulk_edit/`)
.flush(true)
httpTestingController.match(
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
) // list reload
httpTestingController.match(
`${environment.apiBaseUrl}documents/?page=1&page_size=100000&fields=id`
) // listAllFilteredIds
})
it('should set modal dialog text accordingly for document type edit confirmation', () => {
let modal: NgbModalRef
modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
jest
.spyOn(documentListViewService, 'documents', 'get')
.mockReturnValue([{ id: 3 }, { id: 4 }])
jest
.spyOn(documentListViewService, 'selected', 'get')
.mockReturnValue(new Set([3, 4]))
jest
.spyOn(permissionsService, 'currentUserHasObjectPermissions')
.mockReturnValue(true)
component.showConfirmationDialogs = true
fixture.detectChanges()
component.setDocumentTypes({
itemsToAdd: [],
itemsToRemove: [{ id: 101, name: 'DocType 101' }],
})
expect(modal.componentInstance.message).toEqual(
'This operation will remove the document type from 2 selected document(s).'
)
modal.close()
component.setDocumentTypes({
itemsToAdd: [{ id: 101, name: 'DocType 101' }],
itemsToRemove: [],
})
expect(modal.componentInstance.message).toEqual(
'This operation will assign the document type "DocType 101" to 2 selected document(s).'
)
})
it('should execute modify storage path bulk operation', () => {
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
jest
.spyOn(documentListViewService, 'documents', 'get')
.mockReturnValue([{ id: 3 }, { id: 4 }])
jest
.spyOn(documentListViewService, 'selected', 'get')
.mockReturnValue(new Set([3, 4]))
jest
.spyOn(permissionsService, 'currentUserHasObjectPermissions')
.mockReturnValue(true)
component.showConfirmationDialogs = false
fixture.detectChanges()
component.setStoragePaths({
itemsToAdd: [{ id: 101 }],
itemsToRemove: [],
})
let req = httpTestingController.expectOne(
`${environment.apiBaseUrl}documents/bulk_edit/`
)
req.flush(true)
expect(req.request.body).toEqual({
documents: [3, 4],
method: 'set_storage_path',
parameters: { storage_path: 101 },
})
httpTestingController.match(
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
) // list reload
httpTestingController.match(
`${environment.apiBaseUrl}documents/?page=1&page_size=100000&fields=id`
) // listAllFilteredIds
})
it('should execute modify storage path bulk operation with confirmation dialog if enabled', () => {
let modal: NgbModalRef
modalService.activeInstances.subscribe((m) => (modal = m[0]))
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
jest
.spyOn(documentListViewService, 'documents', 'get')
.mockReturnValue([{ id: 3 }, { id: 4 }])
jest
.spyOn(documentListViewService, 'selected', 'get')
.mockReturnValue(new Set([3, 4]))
jest
.spyOn(permissionsService, 'currentUserHasObjectPermissions')
.mockReturnValue(true)
component.showConfirmationDialogs = true
fixture.detectChanges()
component.setStoragePaths({
itemsToAdd: [{ id: 101 }],
itemsToRemove: [],
})
expect(modal).not.toBeUndefined()
modal.componentInstance.confirm()
httpTestingController
.expectOne(`${environment.apiBaseUrl}documents/bulk_edit/`)
.flush(true)
httpTestingController.match(
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
) // list reload
httpTestingController.match(
`${environment.apiBaseUrl}documents/?page=1&page_size=100000&fields=id`
) // listAllFilteredIds
})
it('should set modal dialog text accordingly for storage path edit confirmation', () => {
let modal: NgbModalRef
modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
jest
.spyOn(documentListViewService, 'documents', 'get')
.mockReturnValue([{ id: 3 }, { id: 4 }])
jest
.spyOn(documentListViewService, 'selected', 'get')
.mockReturnValue(new Set([3, 4]))
jest
.spyOn(permissionsService, 'currentUserHasObjectPermissions')
.mockReturnValue(true)
component.showConfirmationDialogs = true
fixture.detectChanges()
component.setStoragePaths({
itemsToAdd: [],
itemsToRemove: [{ id: 101, name: 'StoragePath 101' }],
})
expect(modal.componentInstance.message).toEqual(
'This operation will remove the storage path from 2 selected document(s).'
)
modal.close()
component.setStoragePaths({
itemsToAdd: [{ id: 101, name: 'StoragePath 101' }],
itemsToRemove: [],
})
expect(modal.componentInstance.message).toEqual(
'This operation will assign the storage path "StoragePath 101" to 2 selected document(s).'
)
})
it('should only execute bulk operations when changes are detected', () => {
component.setTags({
itemsToAdd: [],
itemsToRemove: [],
})
component.setCorrespondents({
itemsToAdd: [],
itemsToRemove: [],
})
component.setDocumentTypes({
itemsToAdd: [],
itemsToRemove: [],
})
component.setStoragePaths({
itemsToAdd: [],
itemsToRemove: [],
})
httpTestingController.expectNone(
`${environment.apiBaseUrl}documents/bulk_edit/`
)
})
it('should support bulk delete with confirmation', () => {
let modal: NgbModalRef
modalService.activeInstances.subscribe((m) => (modal = m[0]))
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
jest
.spyOn(documentListViewService, 'documents', 'get')
.mockReturnValue([{ id: 3 }, { id: 4 }])
jest
.spyOn(documentListViewService, 'selected', 'get')
.mockReturnValue(new Set([3, 4]))
jest
.spyOn(permissionsService, 'currentUserHasObjectPermissions')
.mockReturnValue(true)
component.showConfirmationDialogs = true
fixture.detectChanges()
component.applyDelete()
expect(modal).not.toBeUndefined()
modal.componentInstance.confirm()
let req = httpTestingController.expectOne(
`${environment.apiBaseUrl}documents/bulk_edit/`
)
req.flush(true)
expect(req.request.body).toEqual({
documents: [3, 4],
method: 'delete',
parameters: {},
})
httpTestingController.match(
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
) // list reload
httpTestingController.match(
`${environment.apiBaseUrl}documents/?page=1&page_size=100000&fields=id`
) // listAllFilteredIds
})
it('should not be accessible with insufficient global permissions', () => {
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(false)
fixture.detectChanges()
const dropdown = fixture.debugElement.query(
By.directive(FilterableDropdownComponent)
)
expect(dropdown).toBeNull()
})
it('should disable with insufficient object permissions', () => {
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
jest
.spyOn(documentListViewService, 'documents', 'get')
.mockReturnValue([{ id: 3 }, { id: 4 }])
jest
.spyOn(documentListViewService, 'selected', 'get')
.mockReturnValue(new Set([3, 4]))
jest
.spyOn(permissionsService, 'currentUserHasObjectPermissions')
.mockReturnValue(false)
fixture.detectChanges()
const button = fixture.debugElement
.query(By.directive(FilterableDropdownComponent))
.query(By.css('button'))
expect(button.nativeElement.disabled).toBeTruthy()
})
it('should show a warning toast on bulk edit error', () => {
jest
.spyOn(documentService, 'bulkEdit')
.mockReturnValue(
throwError(() => new Error('error executing bulk operation'))
)
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
jest
.spyOn(documentListViewService, 'documents', 'get')
.mockReturnValue([{ id: 3 }, { id: 4 }])
jest
.spyOn(documentListViewService, 'selected', 'get')
.mockReturnValue(new Set([3, 4]))
jest
.spyOn(permissionsService, 'currentUserHasObjectPermissions')
.mockReturnValue(true)
component.showConfirmationDialogs = false
fixture.detectChanges()
const toastSpy = jest.spyOn(toastService, 'showError')
component.setTags({
itemsToAdd: [{ id: 0 }],
itemsToRemove: [],
})
expect(toastSpy).toHaveBeenCalled()
})
it('should support redo ocr', () => {
let modal: NgbModalRef
modalService.activeInstances.subscribe((m) => (modal = m[0]))
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
jest
.spyOn(documentListViewService, 'documents', 'get')
.mockReturnValue([{ id: 3 }, { id: 4 }])
jest
.spyOn(documentListViewService, 'selected', 'get')
.mockReturnValue(new Set([3, 4]))
jest
.spyOn(permissionsService, 'currentUserHasObjectPermissions')
.mockReturnValue(true)
component.showConfirmationDialogs = true
fixture.detectChanges()
component.redoOcrSelected()
expect(modal).not.toBeUndefined()
modal.componentInstance.confirm()
let req = httpTestingController.expectOne(
`${environment.apiBaseUrl}documents/bulk_edit/`
)
req.flush(true)
expect(req.request.body).toEqual({
documents: [3, 4],
method: 'redo_ocr',
parameters: {},
})
httpTestingController.match(
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
) // list reload
httpTestingController.match(
`${environment.apiBaseUrl}documents/?page=1&page_size=100000&fields=id`
) // listAllFilteredIds
})
it('should support bulk download with archive, originals or both and file formatting', () => {
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
jest
.spyOn(documentListViewService, 'documents', 'get')
.mockReturnValue([{ id: 3 }, { id: 4 }])
jest
.spyOn(documentListViewService, 'selected', 'get')
.mockReturnValue(new Set([3, 4]))
jest
.spyOn(permissionsService, 'currentUserHasObjectPermissions')
.mockReturnValue(true)
component.downloadForm.get('downloadFileTypeArchive').patchValue(true)
fixture.detectChanges()
let downloadSpy = jest.spyOn(documentService, 'bulkDownload')
//archive
component.downloadSelected()
expect(downloadSpy).toHaveBeenCalledWith([3, 4], 'archive', false)
//originals
component.downloadForm.get('downloadFileTypeArchive').patchValue(false)
component.downloadForm.get('downloadFileTypeOriginals').patchValue(true)
component.downloadSelected()
expect(downloadSpy).toHaveBeenCalledWith([3, 4], 'originals', false)
//both
component.downloadForm.get('downloadFileTypeArchive').patchValue(true)
component.downloadSelected()
expect(downloadSpy).toHaveBeenCalledWith([3, 4], 'both', false)
//formatting
component.downloadForm.get('downloadUseFormatting').patchValue(true)
component.downloadSelected()
expect(downloadSpy).toHaveBeenCalledWith([3, 4], 'both', true)
httpTestingController.match(
`${environment.apiBaseUrl}documents/bulk_download/`
)
})
it('should support bulk permissions update', () => {
let modal: NgbModalRef
modalService.activeInstances.subscribe((m) => (modal = m[0]))
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
jest
.spyOn(documentListViewService, 'documents', 'get')
.mockReturnValue([{ id: 3 }, { id: 4 }])
jest
.spyOn(documentListViewService, 'selected', 'get')
.mockReturnValue(new Set([3, 4]))
jest
.spyOn(permissionsService, 'currentUserHasObjectPermissions')
.mockReturnValue(true)
component.showConfirmationDialogs = true
fixture.detectChanges()
component.setPermissions()
expect(modal).not.toBeUndefined()
modal.componentInstance.confirmClicked.next()
let req = httpTestingController.expectOne(
`${environment.apiBaseUrl}documents/bulk_edit/`
)
req.flush(true)
expect(req.request.body).toEqual({
documents: [3, 4],
method: 'set_permissions',
parameters: undefined,
})
httpTestingController.match(
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
) // list reload
httpTestingController.match(
`${environment.apiBaseUrl}documents/?page=1&page_size=100000&fields=id`
) // listAllFilteredIds
})
})

View File

@@ -0,0 +1,129 @@
import { DatePipe } from '@angular/common'
import { HttpClientTestingModule } from '@angular/common/http/testing'
import {
ComponentFixture,
TestBed,
fakeAsync,
tick,
} from '@angular/core/testing'
import { By } from '@angular/platform-browser'
import { RouterTestingModule } from '@angular/router/testing'
import {
NgbPopoverModule,
NgbTooltipModule,
NgbProgressbarModule,
} from '@ng-bootstrap/ng-bootstrap'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
import { SafeUrlPipe } from 'src/app/pipes/safeurl.pipe'
import { DocumentCardLargeComponent } from './document-card-large.component'
const doc = {
id: 10,
title: 'Document 10',
tags: [3, 4, 5],
correspondent: 8,
document_type: 10,
storage_path: null,
notes: [
{
id: 11,
note: 'This is some note content bananas',
},
],
content:
'Cupcake ipsum dolor sit amet ice cream. Donut shortbread cheesecake caramels tiramisu pastry caramels chocolate bar. Tart tootsie roll muffin icing cotton candy topping sweet roll. Pie lollipop dragée sesame snaps donut tart pudding. Oat cake apple pie danish danish candy canes. Shortbread candy canes sesame snaps muffin tiramisu marshmallow chocolate bar halvah. Cake lemon drops candy apple pie carrot cake bonbon halvah pastry gummi bears. Sweet roll candy ice cream sesame snaps marzipan cookie ice cream. Cake cheesecake apple pie muffin candy toffee lollipop. Carrot cake oat cake cookie biscuit cupcake cake marshmallow. Sweet roll jujubes carrot cake cheesecake cake candy canes sweet roll gingerbread jelly beans. Apple pie sugar plum oat cake halvah cake. Pie oat cake chocolate cake cookie gingerbread marzipan. Lemon drops cheesecake lollipop danish marzipan candy.',
}
describe('DocumentCardLargeComponent', () => {
let component: DocumentCardLargeComponent
let fixture: ComponentFixture<DocumentCardLargeComponent>
beforeEach(async () => {
TestBed.configureTestingModule({
declarations: [
DocumentCardLargeComponent,
DocumentTitlePipe,
CustomDatePipe,
IfPermissionsDirective,
SafeUrlPipe,
],
providers: [DatePipe],
imports: [
HttpClientTestingModule,
RouterTestingModule,
NgbPopoverModule,
NgbTooltipModule,
NgbProgressbarModule,
],
}).compileComponents()
fixture = TestBed.createComponent(DocumentCardLargeComponent)
component = fixture.componentInstance
component.document = doc
fixture.detectChanges()
})
it('should display a document', () => {
expect(fixture.nativeElement.textContent).toContain('Document 10')
expect(fixture.nativeElement.textContent).toContain('Cupcake ipsum')
})
it('should show preview on mouseover after delay to preload content', fakeAsync(() => {
component.mouseEnterPreview()
expect(component.popover.isOpen()).toBeTruthy()
expect(component.popoverHidden).toBeTruthy()
tick(600)
expect(component.popoverHidden).toBeFalsy()
component.mouseLeaveCard()
component.mouseEnterPreview()
tick(100)
component.mouseLeavePreview()
tick(600)
expect(component.popover.isOpen()).toBeFalsy()
}))
it('should trim content', () => {
expect(component.contentTrimmed).toHaveLength(503) // includes ...
})
it('should display search hits with colored score', () => {
// high
component.document.__search_hit__ = {
score: 0.9,
rank: 1,
highlights: 'cheesecake',
}
fixture.detectChanges()
let search_hit = fixture.debugElement.query(By.css('.search-score'))
expect(search_hit).not.toBeUndefined()
expect(component.searchScoreClass).toEqual('success')
// medium
component.document.__search_hit__.score = 0.6
fixture.detectChanges()
search_hit = fixture.debugElement.query(By.css('.search-score'))
expect(search_hit).not.toBeUndefined()
expect(component.searchScoreClass).toEqual('warning')
// low
component.document.__search_hit__.score = 0.1
fixture.detectChanges()
search_hit = fixture.debugElement.query(By.css('.search-score'))
expect(search_hit).not.toBeUndefined()
expect(component.searchScoreClass).toEqual('danger')
})
it('should display note highlights', () => {
component.document.__search_hit__ = {
score: 0.9,
rank: 1,
note_highlights: '<span>bananas</span>',
}
fixture.detectChanges()
expect(fixture.nativeElement.textContent).toContain('bananas')
expect(component.searchNoteHighlights).toContain('<span>bananas</span>')
})
})

View File

@@ -133,7 +133,7 @@ export class DocumentCardLargeComponent extends ComponentWithPermissions {
get contentTrimmed() {
return (
this.document.content.substr(0, 500) +
this.document.content.substring(0, 500) +
(this.document.content.length > 500 ? '...' : '')
)
}

View File

@@ -0,0 +1,120 @@
import { DatePipe } from '@angular/common'
import { HttpClientTestingModule } from '@angular/common/http/testing'
import {
ComponentFixture,
TestBed,
fakeAsync,
tick,
} from '@angular/core/testing'
import { RouterTestingModule } from '@angular/router/testing'
import {
NgbPopoverModule,
NgbTooltipModule,
NgbProgressbarModule,
} from '@ng-bootstrap/ng-bootstrap'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
import { SafeUrlPipe } from 'src/app/pipes/safeurl.pipe'
import { DocumentCardSmallComponent } from './document-card-small.component'
import { of } from 'rxjs'
import { By } from '@angular/platform-browser'
import { TagComponent } from '../../common/tag/tag.component'
import { PaperlessTag } from 'src/app/data/paperless-tag'
const doc = {
id: 10,
title: 'Document 10',
tags: [1, 2, 3, 4, 5, 6, 7, 8],
correspondent: 8,
document_type: 10,
storage_path: null,
notes: [
{
id: 11,
note: 'This is some note content bananas',
},
],
tags$: of([
{ id: 1, name: 'Tag1' },
{ id: 2, name: 'Tag2' },
{ id: 3, name: 'Tag3' },
{ id: 4, name: 'Tag4' },
{ id: 5, name: 'Tag5' },
{ id: 6, name: 'Tag6' },
{ id: 7, name: 'Tag7' },
{ id: 8, name: 'Tag8' },
]),
content:
'Cupcake ipsum dolor sit amet ice cream. Donut shortbread cheesecake caramels tiramisu pastry caramels chocolate bar. Tart tootsie roll muffin icing cotton candy topping sweet roll. Pie lollipop dragée sesame snaps donut tart pudding. Oat cake apple pie danish danish candy canes. Shortbread candy canes sesame snaps muffin tiramisu marshmallow chocolate bar halvah. Cake lemon drops candy apple pie carrot cake bonbon halvah pastry gummi bears. Sweet roll candy ice cream sesame snaps marzipan cookie ice cream. Cake cheesecake apple pie muffin candy toffee lollipop. Carrot cake oat cake cookie biscuit cupcake cake marshmallow. Sweet roll jujubes carrot cake cheesecake cake candy canes sweet roll gingerbread jelly beans. Apple pie sugar plum oat cake halvah cake. Pie oat cake chocolate cake cookie gingerbread marzipan. Lemon drops cheesecake lollipop danish marzipan candy.',
}
describe('DocumentCardSmallComponent', () => {
let component: DocumentCardSmallComponent
let fixture: ComponentFixture<DocumentCardSmallComponent>
beforeEach(async () => {
TestBed.configureTestingModule({
declarations: [
DocumentCardSmallComponent,
DocumentTitlePipe,
CustomDatePipe,
IfPermissionsDirective,
SafeUrlPipe,
TagComponent,
],
providers: [DatePipe],
imports: [
HttpClientTestingModule,
RouterTestingModule,
NgbPopoverModule,
NgbTooltipModule,
NgbProgressbarModule,
],
}).compileComponents()
fixture = TestBed.createComponent(DocumentCardSmallComponent)
component = fixture.componentInstance
component.document = Object.assign({}, doc)
fixture.detectChanges()
})
it('should display a document, limit tags to 5', () => {
expect(fixture.nativeElement.textContent).toContain('Document 10')
expect(
fixture.debugElement.queryAll(By.directive(TagComponent))
).toHaveLength(5)
component.document.tags = [1, 2]
component.document.tags$ = of([
{ id: 1 } as PaperlessTag,
{ id: 2 } as PaperlessTag,
])
fixture.detectChanges()
expect(
fixture.debugElement.queryAll(By.directive(TagComponent))
).toHaveLength(2)
})
it('should increase limit tags to 6 if no notes', () => {
component.document.notes = []
fixture.detectChanges()
expect(
fixture.debugElement.queryAll(By.directive(TagComponent))
).toHaveLength(6)
})
it('should show preview on mouseover after delay to preload content', fakeAsync(() => {
component.mouseEnterPreview()
expect(component.popover.isOpen()).toBeTruthy()
expect(component.popoverHidden).toBeTruthy()
tick(600)
expect(component.popoverHidden).toBeFalsy()
component.mouseLeaveCard()
component.mouseEnterPreview()
tick(100)
component.mouseLeavePreview()
tick(600)
expect(component.popover.isOpen()).toBeFalsy()
}))
})

View File

@@ -0,0 +1,591 @@
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' },
])
})
})

View File

@@ -9,11 +9,11 @@ import {
import { ActivatedRoute, convertToParamMap, Router } from '@angular/router'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { filter, first, map, Subject, switchMap, takeUntil } from 'rxjs'
import { FilterRule } from 'src/app/data/filter-rule'
import {
FilterRule,
filterRulesDiffer,
isFullTextFilterRule,
} from 'src/app/data/filter-rule'
} from 'src/app/utils/filter-rules'
import { FILTER_FULLTEXT_MORELIKE } from 'src/app/data/filter-rule-type'
import { PaperlessDocument } from 'src/app/data/paperless-document'
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view'

View File

@@ -16,7 +16,8 @@ import { debounceTime, distinctUntilChanged, filter } from 'rxjs/operators'
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
import { TagService } from 'src/app/services/rest/tag.service'
import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
import { filterRulesDiffer, FilterRule } from 'src/app/data/filter-rule'
import { FilterRule } from 'src/app/data/filter-rule'
import { filterRulesDiffer } from 'src/app/utils/filter-rules'
import {
FILTER_ADDED_AFTER,
FILTER_ADDED_BEFORE,
@@ -67,7 +68,6 @@ import {
OwnerFilterType,
PermissionsSelectionModel,
} from '../../common/permissions-filter-dropdown/permissions-filter-dropdown.component'
import { SettingsService } from 'src/app/services/settings.service'
const TEXT_FILTER_TARGET_TITLE = 'title'
const TEXT_FILTER_TARGET_TITLE_CONTENT = 'title-content'
@@ -111,7 +111,8 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
generateFilterName() {
if (this.filterRules.length == 1) {
let rule = this.filterRules[0]
switch (this.filterRules[0].rule_type) {
switch (rule.rule_type) {
case FILTER_CORRESPONDENT:
case FILTER_HAS_CORRESPONDENT_ANY:
if (rule.value) {
return $localize`Correspondent: ${
@@ -121,15 +122,26 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
return $localize`Without correspondent`
}
case FILTER_DOCUMENT_TYPE:
case FILTER_HAS_DOCUMENT_TYPE_ANY:
if (rule.value) {
return $localize`Type: ${
return $localize`Document type: ${
this.documentTypes.find((dt) => dt.id == +rule.value)?.name
}`
} else {
return $localize`Without document type`
}
case FILTER_STORAGE_PATH:
case FILTER_HAS_STORAGE_PATH_ANY:
if (rule.value) {
return $localize`Storage path: ${
this.storagePaths.find((sp) => sp.id == +rule.value)?.name
}`
} else {
return $localize`Without storage path`
}
case FILTER_HAS_TAGS_ALL:
return $localize`Tag: ${
this.tags.find((t) => t.id == +rule.value)?.name
@@ -165,8 +177,7 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
private tagService: TagService,
private correspondentService: CorrespondentService,
private documentService: DocumentService,
private storagePathService: StoragePathService,
private settingsService: SettingsService
private storagePathService: StoragePathService
) {}
@ViewChild('textFilterInput')
@@ -557,7 +568,7 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
) {
filterRules.push({
rule_type: FILTER_FULLTEXT_MORELIKE,
value: this._moreLikeId?.toString(),
value: this._moreLikeId.toString(),
})
}
if (this.tagSelectionModel.isNoneSelected()) {

View File

@@ -0,0 +1,89 @@
import {
ComponentFixture,
TestBed,
fakeAsync,
tick,
} from '@angular/core/testing'
import { SaveViewConfigDialogComponent } from './save-view-config-dialog.component'
import { NgbActiveModal, NgbModalModule } from '@ng-bootstrap/ng-bootstrap'
import { By } from '@angular/platform-browser'
import { TextComponent } from '../../common/input/text/text.component'
import { CheckComponent } from '../../common/input/check/check.component'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
describe('SaveViewConfigDialogComponent', () => {
let component: SaveViewConfigDialogComponent
let fixture: ComponentFixture<SaveViewConfigDialogComponent>
let modal: NgbActiveModal
beforeEach(fakeAsync(() => {
TestBed.configureTestingModule({
declarations: [
SaveViewConfigDialogComponent,
TextComponent,
CheckComponent,
],
providers: [NgbActiveModal],
imports: [NgbModalModule, FormsModule, ReactiveFormsModule],
}).compileComponents()
modal = TestBed.inject(NgbActiveModal)
fixture = TestBed.createComponent(SaveViewConfigDialogComponent)
component = fixture.componentInstance
fixture.detectChanges()
tick()
}))
it('should support default name', () => {
const name = 'Tag: Inbox'
let result
component.saveClicked.subscribe((saveResult) => (result = saveResult))
component.defaultName = name
component.save()
expect(component.defaultName).toEqual(name)
expect(result).toEqual({
name,
showInSideBar: false,
showOnDashboard: false,
})
})
it('should support user input', () => {
const name = 'Tag: Inbox'
let result
component.saveClicked.subscribe((saveResult) => (result = saveResult))
const nameInput = fixture.debugElement
.query(By.directive(TextComponent))
.query(By.css('input'))
nameInput.nativeElement.value = name
component.saveViewConfigForm.get('name').patchValue(name) // normally done by angular
const sidebarCheckInput = fixture.debugElement
.queryAll(By.directive(CheckComponent))[0]
.query(By.css('input'))
sidebarCheckInput.nativeElement.checked = true
component.saveViewConfigForm.get('showInSideBar').patchValue(true) // normally done by angular
const dashboardCheckInput = fixture.debugElement
.queryAll(By.directive(CheckComponent))[1]
.query(By.css('input'))
dashboardCheckInput.nativeElement.checked = true
component.saveViewConfigForm.get('showOnDashboard').patchValue(true) // normally done by angular
component.save()
expect(result).toEqual({
name,
showInSideBar: true,
showOnDashboard: true,
})
})
it('should support default name', () => {
const saveClickedSpy = jest.spyOn(component.saveClicked, 'emit')
const modalCloseSpy = jest.spyOn(modal, 'close')
component.cancel()
expect(saveClickedSpy).not.toHaveBeenCalled()
expect(modalCloseSpy).toHaveBeenCalled()
})
})