mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-04-02 13:45:10 -05:00
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:
parent
e329f6cdf1
commit
06def8c11e
@ -1,8 +1,14 @@
|
||||
module.exports = {
|
||||
moduleNameMapper: {
|
||||
'@core/(.*)': '<rootDir>/src/app/core/$1',
|
||||
},
|
||||
preset: 'jest-preset-angular',
|
||||
setupFilesAfterEnv: ['<rootDir>/setup-jest.ts'],
|
||||
testPathIgnorePatterns: ['/node_modules/', '/cypress/'],
|
||||
testPathIgnorePatterns: [
|
||||
'/node_modules/',
|
||||
'/e2e/',
|
||||
'abstract-name-filter-service',
|
||||
'abstract-paperless-service',
|
||||
],
|
||||
transformIgnorePatterns: [`<rootDir>/node_modules/(?!.*\\.mjs$|lodash-es)`],
|
||||
moduleNameMapper: {
|
||||
'^src/(.*)': '<rootDir>/src/$1',
|
||||
},
|
||||
}
|
||||
|
20
src-ui/package-lock.json
generated
20
src-ui/package-lock.json
generated
@ -53,6 +53,7 @@
|
||||
"jest": "28.1.3",
|
||||
"jest-environment-jsdom": "^29.5.0",
|
||||
"jest-preset-angular": "^12.2.6",
|
||||
"jest-websocket-mock": "^2.4.0",
|
||||
"ts-node": "~10.9.1",
|
||||
"typescript": "~4.9.5",
|
||||
"wait-on": "^7.0.1"
|
||||
@ -12307,6 +12308,16 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-websocket-mock": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-websocket-mock/-/jest-websocket-mock-2.4.0.tgz",
|
||||
"integrity": "sha512-AOwyuRw6fgROXHxMOiTDl1/T4dh3fV4jDquha5N0csS/PNp742HeTZWPAuKppVRSQ8s3fUGgJHoyZT9JDO0hMA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"jest-diff": "^28.0.2",
|
||||
"mock-socket": "^9.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-worker": {
|
||||
"version": "28.1.3",
|
||||
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz",
|
||||
@ -13454,6 +13465,15 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/mock-socket": {
|
||||
"version": "9.2.1",
|
||||
"resolved": "https://registry.npmjs.org/mock-socket/-/mock-socket-9.2.1.tgz",
|
||||
"integrity": "sha512-aw9F9T9G2zpGipLLhSNh6ZpgUyUl4frcVmRN08uE1NWPWg43Wx6+sGPDbQ7E5iFZZDJW5b5bypMeAEHqTbIFag==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
|
@ -5,7 +5,7 @@
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"build": "ng build",
|
||||
"test": "ng test",
|
||||
"test": "ng test --no-watch --coverage",
|
||||
"lint": "ng lint"
|
||||
},
|
||||
"private": true,
|
||||
@ -55,6 +55,7 @@
|
||||
"jest": "28.1.3",
|
||||
"jest-environment-jsdom": "^29.5.0",
|
||||
"jest-preset-angular": "^12.2.6",
|
||||
"jest-websocket-mock": "^2.4.0",
|
||||
"ts-node": "~10.9.1",
|
||||
"typescript": "~4.9.5",
|
||||
"wait-on": "^7.0.1"
|
||||
|
@ -1,4 +1,59 @@
|
||||
import { jest } from '@jest/globals'
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
require('jest-preset-angular/setup-jest')
|
||||
}
|
||||
import '@angular/localize/init'
|
||||
import { TextEncoder, TextDecoder } from 'util'
|
||||
global.TextEncoder = TextEncoder
|
||||
global.TextDecoder = TextDecoder
|
||||
|
||||
import { registerLocaleData } from '@angular/common'
|
||||
import localeAr from '@angular/common/locales/ar'
|
||||
import localeBe from '@angular/common/locales/be'
|
||||
import localeCa from '@angular/common/locales/ca'
|
||||
import localeCs from '@angular/common/locales/cs'
|
||||
import localeDa from '@angular/common/locales/da'
|
||||
import localeDe from '@angular/common/locales/de'
|
||||
import localeEnGb from '@angular/common/locales/en-GB'
|
||||
import localeEs from '@angular/common/locales/es'
|
||||
import localeFi from '@angular/common/locales/fi'
|
||||
import localeFr from '@angular/common/locales/fr'
|
||||
import localeIt from '@angular/common/locales/it'
|
||||
import localeLb from '@angular/common/locales/lb'
|
||||
import localeNl from '@angular/common/locales/nl'
|
||||
import localePl from '@angular/common/locales/pl'
|
||||
import localePt from '@angular/common/locales/pt'
|
||||
import localeRo from '@angular/common/locales/ro'
|
||||
import localeRu from '@angular/common/locales/ru'
|
||||
import localeSl from '@angular/common/locales/sl'
|
||||
import localeSr from '@angular/common/locales/sr'
|
||||
import localeSv from '@angular/common/locales/sv'
|
||||
import localeTr from '@angular/common/locales/tr'
|
||||
import localeZh from '@angular/common/locales/zh'
|
||||
|
||||
registerLocaleData(localeAr)
|
||||
registerLocaleData(localeBe)
|
||||
registerLocaleData(localeCa)
|
||||
registerLocaleData(localeCs)
|
||||
registerLocaleData(localeDa)
|
||||
registerLocaleData(localeDe)
|
||||
registerLocaleData(localeEnGb)
|
||||
registerLocaleData(localeEs)
|
||||
registerLocaleData(localeFi)
|
||||
registerLocaleData(localeFr)
|
||||
registerLocaleData(localeIt)
|
||||
registerLocaleData(localeLb)
|
||||
registerLocaleData(localeNl)
|
||||
registerLocaleData(localePl)
|
||||
registerLocaleData(localePt, 'pt-BR')
|
||||
registerLocaleData(localePt, 'pt-PT')
|
||||
registerLocaleData(localeRo)
|
||||
registerLocaleData(localeRu)
|
||||
registerLocaleData(localeSl)
|
||||
registerLocaleData(localeSr)
|
||||
registerLocaleData(localeSv)
|
||||
registerLocaleData(localeTr)
|
||||
registerLocaleData(localeZh)
|
||||
|
||||
/* global mocks for jsdom */
|
||||
const mock = () => {
|
||||
@ -17,6 +72,8 @@ Object.defineProperty(window, 'getComputedStyle', {
|
||||
value: () => ['-webkit-appearance'],
|
||||
})
|
||||
|
||||
Object.defineProperty(window, 'ResizeObserver', { value: mock() })
|
||||
|
||||
Object.defineProperty(document.body.style, 'transform', {
|
||||
value: () => {
|
||||
return {
|
||||
|
@ -22,7 +22,7 @@ import {
|
||||
PermissionType,
|
||||
} from './services/permissions.service'
|
||||
|
||||
const routes: Routes = [
|
||||
export const routes: Routes = [
|
||||
{ path: '', redirectTo: 'dashboard', pathMatch: 'full' },
|
||||
{
|
||||
path: '',
|
||||
|
182
src-ui/src/app/app.component.spec.ts
Normal file
182
src-ui/src/app/app.component.spec.ts
Normal file
@ -0,0 +1,182 @@
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import {
|
||||
ComponentFixture,
|
||||
TestBed,
|
||||
discardPeriodicTasks,
|
||||
fakeAsync,
|
||||
tick,
|
||||
} from '@angular/core/testing'
|
||||
import { By } from '@angular/platform-browser'
|
||||
import { Router } from '@angular/router'
|
||||
import { RouterTestingModule } from '@angular/router/testing'
|
||||
import { NgxFileDropModule } from 'ngx-file-drop'
|
||||
import { TourService, TourNgBootstrapModule } from 'ngx-ui-tour-ng-bootstrap'
|
||||
import { Subject } from 'rxjs'
|
||||
import { routes } from './app-routing.module'
|
||||
import { AppComponent } from './app.component'
|
||||
import { ToastsComponent } from './components/common/toasts/toasts.component'
|
||||
import {
|
||||
ConsumerStatusService,
|
||||
FileStatus,
|
||||
} from './services/consumer-status.service'
|
||||
import { PermissionsService } from './services/permissions.service'
|
||||
import { ToastService, Toast } from './services/toast.service'
|
||||
import { UploadDocumentsService } from './services/upload-documents.service'
|
||||
import { SettingsService } from './services/settings.service'
|
||||
|
||||
describe('AppComponent', () => {
|
||||
let component: AppComponent
|
||||
let fixture: ComponentFixture<AppComponent>
|
||||
let tourService: TourService
|
||||
let consumerStatusService: ConsumerStatusService
|
||||
let permissionsService: PermissionsService
|
||||
let toastService: ToastService
|
||||
let router: Router
|
||||
let settingsService: SettingsService
|
||||
let uploadDocumentsService: UploadDocumentsService
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [AppComponent, ToastsComponent],
|
||||
providers: [],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
TourNgBootstrapModule,
|
||||
RouterTestingModule.withRoutes(routes),
|
||||
NgxFileDropModule,
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
tourService = TestBed.inject(TourService)
|
||||
consumerStatusService = TestBed.inject(ConsumerStatusService)
|
||||
permissionsService = TestBed.inject(PermissionsService)
|
||||
settingsService = TestBed.inject(SettingsService)
|
||||
toastService = TestBed.inject(ToastService)
|
||||
router = TestBed.inject(Router)
|
||||
uploadDocumentsService = TestBed.inject(UploadDocumentsService)
|
||||
fixture = TestBed.createComponent(AppComponent)
|
||||
component = fixture.componentInstance
|
||||
})
|
||||
|
||||
it('should initialize the tour service & toggle class on body for styling', fakeAsync(() => {
|
||||
jest.spyOn(console, 'warn').mockImplementation(() => {})
|
||||
fixture.detectChanges()
|
||||
const tourSpy = jest.spyOn(tourService, 'initialize')
|
||||
component.ngOnInit()
|
||||
expect(tourSpy).toHaveBeenCalled()
|
||||
tourService.start()
|
||||
expect(document.body.classList).toContain('tour-active')
|
||||
tourService.end()
|
||||
tick(500)
|
||||
expect(document.body.classList).not.toContain('tour-active')
|
||||
}))
|
||||
|
||||
it('should display toast on document consumed with link if user has access', () => {
|
||||
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
|
||||
let toast: Toast
|
||||
toastService.getToasts().subscribe((toasts) => (toast = toasts[0]))
|
||||
const toastSpy = jest.spyOn(toastService, 'show')
|
||||
const fileStatusSubject = new Subject<FileStatus>()
|
||||
jest
|
||||
.spyOn(consumerStatusService, 'onDocumentConsumptionFinished')
|
||||
.mockReturnValue(fileStatusSubject)
|
||||
component.ngOnInit()
|
||||
fileStatusSubject.next(new FileStatus())
|
||||
expect(toastSpy).toHaveBeenCalled()
|
||||
expect(toast.action).not.toBeUndefined()
|
||||
})
|
||||
|
||||
it('should display toast on document consumed without link if user does not have access', () => {
|
||||
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(false)
|
||||
let toast: Toast
|
||||
toastService.getToasts().subscribe((toasts) => (toast = toasts[0]))
|
||||
const toastSpy = jest.spyOn(toastService, 'show')
|
||||
const fileStatusSubject = new Subject<FileStatus>()
|
||||
jest
|
||||
.spyOn(consumerStatusService, 'onDocumentConsumptionFinished')
|
||||
.mockReturnValue(fileStatusSubject)
|
||||
component.ngOnInit()
|
||||
fileStatusSubject.next(new FileStatus())
|
||||
expect(toastSpy).toHaveBeenCalled()
|
||||
expect(toast.action).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should display toast on document added', () => {
|
||||
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
|
||||
const toastSpy = jest.spyOn(toastService, 'show')
|
||||
const fileStatusSubject = new Subject<FileStatus>()
|
||||
jest
|
||||
.spyOn(consumerStatusService, 'onDocumentDetected')
|
||||
.mockReturnValue(fileStatusSubject)
|
||||
component.ngOnInit()
|
||||
fileStatusSubject.next(new FileStatus())
|
||||
expect(toastSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should suppress dashboard notifications if set', () => {
|
||||
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
|
||||
jest.spyOn(settingsService, 'get').mockReturnValue(true)
|
||||
jest.spyOn(router, 'url', 'get').mockReturnValue('/dashboard')
|
||||
const toastSpy = jest.spyOn(toastService, 'show')
|
||||
const fileStatusSubject = new Subject<FileStatus>()
|
||||
jest
|
||||
.spyOn(consumerStatusService, 'onDocumentDetected')
|
||||
.mockReturnValue(fileStatusSubject)
|
||||
component.ngOnInit()
|
||||
fileStatusSubject.next(new FileStatus())
|
||||
expect(toastSpy).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should display toast on document failed', () => {
|
||||
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
|
||||
const toastSpy = jest.spyOn(toastService, 'showError')
|
||||
const fileStatusSubject = new Subject<FileStatus>()
|
||||
jest
|
||||
.spyOn(consumerStatusService, 'onDocumentConsumptionFailed')
|
||||
.mockReturnValue(fileStatusSubject)
|
||||
component.ngOnInit()
|
||||
fileStatusSubject.next(new FileStatus())
|
||||
expect(toastSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should disable drag-drop if on dashboard', () => {
|
||||
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
|
||||
jest.spyOn(router, 'url', 'get').mockReturnValueOnce('/dashboard')
|
||||
expect(component.dragDropEnabled).toBeFalsy()
|
||||
jest.spyOn(router, 'url', 'get').mockReturnValueOnce('/documents')
|
||||
expect(component.dragDropEnabled).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should enable drag-drop if user has permissions', () => {
|
||||
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
|
||||
expect(component.dragDropEnabled).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should disable drag-drop if user does not have permissions', () => {
|
||||
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(false)
|
||||
expect(component.dragDropEnabled).toBeFalsy()
|
||||
})
|
||||
|
||||
it('should support drag drop', fakeAsync(() => {
|
||||
expect(component.fileIsOver).toBeFalsy()
|
||||
component.fileOver()
|
||||
tick(1)
|
||||
fixture.detectChanges()
|
||||
expect(component.fileIsOver).toBeTruthy()
|
||||
const dropzone = fixture.debugElement.query(
|
||||
By.css('.global-dropzone-overlay')
|
||||
)
|
||||
expect(dropzone).not.toBeNull()
|
||||
component.fileLeave()
|
||||
tick(700)
|
||||
fixture.detectChanges()
|
||||
expect(dropzone.classes['hide']).toBeTruthy()
|
||||
// drop
|
||||
const toastSpy = jest.spyOn(toastService, 'show')
|
||||
const uploadSpy = jest.spyOn(uploadDocumentsService, 'uploadFiles')
|
||||
component.dropped([])
|
||||
tick(3000)
|
||||
expect(toastSpy).toHaveBeenCalled()
|
||||
expect(uploadSpy).toHaveBeenCalled()
|
||||
}))
|
||||
})
|
272
src-ui/src/app/components/app-frame/app-frame.component.spec.ts
Normal file
272
src-ui/src/app/components/app-frame/app-frame.component.spec.ts
Normal file
@ -0,0 +1,272 @@
|
||||
import {
|
||||
HttpClientTestingModule,
|
||||
HttpTestingController,
|
||||
} from '@angular/common/http/testing'
|
||||
import { AppFrameComponent } from './app-frame.component'
|
||||
import {
|
||||
ComponentFixture,
|
||||
TestBed,
|
||||
fakeAsync,
|
||||
tick,
|
||||
} from '@angular/core/testing'
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { BrowserModule } from '@angular/platform-browser'
|
||||
import { RouterTestingModule } from '@angular/router/testing'
|
||||
import { SettingsService } from 'src/app/services/settings.service'
|
||||
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
|
||||
import { PermissionsService } from 'src/app/services/permissions.service'
|
||||
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
|
||||
import { RemoteVersionService } from 'src/app/services/rest/remote-version.service'
|
||||
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { of } from 'rxjs'
|
||||
import { ToastService } from 'src/app/services/toast.service'
|
||||
import { environment } from 'src/environments/environment'
|
||||
import { OpenDocumentsService } from 'src/app/services/open-documents.service'
|
||||
import { ActivatedRoute, Router } from '@angular/router'
|
||||
import { DocumentDetailComponent } from '../document-detail/document-detail.component'
|
||||
import { SearchService } from 'src/app/services/rest/search.service'
|
||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
||||
import { FILTER_FULLTEXT_QUERY } from 'src/app/data/filter-rule-type'
|
||||
import { routes } from 'src/app/app-routing.module'
|
||||
import { PermissionsGuard } from 'src/app/guards/permissions.guard'
|
||||
|
||||
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 openDocumentsService: OpenDocumentsService
|
||||
let searchService: SearchService
|
||||
let documentListViewService: DocumentListViewService
|
||||
let router: Router
|
||||
let savedViewSpy
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [AppFrameComponent, IfPermissionsDirective],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
BrowserModule,
|
||||
RouterTestingModule.withRoutes(routes),
|
||||
NgbModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
],
|
||||
providers: [
|
||||
SettingsService,
|
||||
SavedViewService,
|
||||
PermissionsService,
|
||||
RemoteVersionService,
|
||||
IfPermissionsDirective,
|
||||
ToastService,
|
||||
OpenDocumentsService,
|
||||
SearchService,
|
||||
{
|
||||
provide: ActivatedRoute,
|
||||
useValue: {
|
||||
firstChild: {
|
||||
component: DocumentDetailComponent,
|
||||
},
|
||||
snapshot: {
|
||||
firstChild: {
|
||||
component: DocumentDetailComponent,
|
||||
params: {
|
||||
id: document.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
PermissionsGuard,
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
settingsService = TestBed.inject(SettingsService)
|
||||
const savedViewService = TestBed.inject(SavedViewService)
|
||||
permissionsService = TestBed.inject(PermissionsService)
|
||||
remoteVersionService = TestBed.inject(RemoteVersionService)
|
||||
toastService = TestBed.inject(ToastService)
|
||||
openDocumentsService = TestBed.inject(OpenDocumentsService)
|
||||
searchService = TestBed.inject(SearchService)
|
||||
documentListViewService = TestBed.inject(DocumentListViewService)
|
||||
router = TestBed.inject(Router)
|
||||
|
||||
jest
|
||||
.spyOn(settingsService, 'displayName', 'get')
|
||||
.mockReturnValue('Hello World')
|
||||
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
|
||||
|
||||
savedViewSpy = jest.spyOn(savedViewService, 'initialize')
|
||||
|
||||
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 collapsable 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 call autocomplete endpoint on input', fakeAsync(() => {
|
||||
const autocompleteSpy = jest.spyOn(searchService, 'autocomplete')
|
||||
component.searchAutoComplete(of('hello')).subscribe()
|
||||
tick(250)
|
||||
expect(autocompleteSpy).toHaveBeenCalled()
|
||||
|
||||
component.searchAutoComplete(of('hello world 1')).subscribe()
|
||||
tick(250)
|
||||
expect(autocompleteSpy).toHaveBeenCalled()
|
||||
}))
|
||||
|
||||
it('should support reset search field', () => {
|
||||
const resetSpy = jest.spyOn(component, 'resetSearchField')
|
||||
const input = (fixture.nativeElement as HTMLDivElement).querySelector(
|
||||
'input'
|
||||
) as HTMLInputElement
|
||||
input.dispatchEvent(new KeyboardEvent('keyup', { key: 'Escape' }))
|
||||
expect(resetSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should support choosing a search item', () => {
|
||||
expect(component.searchField.value).toEqual('')
|
||||
component.itemSelected({ item: 'hello', preventDefault: () => true })
|
||||
expect(component.searchField.value).toEqual('hello ')
|
||||
component.itemSelected({ item: 'world', preventDefault: () => true })
|
||||
expect(component.searchField.value).toEqual('hello world ')
|
||||
})
|
||||
|
||||
it('should navigate via quickFilter on search', () => {
|
||||
const str = 'hello world '
|
||||
component.searchField.patchValue(str)
|
||||
const qfSpy = jest.spyOn(documentListViewService, 'quickFilter')
|
||||
component.search()
|
||||
expect(qfSpy).toHaveBeenCalledWith([
|
||||
{
|
||||
rule_type: FILTER_FULLTEXT_QUERY,
|
||||
value: str.trim(),
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
@ -53,7 +53,7 @@ export class AppFrameComponent
|
||||
public settingsService: SettingsService,
|
||||
public tasksService: TasksService,
|
||||
private readonly toastService: ToastService,
|
||||
private permissionsService: PermissionsService
|
||||
permissionsService: PermissionsService
|
||||
) {
|
||||
super()
|
||||
|
||||
@ -75,7 +75,7 @@ export class AppFrameComponent
|
||||
}
|
||||
|
||||
versionString = `${environment.appTitle} ${environment.version}`
|
||||
appRemoteVersion
|
||||
appRemoteVersion: AppRemoteVersion
|
||||
|
||||
isMenuCollapsed: boolean = true
|
||||
|
||||
@ -103,7 +103,7 @@ export class AppFrameComponent
|
||||
this.toastService.showError(
|
||||
$localize`An error occurred while saving settings.`
|
||||
)
|
||||
console.log(error)
|
||||
console.warn(error)
|
||||
},
|
||||
})
|
||||
}
|
||||
@ -236,7 +236,7 @@ export class AppFrameComponent
|
||||
this.toastService.showError(
|
||||
$localize`An error occurred while saving update checking settings.`
|
||||
)
|
||||
console.log(error)
|
||||
console.warn(error)
|
||||
},
|
||||
})
|
||||
if (enable) {
|
||||
|
@ -0,0 +1,43 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { ClearableBadgeComponent } from './clearable-badge.component'
|
||||
|
||||
describe('ClearableBadgeComponent', () => {
|
||||
let component: ClearableBadgeComponent
|
||||
let fixture: ComponentFixture<ClearableBadgeComponent>
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ClearableBadgeComponent],
|
||||
}).compileComponents()
|
||||
|
||||
fixture = TestBed.createComponent(ClearableBadgeComponent)
|
||||
component = fixture.componentInstance
|
||||
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
it('should support selected', () => {
|
||||
component.selected = true
|
||||
expect(component.active).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should support numbered', () => {
|
||||
component.number = 3
|
||||
fixture.detectChanges()
|
||||
expect(component.active).toBeTruthy()
|
||||
expect((fixture.nativeElement as HTMLDivElement).textContent).toContain('3')
|
||||
})
|
||||
|
||||
it('should support selected', () => {
|
||||
let clearedResult
|
||||
component.selected = true
|
||||
fixture.detectChanges()
|
||||
component.cleared.subscribe((clear) => {
|
||||
clearedResult = clear
|
||||
})
|
||||
fixture.nativeElement
|
||||
.querySelectorAll('button')[0]
|
||||
.dispatchEvent(new MouseEvent('click'))
|
||||
expect(clearedResult).toBeTruthy()
|
||||
})
|
||||
})
|
@ -0,0 +1,99 @@
|
||||
import {
|
||||
ComponentFixture,
|
||||
TestBed,
|
||||
discardPeriodicTasks,
|
||||
fakeAsync,
|
||||
tick,
|
||||
} from '@angular/core/testing'
|
||||
import { ConfirmDialogComponent } from './confirm-dialog.component'
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
|
||||
import { Subject } from 'rxjs'
|
||||
|
||||
describe('ConfirmDialogComponent', () => {
|
||||
let component: ConfirmDialogComponent
|
||||
let modal: NgbActiveModal
|
||||
let fixture: ComponentFixture<ConfirmDialogComponent>
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ConfirmDialogComponent, SafeHtmlPipe],
|
||||
providers: [NgbActiveModal, SafeHtmlPipe],
|
||||
imports: [],
|
||||
}).compileComponents()
|
||||
|
||||
modal = TestBed.inject(NgbActiveModal)
|
||||
|
||||
fixture = TestBed.createComponent(ConfirmDialogComponent)
|
||||
component = fixture.componentInstance
|
||||
component.title = 'Confirm delete'
|
||||
component.messageBold = 'Do you really want to delete document file.pdf?'
|
||||
component.message =
|
||||
'The files for this document will be deleted permanently. This operation cannot be undone.'
|
||||
component.btnClass = 'btn-danger'
|
||||
component.btnCaption = 'Delete document'
|
||||
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
it('should support alternative', () => {
|
||||
let alternativeClickedResult
|
||||
let alternativeSubjectResult
|
||||
component.alternativeClicked.subscribe((result) => {
|
||||
alternativeClickedResult = true
|
||||
})
|
||||
component.alternative()
|
||||
// with subject
|
||||
const subject = new Subject<boolean>()
|
||||
component.alternativeSubject = subject
|
||||
subject.asObservable().subscribe((result) => {
|
||||
alternativeSubjectResult = result
|
||||
})
|
||||
component.alternative()
|
||||
expect(alternativeClickedResult).toBeTruthy()
|
||||
expect(alternativeSubjectResult).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should support confirm', () => {
|
||||
let confirmClickedResult
|
||||
let confirmSubjectResult
|
||||
component.confirmClicked.subscribe((result) => {
|
||||
confirmClickedResult = true
|
||||
})
|
||||
component.confirm()
|
||||
// with subject
|
||||
const subject = new Subject<boolean>()
|
||||
component.confirmSubject = subject
|
||||
subject.asObservable().subscribe((result) => {
|
||||
confirmSubjectResult = result
|
||||
})
|
||||
component.confirm()
|
||||
expect(confirmClickedResult).toBeTruthy()
|
||||
expect(confirmSubjectResult).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should support cancel & close modal', () => {
|
||||
let confirmSubjectResult
|
||||
const closeModalSpy = jest.spyOn(modal, 'close')
|
||||
component.cancel()
|
||||
const subject = new Subject<boolean>()
|
||||
component.confirmSubject = subject
|
||||
subject.asObservable().subscribe((result) => {
|
||||
confirmSubjectResult = result
|
||||
})
|
||||
component.cancel()
|
||||
// with subject
|
||||
expect(closeModalSpy).toHaveBeenCalled()
|
||||
expect(confirmSubjectResult).toBeFalsy()
|
||||
})
|
||||
|
||||
it('should support delay confirm', fakeAsync(() => {
|
||||
component.confirmButtonEnabled = false
|
||||
component.delayConfirm(1)
|
||||
expect(component.confirmButtonEnabled).toBeFalsy()
|
||||
tick(1500)
|
||||
fixture.detectChanges()
|
||||
expect(component.confirmButtonEnabled).toBeTruthy()
|
||||
discardPeriodicTasks()
|
||||
}))
|
||||
})
|
@ -0,0 +1,141 @@
|
||||
import {
|
||||
ComponentFixture,
|
||||
TestBed,
|
||||
fakeAsync,
|
||||
tick,
|
||||
} from '@angular/core/testing'
|
||||
let fixture: ComponentFixture<DateDropdownComponent>
|
||||
import {
|
||||
DateDropdownComponent,
|
||||
DateSelection,
|
||||
RelativeDate,
|
||||
} from './date-dropdown.component'
|
||||
import {
|
||||
HttpClientTestingModule,
|
||||
HttpTestingController,
|
||||
} from '@angular/common/http/testing'
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { SettingsService } from 'src/app/services/settings.service'
|
||||
import { ClearableBadgeComponent } from '../clearable-badge/clearable-badge.component'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
|
||||
import { DatePipe } from '@angular/common'
|
||||
|
||||
describe('DateDropdownComponent', () => {
|
||||
let component: DateDropdownComponent
|
||||
let httpTestingController: HttpTestingController
|
||||
let settingsService: SettingsService
|
||||
let settingsSpy
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
DateDropdownComponent,
|
||||
ClearableBadgeComponent,
|
||||
CustomDatePipe,
|
||||
],
|
||||
providers: [SettingsService, CustomDatePipe, DatePipe],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
NgbModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
httpTestingController = TestBed.inject(HttpTestingController)
|
||||
settingsService = TestBed.inject(SettingsService)
|
||||
settingsSpy = jest.spyOn(settingsService, 'getLocalizedDateInputFormat')
|
||||
|
||||
fixture = TestBed.createComponent(DateDropdownComponent)
|
||||
component = fixture.componentInstance
|
||||
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
it('should use a localized date placeholder', () => {
|
||||
expect(component.datePlaceHolder).toEqual('mm/dd/yyyy')
|
||||
expect(settingsSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should support date input, emit change', fakeAsync(() => {
|
||||
let result: string
|
||||
component.dateAfterChange.subscribe((date) => (result = date))
|
||||
const input: HTMLInputElement = fixture.nativeElement.querySelector('input')
|
||||
input.value = '5/30/2023'
|
||||
input.dispatchEvent(new Event('change'))
|
||||
tick(500)
|
||||
expect(result).not.toBeNull()
|
||||
}))
|
||||
|
||||
it('should support date select, emit datesSet change', fakeAsync(() => {
|
||||
let result: DateSelection
|
||||
component.datesSet.subscribe((date) => (result = date))
|
||||
const input: HTMLInputElement = fixture.nativeElement.querySelector('input')
|
||||
input.value = '5/30/2023'
|
||||
input.dispatchEvent(new Event('dateSelect'))
|
||||
tick(500)
|
||||
expect(result).not.toBeNull()
|
||||
}))
|
||||
|
||||
it('should support relative dates', fakeAsync(() => {
|
||||
let result: DateSelection
|
||||
component.datesSet.subscribe((date) => (result = date))
|
||||
component.setRelativeDate(null)
|
||||
component.setRelativeDate(RelativeDate.LAST_7_DAYS)
|
||||
tick(500)
|
||||
expect(result).toEqual({
|
||||
after: null,
|
||||
before: null,
|
||||
relativeDateID: RelativeDate.LAST_7_DAYS,
|
||||
})
|
||||
}))
|
||||
|
||||
it('should support report if active', () => {
|
||||
component.relativeDate = RelativeDate.LAST_7_DAYS
|
||||
expect(component.isActive).toBeTruthy()
|
||||
component.relativeDate = null
|
||||
component.dateAfter = '2023-05-30'
|
||||
expect(component.isActive).toBeTruthy()
|
||||
component.dateAfter = null
|
||||
component.dateBefore = '2023-05-30'
|
||||
expect(component.isActive).toBeTruthy()
|
||||
component.dateBefore = null
|
||||
expect(component.isActive).toBeFalsy()
|
||||
})
|
||||
|
||||
it('should support reset', () => {
|
||||
component.dateAfter = '2023-05-30'
|
||||
component.reset()
|
||||
expect(component.dateAfter).toBeNull()
|
||||
})
|
||||
|
||||
it('should support clearAfter', () => {
|
||||
component.dateAfter = '2023-05-30'
|
||||
component.clearAfter()
|
||||
expect(component.dateAfter).toBeNull()
|
||||
})
|
||||
|
||||
it('should support clearBefore', () => {
|
||||
component.dateBefore = '2023-05-30'
|
||||
component.clearBefore()
|
||||
expect(component.dateBefore).toBeNull()
|
||||
})
|
||||
|
||||
it('should limit keyboard events', () => {
|
||||
const input: HTMLInputElement = fixture.nativeElement.querySelector('input')
|
||||
let event: KeyboardEvent = new KeyboardEvent('keypress', {
|
||||
key: '9',
|
||||
})
|
||||
let eventSpy = jest.spyOn(event, 'preventDefault')
|
||||
input.dispatchEvent(event)
|
||||
expect(eventSpy).not.toHaveBeenCalled()
|
||||
|
||||
event = new KeyboardEvent('keypress', {
|
||||
key: '{',
|
||||
})
|
||||
eventSpy = jest.spyOn(event, 'preventDefault')
|
||||
input.dispatchEvent(event)
|
||||
expect(eventSpy).toHaveBeenCalled()
|
||||
})
|
||||
})
|
@ -1,4 +1,3 @@
|
||||
import { formatDate } from '@angular/common'
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
|
@ -0,0 +1,55 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { CorrespondentEditDialogComponent } from './correspondent-edit-dialog.component'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { EditDialogMode } from '../edit-dialog.component'
|
||||
import { IfOwnerDirective } from 'src/app/directives/if-owner.directive'
|
||||
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
||||
import { SelectComponent } from '../../input/select/select.component'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { TextComponent } from '../../input/text/text.component'
|
||||
import { NgSelectModule } from '@ng-select/ng-select'
|
||||
import { PermissionsFormComponent } from '../../input/permissions/permissions-form/permissions-form.component'
|
||||
|
||||
describe('CorrespondentEditDialogComponent', () => {
|
||||
let component: CorrespondentEditDialogComponent
|
||||
let fixture: ComponentFixture<CorrespondentEditDialogComponent>
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
CorrespondentEditDialogComponent,
|
||||
IfPermissionsDirective,
|
||||
IfOwnerDirective,
|
||||
SelectComponent,
|
||||
TextComponent,
|
||||
PermissionsFormComponent,
|
||||
],
|
||||
providers: [NgbActiveModal],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NgSelectModule,
|
||||
NgbModule,
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
fixture = TestBed.createComponent(CorrespondentEditDialogComponent)
|
||||
component = fixture.componentInstance
|
||||
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
it('should support create and edit modes', () => {
|
||||
component.dialogMode = EditDialogMode.CREATE
|
||||
const createTitleSpy = jest.spyOn(component, 'getCreateTitle')
|
||||
const editTitleSpy = jest.spyOn(component, 'getEditTitle')
|
||||
fixture.detectChanges()
|
||||
expect(createTitleSpy).toHaveBeenCalled()
|
||||
expect(editTitleSpy).not.toHaveBeenCalled()
|
||||
component.dialogMode = EditDialogMode.EDIT
|
||||
fixture.detectChanges()
|
||||
expect(editTitleSpy).toHaveBeenCalled()
|
||||
})
|
||||
})
|
@ -0,0 +1,55 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { DocumentTypeEditDialogComponent } from './document-type-edit-dialog.component'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { EditDialogMode } from '../edit-dialog.component'
|
||||
import { IfOwnerDirective } from 'src/app/directives/if-owner.directive'
|
||||
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
||||
import { SelectComponent } from '../../input/select/select.component'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { TextComponent } from '../../input/text/text.component'
|
||||
import { NgSelectModule } from '@ng-select/ng-select'
|
||||
import { PermissionsFormComponent } from '../../input/permissions/permissions-form/permissions-form.component'
|
||||
|
||||
describe('DocumentTypeEditDialogComponent', () => {
|
||||
let component: DocumentTypeEditDialogComponent
|
||||
let fixture: ComponentFixture<DocumentTypeEditDialogComponent>
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
DocumentTypeEditDialogComponent,
|
||||
IfPermissionsDirective,
|
||||
IfOwnerDirective,
|
||||
SelectComponent,
|
||||
TextComponent,
|
||||
PermissionsFormComponent,
|
||||
],
|
||||
providers: [NgbActiveModal],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NgSelectModule,
|
||||
NgbModule,
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
fixture = TestBed.createComponent(DocumentTypeEditDialogComponent)
|
||||
component = fixture.componentInstance
|
||||
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
it('should support create and edit modes', () => {
|
||||
component.dialogMode = EditDialogMode.CREATE
|
||||
const createTitleSpy = jest.spyOn(component, 'getCreateTitle')
|
||||
const editTitleSpy = jest.spyOn(component, 'getEditTitle')
|
||||
fixture.detectChanges()
|
||||
expect(createTitleSpy).toHaveBeenCalled()
|
||||
expect(editTitleSpy).not.toHaveBeenCalled()
|
||||
component.dialogMode = EditDialogMode.EDIT
|
||||
fixture.detectChanges()
|
||||
expect(editTitleSpy).toHaveBeenCalled()
|
||||
})
|
||||
})
|
@ -0,0 +1,234 @@
|
||||
import {
|
||||
HttpClientTestingModule,
|
||||
HttpTestingController,
|
||||
} from '@angular/common/http/testing'
|
||||
import { Component } from '@angular/core'
|
||||
import {
|
||||
ComponentFixture,
|
||||
TestBed,
|
||||
fakeAsync,
|
||||
tick,
|
||||
} from '@angular/core/testing'
|
||||
import {
|
||||
FormGroup,
|
||||
FormControl,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
} from '@angular/forms'
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { PaperlessTag } from 'src/app/data/paperless-tag'
|
||||
import { TagService } from 'src/app/services/rest/tag.service'
|
||||
import { UserService } from 'src/app/services/rest/user.service'
|
||||
import { SettingsService } from 'src/app/services/settings.service'
|
||||
import { EditDialogComponent, EditDialogMode } from './edit-dialog.component'
|
||||
import {
|
||||
DEFAULT_MATCHING_ALGORITHM,
|
||||
MATCH_ALL,
|
||||
MATCH_AUTO,
|
||||
MATCH_NONE,
|
||||
} from 'src/app/data/matching-model'
|
||||
import { of } from 'rxjs'
|
||||
import { environment } from 'src/environments/environment'
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<div>
|
||||
<h4 class="modal-title" id="modal-basic-title">{{ getTitle() }}</h4>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
class TestComponent extends EditDialogComponent<PaperlessTag> {
|
||||
constructor(
|
||||
service: TagService,
|
||||
activeModal: NgbActiveModal,
|
||||
userService: UserService,
|
||||
settingsService: SettingsService
|
||||
) {
|
||||
super(service, activeModal, userService, settingsService)
|
||||
}
|
||||
|
||||
getForm(): FormGroup<any> {
|
||||
return new FormGroup({
|
||||
name: new FormControl(''),
|
||||
color: new FormControl(''),
|
||||
is_inbox_tag: new FormControl(false),
|
||||
permissions_form: new FormControl(null),
|
||||
matching_algorithm: new FormControl(DEFAULT_MATCHING_ALGORITHM),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const currentUser = {
|
||||
id: 99,
|
||||
username: 'user99',
|
||||
}
|
||||
|
||||
const permissions = {
|
||||
view: {
|
||||
users: [11],
|
||||
groups: [],
|
||||
},
|
||||
change: {
|
||||
users: [],
|
||||
groups: [2],
|
||||
},
|
||||
}
|
||||
|
||||
const tag = {
|
||||
id: 1,
|
||||
name: 'Tag 1',
|
||||
color: '#fff000',
|
||||
is_inbox_tag: false,
|
||||
matching_algorithm: MATCH_AUTO,
|
||||
owner: 10,
|
||||
permissions,
|
||||
}
|
||||
|
||||
describe('EditDialogComponent', () => {
|
||||
let component: TestComponent
|
||||
let fixture: ComponentFixture<TestComponent>
|
||||
let tagService: TagService
|
||||
let activeModal: NgbActiveModal
|
||||
let httpTestingController: HttpTestingController
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [TestComponent],
|
||||
providers: [
|
||||
NgbActiveModal,
|
||||
{
|
||||
provide: UserService,
|
||||
useValue: {
|
||||
listAll: () =>
|
||||
of({
|
||||
results: [
|
||||
{
|
||||
id: 13,
|
||||
username: 'user1',
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: SettingsService,
|
||||
useValue: {
|
||||
currentUser,
|
||||
},
|
||||
},
|
||||
TagService,
|
||||
],
|
||||
imports: [HttpClientTestingModule, FormsModule, ReactiveFormsModule],
|
||||
}).compileComponents()
|
||||
|
||||
tagService = TestBed.inject(TagService)
|
||||
activeModal = TestBed.inject(NgbActiveModal)
|
||||
httpTestingController = TestBed.inject(HttpTestingController)
|
||||
|
||||
fixture = TestBed.createComponent(TestComponent)
|
||||
component = fixture.componentInstance
|
||||
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
it('should interpolate object permissions', () => {
|
||||
component.object = tag
|
||||
component.dialogMode = EditDialogMode.EDIT
|
||||
component.ngOnInit()
|
||||
|
||||
expect(component.objectForm.get('permissions_form').value).toEqual({
|
||||
owner: tag.owner,
|
||||
set_permissions: permissions,
|
||||
})
|
||||
})
|
||||
|
||||
it('should delay close enabled', fakeAsync(() => {
|
||||
expect(component.closeEnabled).toBeFalsy()
|
||||
component.ngOnInit()
|
||||
tick(100)
|
||||
expect(component.closeEnabled).toBeTruthy()
|
||||
}))
|
||||
|
||||
it('should set default owner when in create mode', () => {
|
||||
component.dialogMode = EditDialogMode.CREATE
|
||||
component.ngOnInit()
|
||||
expect(component.objectForm.get('permissions_form').value.owner).toEqual(
|
||||
currentUser.id
|
||||
)
|
||||
// cover optional chaining
|
||||
component.objectForm.removeControl('permissions_form')
|
||||
component.ngOnInit()
|
||||
})
|
||||
|
||||
it('should detect if pattern required', () => {
|
||||
expect(component.patternRequired).toBeFalsy()
|
||||
component.objectForm.get('matching_algorithm').setValue(MATCH_AUTO)
|
||||
expect(component.patternRequired).toBeFalsy()
|
||||
component.objectForm.get('matching_algorithm').setValue(MATCH_NONE)
|
||||
expect(component.patternRequired).toBeFalsy()
|
||||
component.objectForm.get('matching_algorithm').setValue(MATCH_ALL)
|
||||
expect(component.patternRequired).toBeTruthy()
|
||||
// coverage
|
||||
component.objectForm = null
|
||||
expect(component.patternRequired).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should support create and edit modes', () => {
|
||||
component.dialogMode = EditDialogMode.CREATE
|
||||
const createTitleSpy = jest.spyOn(component, 'getCreateTitle')
|
||||
const editTitleSpy = jest.spyOn(component, 'getEditTitle')
|
||||
fixture.detectChanges()
|
||||
expect(createTitleSpy).toHaveBeenCalled()
|
||||
expect(editTitleSpy).not.toHaveBeenCalled()
|
||||
component.dialogMode = EditDialogMode.EDIT
|
||||
fixture.detectChanges()
|
||||
expect(editTitleSpy).toHaveBeenCalled()
|
||||
// coverage
|
||||
component.dialogMode = null
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
it('should close on cancel', () => {
|
||||
const closeSpy = jest.spyOn(activeModal, 'close')
|
||||
component.cancel()
|
||||
expect(closeSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should update an object on save in edit mode', () => {
|
||||
const updateSpy = jest.spyOn(tagService, 'update')
|
||||
component.dialogMode = EditDialogMode.EDIT
|
||||
component.save()
|
||||
expect(updateSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should create an object on save in edit mode', () => {
|
||||
const createSpy = jest.spyOn(tagService, 'create')
|
||||
component.dialogMode = EditDialogMode.CREATE
|
||||
component.save()
|
||||
expect(createSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should close on successful save', () => {
|
||||
const closeSpy = jest.spyOn(activeModal, 'close')
|
||||
const successSpy = jest.spyOn(component.succeeded, 'emit')
|
||||
component.save()
|
||||
httpTestingController.expectOne(`${environment.apiBaseUrl}tags/`).flush({})
|
||||
expect(closeSpy).toHaveBeenCalled()
|
||||
expect(successSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not close on failed save', () => {
|
||||
const closeSpy = jest.spyOn(activeModal, 'close')
|
||||
const failedSpy = jest.spyOn(component.failed, 'next')
|
||||
component.save()
|
||||
httpTestingController
|
||||
.expectOne(`${environment.apiBaseUrl}tags/`)
|
||||
.flush('error', {
|
||||
status: 500,
|
||||
statusText: 'error',
|
||||
})
|
||||
expect(closeSpy).not.toHaveBeenCalled()
|
||||
expect(failedSpy).toHaveBeenCalled()
|
||||
expect(component.error).toEqual('error')
|
||||
})
|
||||
})
|
@ -15,6 +15,11 @@ import { UserService } from 'src/app/services/rest/user.service'
|
||||
import { PermissionsFormObject } from '../input/permissions/permissions-form/permissions-form.component'
|
||||
import { SettingsService } from 'src/app/services/settings.service'
|
||||
|
||||
export enum EditDialogMode {
|
||||
CREATE = 0,
|
||||
EDIT = 1,
|
||||
}
|
||||
|
||||
@Directive()
|
||||
export abstract class EditDialogComponent<
|
||||
T extends ObjectWithPermissions | ObjectWithId
|
||||
@ -30,7 +35,7 @@ export abstract class EditDialogComponent<
|
||||
users: PaperlessUser[]
|
||||
|
||||
@Input()
|
||||
dialogMode: string = 'create'
|
||||
dialogMode: EditDialogMode = EditDialogMode.CREATE
|
||||
|
||||
@Input()
|
||||
object: T
|
||||
@ -71,7 +76,7 @@ export abstract class EditDialogComponent<
|
||||
|
||||
this.userService.listAll().subscribe((r) => {
|
||||
this.users = r.results
|
||||
if (this.dialogMode === 'create') {
|
||||
if (this.dialogMode === EditDialogMode.CREATE) {
|
||||
this.objectForm.get('permissions_form')?.setValue({
|
||||
owner: this.settingsService.currentUser.id,
|
||||
})
|
||||
@ -87,15 +92,11 @@ export abstract class EditDialogComponent<
|
||||
return $localize`Edit item`
|
||||
}
|
||||
|
||||
getSaveErrorMessage(error: string) {
|
||||
return $localize`Could not save element: ${error}`
|
||||
}
|
||||
|
||||
getTitle() {
|
||||
switch (this.dialogMode) {
|
||||
case 'create':
|
||||
case EditDialogMode.CREATE:
|
||||
return this.getCreateTitle()
|
||||
case 'edit':
|
||||
case EditDialogMode.EDIT:
|
||||
return this.getEditTitle()
|
||||
default:
|
||||
break
|
||||
@ -127,10 +128,10 @@ export abstract class EditDialogComponent<
|
||||
var newObject = Object.assign(Object.assign({}, this.object), formValues)
|
||||
var serverResponse: Observable<T>
|
||||
switch (this.dialogMode) {
|
||||
case 'create':
|
||||
case EditDialogMode.CREATE:
|
||||
serverResponse = this.service.create(newObject)
|
||||
break
|
||||
case 'edit':
|
||||
case EditDialogMode.EDIT:
|
||||
serverResponse = this.service.update(newObject)
|
||||
default:
|
||||
break
|
||||
|
@ -0,0 +1,57 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { EditDialogMode } from '../edit-dialog.component'
|
||||
import { IfOwnerDirective } from 'src/app/directives/if-owner.directive'
|
||||
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
||||
import { SelectComponent } from '../../input/select/select.component'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { TextComponent } from '../../input/text/text.component'
|
||||
import { NgSelectModule } from '@ng-select/ng-select'
|
||||
import { PermissionsFormComponent } from '../../input/permissions/permissions-form/permissions-form.component'
|
||||
import { GroupEditDialogComponent } from './group-edit-dialog.component'
|
||||
import { PermissionsSelectComponent } from '../../permissions-select/permissions-select.component'
|
||||
|
||||
describe('GroupEditDialogComponent', () => {
|
||||
let component: GroupEditDialogComponent
|
||||
let fixture: ComponentFixture<GroupEditDialogComponent>
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
GroupEditDialogComponent,
|
||||
IfPermissionsDirective,
|
||||
IfOwnerDirective,
|
||||
SelectComponent,
|
||||
TextComponent,
|
||||
PermissionsFormComponent,
|
||||
PermissionsSelectComponent,
|
||||
],
|
||||
providers: [NgbActiveModal],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NgSelectModule,
|
||||
NgbModule,
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
fixture = TestBed.createComponent(GroupEditDialogComponent)
|
||||
component = fixture.componentInstance
|
||||
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
it('should support create and edit modes', () => {
|
||||
component.dialogMode = EditDialogMode.CREATE
|
||||
const createTitleSpy = jest.spyOn(component, 'getCreateTitle')
|
||||
const editTitleSpy = jest.spyOn(component, 'getEditTitle')
|
||||
fixture.detectChanges()
|
||||
expect(createTitleSpy).toHaveBeenCalled()
|
||||
expect(editTitleSpy).not.toHaveBeenCalled()
|
||||
component.dialogMode = EditDialogMode.EDIT
|
||||
fixture.detectChanges()
|
||||
expect(editTitleSpy).toHaveBeenCalled()
|
||||
})
|
||||
})
|
@ -0,0 +1,117 @@
|
||||
import {
|
||||
ComponentFixture,
|
||||
TestBed,
|
||||
discardPeriodicTasks,
|
||||
fakeAsync,
|
||||
tick,
|
||||
} from '@angular/core/testing'
|
||||
import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import {
|
||||
HttpClientTestingModule,
|
||||
HttpTestingController,
|
||||
} from '@angular/common/http/testing'
|
||||
import { EditDialogMode } from '../edit-dialog.component'
|
||||
import { IfOwnerDirective } from 'src/app/directives/if-owner.directive'
|
||||
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
||||
import { SelectComponent } from '../../input/select/select.component'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { TextComponent } from '../../input/text/text.component'
|
||||
import { NgSelectModule } from '@ng-select/ng-select'
|
||||
import { PermissionsFormComponent } from '../../input/permissions/permissions-form/permissions-form.component'
|
||||
import { MailAccountEditDialogComponent } from './mail-account-edit-dialog.component'
|
||||
import { PasswordComponent } from '../../input/password/password.component'
|
||||
import { CheckComponent } from '../../input/check/check.component'
|
||||
import { IMAPSecurity } from 'src/app/data/paperless-mail-account'
|
||||
import { environment } from 'src/environments/environment'
|
||||
|
||||
describe('MailAccountEditDialogComponent', () => {
|
||||
let component: MailAccountEditDialogComponent
|
||||
let fixture: ComponentFixture<MailAccountEditDialogComponent>
|
||||
let httpController: HttpTestingController
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
MailAccountEditDialogComponent,
|
||||
IfPermissionsDirective,
|
||||
IfOwnerDirective,
|
||||
SelectComponent,
|
||||
TextComponent,
|
||||
CheckComponent,
|
||||
PermissionsFormComponent,
|
||||
PasswordComponent,
|
||||
],
|
||||
providers: [NgbActiveModal],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NgSelectModule,
|
||||
NgbModule,
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
httpController = TestBed.inject(HttpTestingController)
|
||||
|
||||
fixture = TestBed.createComponent(MailAccountEditDialogComponent)
|
||||
component = fixture.componentInstance
|
||||
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
it('should support create and edit modes', () => {
|
||||
component.dialogMode = EditDialogMode.CREATE
|
||||
const createTitleSpy = jest.spyOn(component, 'getCreateTitle')
|
||||
const editTitleSpy = jest.spyOn(component, 'getEditTitle')
|
||||
fixture.detectChanges()
|
||||
expect(createTitleSpy).toHaveBeenCalled()
|
||||
expect(editTitleSpy).not.toHaveBeenCalled()
|
||||
component.dialogMode = EditDialogMode.EDIT
|
||||
fixture.detectChanges()
|
||||
expect(editTitleSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should support test mail account and show appropriate expiring alert', fakeAsync(() => {
|
||||
component.object = {
|
||||
name: 'example',
|
||||
imap_server: 'imap.example.com',
|
||||
username: 'user',
|
||||
password: 'pass',
|
||||
imap_port: 443,
|
||||
imap_security: IMAPSecurity.SSL,
|
||||
is_token: false,
|
||||
}
|
||||
|
||||
// success
|
||||
component.test()
|
||||
httpController
|
||||
.expectOne(`${environment.apiBaseUrl}mail_accounts/test/`)
|
||||
.flush({ success: true })
|
||||
fixture.detectChanges()
|
||||
expect(fixture.nativeElement.textContent).toContain(
|
||||
'Successfully connected'
|
||||
)
|
||||
tick(6000)
|
||||
fixture.detectChanges()
|
||||
expect(fixture.nativeElement.textContent).not.toContain(
|
||||
'Successfully connected'
|
||||
)
|
||||
|
||||
// not success
|
||||
component.test()
|
||||
httpController
|
||||
.expectOne(`${environment.apiBaseUrl}mail_accounts/test/`)
|
||||
.flush({ success: false })
|
||||
fixture.detectChanges()
|
||||
expect(fixture.nativeElement.textContent).toContain('Unable to connect')
|
||||
|
||||
// error
|
||||
component.test()
|
||||
httpController
|
||||
.expectOne(`${environment.apiBaseUrl}mail_accounts/test/`)
|
||||
.flush({}, { status: 500, statusText: 'error' })
|
||||
fixture.detectChanges()
|
||||
expect(fixture.nativeElement.textContent).toContain('Unable to connect')
|
||||
tick(6000)
|
||||
}))
|
||||
})
|
@ -0,0 +1,113 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { EditDialogMode } from '../edit-dialog.component'
|
||||
import { IfOwnerDirective } from 'src/app/directives/if-owner.directive'
|
||||
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
||||
import { SelectComponent } from '../../input/select/select.component'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { TextComponent } from '../../input/text/text.component'
|
||||
import { NgSelectModule } from '@ng-select/ng-select'
|
||||
import { PermissionsFormComponent } from '../../input/permissions/permissions-form/permissions-form.component'
|
||||
import { MailRuleEditDialogComponent } from './mail-rule-edit-dialog.component'
|
||||
import { NumberComponent } from '../../input/number/number.component'
|
||||
import { TagsComponent } from '../../input/tags/tags.component'
|
||||
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
|
||||
import { MailAccountService } from 'src/app/services/rest/mail-account.service'
|
||||
import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
|
||||
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
|
||||
import { of } from 'rxjs'
|
||||
import {
|
||||
MailAction,
|
||||
MailMetadataCorrespondentOption,
|
||||
} from 'src/app/data/paperless-mail-rule'
|
||||
|
||||
describe('MailRuleEditDialogComponent', () => {
|
||||
let component: MailRuleEditDialogComponent
|
||||
let fixture: ComponentFixture<MailRuleEditDialogComponent>
|
||||
let accountService: MailAccountService
|
||||
let correspondentService: CorrespondentService
|
||||
let documentTypeService: DocumentTypeService
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
MailRuleEditDialogComponent,
|
||||
IfPermissionsDirective,
|
||||
IfOwnerDirective,
|
||||
SelectComponent,
|
||||
TextComponent,
|
||||
PermissionsFormComponent,
|
||||
NumberComponent,
|
||||
TagsComponent,
|
||||
SafeHtmlPipe,
|
||||
],
|
||||
providers: [
|
||||
NgbActiveModal,
|
||||
{
|
||||
provide: MailAccountService,
|
||||
useValue: {
|
||||
listAll: () => of([]),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: CorrespondentService,
|
||||
useValue: {
|
||||
listAll: () => of([]),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: DocumentTypeService,
|
||||
useValue: {
|
||||
listAll: () => of([]),
|
||||
},
|
||||
},
|
||||
],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NgSelectModule,
|
||||
NgbModule,
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
fixture = TestBed.createComponent(MailRuleEditDialogComponent)
|
||||
component = fixture.componentInstance
|
||||
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
it('should support create and edit modes', () => {
|
||||
component.dialogMode = EditDialogMode.CREATE
|
||||
const createTitleSpy = jest.spyOn(component, 'getCreateTitle')
|
||||
const editTitleSpy = jest.spyOn(component, 'getEditTitle')
|
||||
fixture.detectChanges()
|
||||
expect(createTitleSpy).toHaveBeenCalled()
|
||||
expect(editTitleSpy).not.toHaveBeenCalled()
|
||||
component.dialogMode = EditDialogMode.EDIT
|
||||
fixture.detectChanges()
|
||||
expect(editTitleSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should support optional fields', () => {
|
||||
expect(component.showCorrespondentField).toBeFalsy()
|
||||
component.objectForm
|
||||
.get('assign_correspondent_from')
|
||||
.setValue(MailMetadataCorrespondentOption.FromCustom)
|
||||
expect(component.showCorrespondentField).toBeTruthy()
|
||||
|
||||
expect(component.showActionParamField).toBeFalsy()
|
||||
component.objectForm.get('action').setValue(MailAction.Move)
|
||||
expect(component.showActionParamField).toBeTruthy()
|
||||
component.objectForm.get('action').setValue('')
|
||||
expect(component.showActionParamField).toBeFalsy()
|
||||
component.objectForm.get('action').setValue(MailAction.Tag)
|
||||
expect(component.showActionParamField).toBeTruthy()
|
||||
|
||||
// coverage of optional chaining
|
||||
component.objectForm = null
|
||||
expect(component.showCorrespondentField).toBeFalsy()
|
||||
expect(component.showActionParamField).toBeFalsy()
|
||||
})
|
||||
})
|
@ -0,0 +1,57 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { EditDialogMode } from '../edit-dialog.component'
|
||||
import { IfOwnerDirective } from 'src/app/directives/if-owner.directive'
|
||||
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
||||
import { SelectComponent } from '../../input/select/select.component'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { TextComponent } from '../../input/text/text.component'
|
||||
import { NgSelectModule } from '@ng-select/ng-select'
|
||||
import { PermissionsFormComponent } from '../../input/permissions/permissions-form/permissions-form.component'
|
||||
import { StoragePathEditDialogComponent } from './storage-path-edit-dialog.component'
|
||||
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
|
||||
|
||||
describe('StoragePathEditDialogComponent', () => {
|
||||
let component: StoragePathEditDialogComponent
|
||||
let fixture: ComponentFixture<StoragePathEditDialogComponent>
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
StoragePathEditDialogComponent,
|
||||
IfPermissionsDirective,
|
||||
IfOwnerDirective,
|
||||
SelectComponent,
|
||||
TextComponent,
|
||||
PermissionsFormComponent,
|
||||
SafeHtmlPipe,
|
||||
],
|
||||
providers: [NgbActiveModal],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NgSelectModule,
|
||||
NgbModule,
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
fixture = TestBed.createComponent(StoragePathEditDialogComponent)
|
||||
component = fixture.componentInstance
|
||||
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
it('should support create and edit modes', () => {
|
||||
component.dialogMode = EditDialogMode.CREATE
|
||||
const createTitleSpy = jest.spyOn(component, 'getCreateTitle')
|
||||
const editTitleSpy = jest.spyOn(component, 'getEditTitle')
|
||||
fixture.detectChanges()
|
||||
expect(createTitleSpy).toHaveBeenCalled()
|
||||
expect(editTitleSpy).not.toHaveBeenCalled()
|
||||
component.dialogMode = EditDialogMode.EDIT
|
||||
fixture.detectChanges()
|
||||
expect(editTitleSpy).toHaveBeenCalled()
|
||||
})
|
||||
})
|
@ -0,0 +1,59 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { EditDialogMode } from '../edit-dialog.component'
|
||||
import { IfOwnerDirective } from 'src/app/directives/if-owner.directive'
|
||||
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
||||
import { SelectComponent } from '../../input/select/select.component'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { TextComponent } from '../../input/text/text.component'
|
||||
import { NgSelectModule } from '@ng-select/ng-select'
|
||||
import { PermissionsFormComponent } from '../../input/permissions/permissions-form/permissions-form.component'
|
||||
import { TagEditDialogComponent } from './tag-edit-dialog.component'
|
||||
import { ColorComponent } from '../../input/color/color.component'
|
||||
import { CheckComponent } from '../../input/check/check.component'
|
||||
|
||||
describe('TagEditDialogComponent', () => {
|
||||
let component: TagEditDialogComponent
|
||||
let fixture: ComponentFixture<TagEditDialogComponent>
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
TagEditDialogComponent,
|
||||
IfPermissionsDirective,
|
||||
IfOwnerDirective,
|
||||
SelectComponent,
|
||||
TextComponent,
|
||||
PermissionsFormComponent,
|
||||
ColorComponent,
|
||||
CheckComponent,
|
||||
],
|
||||
providers: [NgbActiveModal],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NgSelectModule,
|
||||
NgbModule,
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
fixture = TestBed.createComponent(TagEditDialogComponent)
|
||||
component = fixture.componentInstance
|
||||
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
it('should support create and edit modes', () => {
|
||||
component.dialogMode = EditDialogMode.CREATE
|
||||
const createTitleSpy = jest.spyOn(component, 'getCreateTitle')
|
||||
const editTitleSpy = jest.spyOn(component, 'getEditTitle')
|
||||
fixture.detectChanges()
|
||||
expect(createTitleSpy).toHaveBeenCalled()
|
||||
expect(editTitleSpy).not.toHaveBeenCalled()
|
||||
component.dialogMode = EditDialogMode.EDIT
|
||||
fixture.detectChanges()
|
||||
expect(editTitleSpy).toHaveBeenCalled()
|
||||
})
|
||||
})
|
@ -0,0 +1,115 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { EditDialogMode } from '../edit-dialog.component'
|
||||
import { IfOwnerDirective } from 'src/app/directives/if-owner.directive'
|
||||
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
||||
import { SelectComponent } from '../../input/select/select.component'
|
||||
import {
|
||||
AbstractControl,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
} from '@angular/forms'
|
||||
import { TextComponent } from '../../input/text/text.component'
|
||||
import { NgSelectModule } from '@ng-select/ng-select'
|
||||
import { PermissionsFormComponent } from '../../input/permissions/permissions-form/permissions-form.component'
|
||||
import { UserEditDialogComponent } from './user-edit-dialog.component'
|
||||
import { PasswordComponent } from '../../input/password/password.component'
|
||||
import { PermissionsSelectComponent } from '../../permissions-select/permissions-select.component'
|
||||
import { GroupService } from 'src/app/services/rest/group.service'
|
||||
import { of } from 'rxjs'
|
||||
|
||||
describe('UserEditDialogComponent', () => {
|
||||
let component: UserEditDialogComponent
|
||||
let fixture: ComponentFixture<UserEditDialogComponent>
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
UserEditDialogComponent,
|
||||
IfPermissionsDirective,
|
||||
IfOwnerDirective,
|
||||
SelectComponent,
|
||||
TextComponent,
|
||||
PasswordComponent,
|
||||
PermissionsFormComponent,
|
||||
PermissionsSelectComponent,
|
||||
],
|
||||
providers: [
|
||||
NgbActiveModal,
|
||||
{
|
||||
provide: GroupService,
|
||||
useValue: {
|
||||
listAll: () =>
|
||||
of({
|
||||
results: [
|
||||
{
|
||||
id: 1,
|
||||
permissions: ['dummy_perms'],
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
},
|
||||
],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NgSelectModule,
|
||||
NgbModule,
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
fixture = TestBed.createComponent(UserEditDialogComponent)
|
||||
component = fixture.componentInstance
|
||||
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
it('should support create and edit modes', () => {
|
||||
component.dialogMode = EditDialogMode.CREATE
|
||||
const createTitleSpy = jest.spyOn(component, 'getCreateTitle')
|
||||
const editTitleSpy = jest.spyOn(component, 'getEditTitle')
|
||||
fixture.detectChanges()
|
||||
expect(createTitleSpy).toHaveBeenCalled()
|
||||
expect(editTitleSpy).not.toHaveBeenCalled()
|
||||
component.dialogMode = EditDialogMode.EDIT
|
||||
fixture.detectChanges()
|
||||
expect(editTitleSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should disable user permissions select on toggle superuser', () => {
|
||||
const control: AbstractControl =
|
||||
component.objectForm.get('user_permissions')
|
||||
expect(control.disabled).toBeFalsy()
|
||||
component.objectForm.get('is_superuser').setValue(true)
|
||||
component.onToggleSuperUser()
|
||||
expect(control.disabled).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should update inherited permissions', () => {
|
||||
component.objectForm.get('groups').setValue(null)
|
||||
expect(component.inheritedPermissions).toEqual([])
|
||||
component.objectForm.get('groups').setValue([1])
|
||||
expect(component.inheritedPermissions).toEqual(['dummy_perms'])
|
||||
component.objectForm.get('groups').setValue([2])
|
||||
expect(component.inheritedPermissions).toEqual([])
|
||||
})
|
||||
|
||||
it('should detect whether password was changed in form on save', () => {
|
||||
component.objectForm.get('password').setValue(null)
|
||||
component.save()
|
||||
expect(component.passwordIsSet).toBeFalsy()
|
||||
|
||||
// unchanged pw
|
||||
component.objectForm.get('password').setValue('*******')
|
||||
component.save()
|
||||
expect(component.passwordIsSet).toBeFalsy()
|
||||
|
||||
// unchanged pw
|
||||
component.objectForm.get('password').setValue('helloworld')
|
||||
component.save()
|
||||
expect(component.passwordIsSet).toBeTruthy()
|
||||
})
|
||||
})
|
@ -34,7 +34,7 @@
|
||||
<div *ngIf="selectionModel.items" class="items" #buttonItems>
|
||||
<ng-container *ngFor="let item of selectionModel.itemsSorted | filter: filterText; let i = index">
|
||||
<app-toggleable-dropdown-button
|
||||
*ngIf="allowSelectNone || item.id" [item]="item" [hideCount]="hideCount(item)" [state]="selectionModel.get(item.id)" [count]="getUpdatedDocumentCount(item.id)" (toggle)="selectionModel.toggle(item.id)" (exclude)="excludeClicked(item.id)" (click)="setButtonItemIndex(i)" [disabled]="disabled">
|
||||
*ngIf="allowSelectNone || item.id" [item]="item" [hideCount]="hideCount(item)" [state]="selectionModel.get(item.id)" [count]="getUpdatedDocumentCount(item.id)" (toggle)="selectionModel.toggle(item.id)" (exclude)="excludeClicked(item.id)" (click)="setButtonItemIndex(i - 1)" [disabled]="disabled">
|
||||
</app-toggleable-dropdown-button>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
@ -0,0 +1,487 @@
|
||||
import {
|
||||
ComponentFixture,
|
||||
TestBed,
|
||||
fakeAsync,
|
||||
tick,
|
||||
} from '@angular/core/testing'
|
||||
import {
|
||||
ChangedItems,
|
||||
FilterableDropdownComponent,
|
||||
FilterableDropdownSelectionModel,
|
||||
Intersection,
|
||||
LogicalOperator,
|
||||
} from './filterable-dropdown.component'
|
||||
import { FilterPipe } from 'src/app/pipes/filter.pipe'
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { PaperlessTag } from 'src/app/data/paperless-tag'
|
||||
import {
|
||||
DEFAULT_MATCHING_ALGORITHM,
|
||||
MATCH_ALL,
|
||||
} from 'src/app/data/matching-model'
|
||||
import {
|
||||
ToggleableDropdownButtonComponent,
|
||||
ToggleableItemState,
|
||||
} from './toggleable-dropdown-button/toggleable-dropdown-button.component'
|
||||
import { TagComponent } from '../tag/tag.component'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { ClearableBadgeComponent } from '../clearable-badge/clearable-badge.component'
|
||||
|
||||
const items: PaperlessTag[] = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Tag1',
|
||||
is_inbox_tag: false,
|
||||
matching_algorithm: DEFAULT_MATCHING_ALGORITHM,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Tag2',
|
||||
is_inbox_tag: true,
|
||||
matching_algorithm: MATCH_ALL,
|
||||
match: 'str',
|
||||
},
|
||||
]
|
||||
|
||||
const nullItem = {
|
||||
id: null,
|
||||
name: 'Not assigned',
|
||||
}
|
||||
|
||||
let selectionModel: FilterableDropdownSelectionModel
|
||||
|
||||
describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () => {
|
||||
let component: FilterableDropdownComponent
|
||||
let fixture: ComponentFixture<FilterableDropdownComponent>
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
FilterableDropdownComponent,
|
||||
FilterPipe,
|
||||
ToggleableDropdownButtonComponent,
|
||||
TagComponent,
|
||||
ClearableBadgeComponent,
|
||||
],
|
||||
providers: [FilterPipe],
|
||||
imports: [NgbModule, FormsModule, ReactiveFormsModule],
|
||||
}).compileComponents()
|
||||
|
||||
fixture = TestBed.createComponent(FilterableDropdownComponent)
|
||||
component = fixture.componentInstance
|
||||
selectionModel = new FilterableDropdownSelectionModel()
|
||||
})
|
||||
|
||||
it('should sanitize title', () => {
|
||||
expect(component.name).toBeNull()
|
||||
component.title = 'Foo Bar'
|
||||
expect(component.name).toEqual('foo_bar')
|
||||
})
|
||||
|
||||
it('should support reset', () => {
|
||||
component.items = items
|
||||
component.selectionModel = selectionModel
|
||||
selectionModel.set(items[0].id, ToggleableItemState.Selected)
|
||||
expect(selectionModel.getSelectedItems()).toHaveLength(1)
|
||||
expect(selectionModel.isDirty()).toBeTruthy()
|
||||
component.reset()
|
||||
expect(selectionModel.getSelectedItems()).toHaveLength(0)
|
||||
expect(selectionModel.isDirty()).toBeFalsy()
|
||||
})
|
||||
|
||||
it('should report document counts', () => {
|
||||
component.documentCounts = [
|
||||
{
|
||||
id: items[0].id,
|
||||
document_count: 12,
|
||||
},
|
||||
]
|
||||
expect(component.getUpdatedDocumentCount(items[0].id)).toEqual(12)
|
||||
expect(component.getUpdatedDocumentCount(items[1].id)).toBeUndefined() // coverate of optional chaining
|
||||
})
|
||||
|
||||
it('should emit change when items selected', () => {
|
||||
component.items = items
|
||||
component.selectionModel = selectionModel
|
||||
let newModel: FilterableDropdownSelectionModel
|
||||
component.selectionModelChange.subscribe((model) => (newModel = model))
|
||||
expect(newModel).toBeUndefined()
|
||||
|
||||
selectionModel.set(items[0].id, ToggleableItemState.Selected)
|
||||
expect(selectionModel.isDirty()).toBeTruthy()
|
||||
expect(newModel.getSelectedItems()).toEqual([items[0]])
|
||||
expect(newModel.getExcludedItems()).toEqual([])
|
||||
|
||||
selectionModel.set(items[0].id, ToggleableItemState.NotSelected)
|
||||
expect(newModel.getSelectedItems()).toEqual([])
|
||||
|
||||
expect(component.items).toEqual([nullItem, ...items])
|
||||
})
|
||||
|
||||
it('should emit change when items excluded', () => {
|
||||
component.items = items
|
||||
component.selectionModel = selectionModel
|
||||
let newModel: FilterableDropdownSelectionModel
|
||||
component.selectionModelChange.subscribe((model) => (newModel = model))
|
||||
expect(newModel).toBeUndefined()
|
||||
selectionModel.toggle(items[0].id)
|
||||
expect(newModel.getSelectedItems()).toEqual([items[0]])
|
||||
})
|
||||
|
||||
it('should emit change when items excluded', () => {
|
||||
component.items = items
|
||||
component.selectionModel = selectionModel
|
||||
let newModel: FilterableDropdownSelectionModel
|
||||
component.selectionModelChange.subscribe((model) => (newModel = model))
|
||||
|
||||
selectionModel.set(items[0].id, ToggleableItemState.Excluded)
|
||||
expect(newModel.getSelectedItems()).toEqual([])
|
||||
expect(newModel.getExcludedItems()).toEqual([items[0]])
|
||||
|
||||
selectionModel.set(items[0].id, ToggleableItemState.NotSelected)
|
||||
expect(newModel.getSelectedItems()).toEqual([])
|
||||
expect(newModel.getExcludedItems()).toEqual([])
|
||||
})
|
||||
|
||||
it('should exclude items when excluded and not editing', () => {
|
||||
component.items = items
|
||||
component.manyToOne = true
|
||||
component.selectionModel = selectionModel
|
||||
selectionModel.set(items[0].id, ToggleableItemState.Selected)
|
||||
component.excludeClicked(items[0].id)
|
||||
expect(selectionModel.getSelectedItems()).toEqual([])
|
||||
expect(selectionModel.getExcludedItems()).toEqual([items[0]])
|
||||
})
|
||||
|
||||
it('should toggle when items excluded and editing', () => {
|
||||
component.items = items
|
||||
component.manyToOne = true
|
||||
component.editing = true
|
||||
component.selectionModel = selectionModel
|
||||
selectionModel.set(items[0].id, ToggleableItemState.NotSelected)
|
||||
component.excludeClicked(items[0].id)
|
||||
expect(selectionModel.getSelectedItems()).toEqual([items[0]])
|
||||
expect(selectionModel.getExcludedItems()).toEqual([])
|
||||
})
|
||||
|
||||
it('should hide count for item if adding will increase size of set', () => {
|
||||
component.items = items
|
||||
component.manyToOne = true
|
||||
component.selectionModel = selectionModel
|
||||
expect(component.hideCount(items[0])).toBeFalsy()
|
||||
selectionModel.logicalOperator = LogicalOperator.Or
|
||||
expect(component.hideCount(items[0])).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should enforce single select when editing', () => {
|
||||
component.editing = true
|
||||
component.items = items
|
||||
component.selectionModel = selectionModel
|
||||
let newModel: FilterableDropdownSelectionModel
|
||||
component.selectionModelChange.subscribe((model) => (newModel = model))
|
||||
|
||||
expect(selectionModel.singleSelect).toEqual(true)
|
||||
selectionModel.toggle(items[0].id)
|
||||
selectionModel.toggle(items[1].id)
|
||||
expect(newModel.getSelectedItems()).toEqual([items[1]])
|
||||
})
|
||||
|
||||
it('should support manyToOne selecting', () => {
|
||||
component.items = items
|
||||
selectionModel.manyToOne = false
|
||||
component.selectionModel = selectionModel
|
||||
component.manyToOne = true
|
||||
expect(component.manyToOne).toBeTruthy()
|
||||
let newModel: FilterableDropdownSelectionModel
|
||||
component.selectionModelChange.subscribe((model) => (newModel = model))
|
||||
|
||||
expect(selectionModel.singleSelect).toEqual(false)
|
||||
selectionModel.toggle(items[0].id)
|
||||
selectionModel.toggle(items[1].id)
|
||||
expect(newModel.getSelectedItems()).toEqual([items[0], items[1]])
|
||||
})
|
||||
|
||||
it('should dynamically enable / disable modifier toggle', () => {
|
||||
component.items = items
|
||||
component.selectionModel = selectionModel
|
||||
expect(component.modifierToggleEnabled).toBeTruthy()
|
||||
selectionModel.toggle(null)
|
||||
expect(component.modifierToggleEnabled).toBeFalsy()
|
||||
component.manyToOne = true
|
||||
expect(component.modifierToggleEnabled).toBeFalsy()
|
||||
selectionModel.toggle(items[0].id)
|
||||
selectionModel.toggle(items[1].id)
|
||||
expect(component.modifierToggleEnabled).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should apply changes and close when apply button clicked', () => {
|
||||
component.items = items
|
||||
component.editing = true
|
||||
component.selectionModel = selectionModel
|
||||
fixture.nativeElement
|
||||
.querySelector('button')
|
||||
.dispatchEvent(new MouseEvent('click')) // open
|
||||
selectionModel.toggle(items[0].id)
|
||||
fixture.detectChanges()
|
||||
expect(component.modelIsDirty).toBeTruthy()
|
||||
let applyResult: ChangedItems
|
||||
const closeSpy = jest.spyOn(component.dropdown, 'close')
|
||||
component.apply.subscribe((result) => (applyResult = result))
|
||||
const applyButton = Array.from(
|
||||
(fixture.nativeElement as HTMLDivElement).querySelectorAll('button')
|
||||
).find((b) => b.textContent.includes('Apply'))
|
||||
applyButton.dispatchEvent(new MouseEvent('click'))
|
||||
expect(closeSpy).toHaveBeenCalled()
|
||||
expect(applyResult).toEqual({ itemsToAdd: [items[0]], itemsToRemove: [] })
|
||||
})
|
||||
|
||||
it('should apply on close if enabled', () => {
|
||||
component.items = items
|
||||
component.editing = true
|
||||
component.applyOnClose = true
|
||||
component.selectionModel = selectionModel
|
||||
fixture.nativeElement
|
||||
.querySelector('button')
|
||||
.dispatchEvent(new MouseEvent('click')) // open
|
||||
selectionModel.toggle(items[0].id)
|
||||
fixture.detectChanges()
|
||||
expect(component.modelIsDirty).toBeTruthy()
|
||||
let applyResult: ChangedItems
|
||||
component.apply.subscribe((result) => (applyResult = result))
|
||||
component.dropdown.close()
|
||||
expect(applyResult).toEqual({ itemsToAdd: [items[0]], itemsToRemove: [] })
|
||||
})
|
||||
|
||||
it('should focus text filter on open, support filtering, clear on close', fakeAsync(() => {
|
||||
component.items = items
|
||||
fixture.nativeElement
|
||||
.querySelector('button')
|
||||
.dispatchEvent(new MouseEvent('click')) // open
|
||||
fixture.detectChanges()
|
||||
tick(100)
|
||||
expect(document.activeElement).toEqual(
|
||||
component.listFilterTextInput.nativeElement
|
||||
)
|
||||
expect(
|
||||
Array.from(
|
||||
(fixture.nativeElement as HTMLDivElement).querySelectorAll('button')
|
||||
).filter((b) => b.textContent.includes('Tag'))
|
||||
).toHaveLength(2)
|
||||
component.filterText = 'Tag2'
|
||||
fixture.detectChanges()
|
||||
expect(
|
||||
Array.from(
|
||||
(fixture.nativeElement as HTMLDivElement).querySelectorAll('button')
|
||||
).filter((b) => b.textContent.includes('Tag'))
|
||||
).toHaveLength(1)
|
||||
component.dropdown.close()
|
||||
expect(component.filterText).toHaveLength(0)
|
||||
}))
|
||||
|
||||
it('should toggle & close on enter inside filter field if 1 item remains', fakeAsync(() => {
|
||||
component.items = items
|
||||
expect(component.selectionModel.getSelectedItems()).toEqual([])
|
||||
fixture.nativeElement
|
||||
.querySelector('button')
|
||||
.dispatchEvent(new MouseEvent('click')) // open
|
||||
fixture.detectChanges()
|
||||
tick(100)
|
||||
component.filterText = 'Tag2'
|
||||
fixture.detectChanges()
|
||||
const closeSpy = jest.spyOn(component.dropdown, 'close')
|
||||
component.listFilterTextInput.nativeElement.dispatchEvent(
|
||||
new KeyboardEvent('keyup', { key: 'Enter' })
|
||||
)
|
||||
expect(component.selectionModel.getSelectedItems()).toEqual([items[1]])
|
||||
tick(300)
|
||||
expect(closeSpy).toHaveBeenCalled()
|
||||
}))
|
||||
|
||||
it('should apply & close on enter inside filter field if 1 item remains if editing', fakeAsync(() => {
|
||||
component.items = items
|
||||
component.editing = true
|
||||
let applyResult: ChangedItems
|
||||
component.apply.subscribe((result) => (applyResult = result))
|
||||
expect(component.selectionModel.getSelectedItems()).toEqual([])
|
||||
fixture.nativeElement
|
||||
.querySelector('button')
|
||||
.dispatchEvent(new MouseEvent('click')) // open
|
||||
fixture.detectChanges()
|
||||
tick(100)
|
||||
component.filterText = 'Tag2'
|
||||
fixture.detectChanges()
|
||||
component.listFilterTextInput.nativeElement.dispatchEvent(
|
||||
new KeyboardEvent('keyup', { key: 'Enter' })
|
||||
)
|
||||
expect(component.selectionModel.getSelectedItems()).toEqual([items[1]])
|
||||
tick(300)
|
||||
expect(applyResult).toEqual({ itemsToAdd: [items[1]], itemsToRemove: [] })
|
||||
}))
|
||||
|
||||
it('should support arrow keyboard navigation', fakeAsync(() => {
|
||||
component.items = items
|
||||
fixture.nativeElement
|
||||
.querySelector('button')
|
||||
.dispatchEvent(new MouseEvent('click')) // open
|
||||
fixture.detectChanges()
|
||||
tick(100)
|
||||
const filterInputEl: HTMLInputElement =
|
||||
component.listFilterTextInput.nativeElement
|
||||
expect(document.activeElement).toEqual(filterInputEl)
|
||||
const itemButtons = Array.from(
|
||||
(fixture.nativeElement as HTMLDivElement).querySelectorAll('button')
|
||||
).filter((b) => b.textContent.includes('Tag'))
|
||||
filterInputEl.dispatchEvent(
|
||||
new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true })
|
||||
)
|
||||
expect(document.activeElement).toEqual(itemButtons[0])
|
||||
itemButtons[0].dispatchEvent(
|
||||
new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true })
|
||||
)
|
||||
expect(document.activeElement).toEqual(itemButtons[1])
|
||||
itemButtons[1].dispatchEvent(
|
||||
new KeyboardEvent('keydown', { key: 'ArrowUp', bubbles: true })
|
||||
)
|
||||
expect(document.activeElement).toEqual(itemButtons[0])
|
||||
itemButtons[0].dispatchEvent(
|
||||
new KeyboardEvent('keydown', { key: 'ArrowUp', bubbles: true })
|
||||
)
|
||||
expect(document.activeElement).toEqual(filterInputEl)
|
||||
filterInputEl.value = 'foo'
|
||||
component.filterText = 'foo'
|
||||
|
||||
// dont move focus if we're traversing the field
|
||||
filterInputEl.selectionStart = 1
|
||||
expect(document.activeElement).toEqual(filterInputEl)
|
||||
|
||||
// now we're at end, so move focus
|
||||
filterInputEl.selectionStart = 3
|
||||
filterInputEl.dispatchEvent(
|
||||
new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true })
|
||||
)
|
||||
expect(document.activeElement).toEqual(itemButtons[0])
|
||||
}))
|
||||
|
||||
it('should support arrow keyboard navigation after tab keyboard navigation', fakeAsync(() => {
|
||||
component.items = items
|
||||
fixture.nativeElement
|
||||
.querySelector('button')
|
||||
.dispatchEvent(new MouseEvent('click')) // open
|
||||
fixture.detectChanges()
|
||||
tick(100)
|
||||
const filterInputEl: HTMLInputElement =
|
||||
component.listFilterTextInput.nativeElement
|
||||
expect(document.activeElement).toEqual(filterInputEl)
|
||||
const itemButtons = Array.from(
|
||||
(fixture.nativeElement as HTMLDivElement).querySelectorAll('button')
|
||||
).filter((b) => b.textContent.includes('Tag'))
|
||||
filterInputEl.dispatchEvent(
|
||||
new KeyboardEvent('keydown', { key: 'Tab', bubbles: true })
|
||||
)
|
||||
itemButtons[0].focus() // normally handled by browser
|
||||
itemButtons[0].dispatchEvent(
|
||||
new KeyboardEvent('keydown', { key: 'Tab', bubbles: true })
|
||||
)
|
||||
itemButtons[1].focus() // normally handled by browser
|
||||
itemButtons[1].dispatchEvent(
|
||||
new KeyboardEvent('keydown', {
|
||||
key: 'Tab',
|
||||
shiftKey: true,
|
||||
bubbles: true,
|
||||
})
|
||||
)
|
||||
itemButtons[0].focus() // normally handled by browser
|
||||
itemButtons[0].dispatchEvent(
|
||||
new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true })
|
||||
)
|
||||
expect(document.activeElement).toEqual(itemButtons[1])
|
||||
}))
|
||||
|
||||
it('should support arrow keyboard navigation after click', fakeAsync(() => {
|
||||
component.items = items
|
||||
fixture.nativeElement
|
||||
.querySelector('button')
|
||||
.dispatchEvent(new MouseEvent('click')) // open
|
||||
fixture.detectChanges()
|
||||
tick(100)
|
||||
const filterInputEl: HTMLInputElement =
|
||||
component.listFilterTextInput.nativeElement
|
||||
expect(document.activeElement).toEqual(filterInputEl)
|
||||
const itemButtons = Array.from(
|
||||
(fixture.nativeElement as HTMLDivElement).querySelectorAll('button')
|
||||
).filter((b) => b.textContent.includes('Tag'))
|
||||
fixture.nativeElement
|
||||
.querySelector('app-toggleable-dropdown-button')
|
||||
.dispatchEvent(new MouseEvent('click'))
|
||||
itemButtons[0].focus() // normally handled by browser
|
||||
expect(document.activeElement).toEqual(itemButtons[0])
|
||||
itemButtons[0].dispatchEvent(
|
||||
new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true })
|
||||
)
|
||||
expect(document.activeElement).toEqual(itemButtons[1])
|
||||
}))
|
||||
|
||||
it('should toggle logical operator', fakeAsync(() => {
|
||||
component.items = items
|
||||
component.manyToOne = true
|
||||
selectionModel.set(items[0].id, ToggleableItemState.Selected)
|
||||
selectionModel.set(items[1].id, ToggleableItemState.Selected)
|
||||
component.selectionModel = selectionModel
|
||||
let changedResult: FilterableDropdownSelectionModel
|
||||
component.selectionModelChange.subscribe(
|
||||
(result) => (changedResult = result)
|
||||
)
|
||||
|
||||
fixture.nativeElement
|
||||
.querySelector('button')
|
||||
.dispatchEvent(new MouseEvent('click')) // open
|
||||
fixture.detectChanges()
|
||||
tick(100)
|
||||
|
||||
expect(component.modifierToggleEnabled).toBeTruthy()
|
||||
const operatorButtons: HTMLInputElement[] = Array.from(
|
||||
(fixture.nativeElement as HTMLDivElement).querySelectorAll('input')
|
||||
).filter((b) => ['and', 'or'].includes(b.value))
|
||||
expect(operatorButtons[0].checked).toBeTruthy()
|
||||
operatorButtons[1].dispatchEvent(new MouseEvent('click'))
|
||||
fixture.detectChanges()
|
||||
expect(selectionModel.logicalOperator).toEqual(LogicalOperator.Or)
|
||||
expect(changedResult.logicalOperator).toEqual(LogicalOperator.Or)
|
||||
}))
|
||||
|
||||
it('should toggle intersection include / exclude', fakeAsync(() => {
|
||||
component.items = items
|
||||
selectionModel.set(items[0].id, ToggleableItemState.Selected)
|
||||
selectionModel.set(items[1].id, ToggleableItemState.Selected)
|
||||
component.selectionModel = selectionModel
|
||||
let changedResult: FilterableDropdownSelectionModel
|
||||
component.selectionModelChange.subscribe(
|
||||
(result) => (changedResult = result)
|
||||
)
|
||||
|
||||
fixture.nativeElement
|
||||
.querySelector('button')
|
||||
.dispatchEvent(new MouseEvent('click')) // open
|
||||
fixture.detectChanges()
|
||||
tick(100)
|
||||
|
||||
expect(component.modifierToggleEnabled).toBeTruthy()
|
||||
const intersectionButtons: HTMLInputElement[] = Array.from(
|
||||
(fixture.nativeElement as HTMLDivElement).querySelectorAll('input')
|
||||
).filter((b) => ['include', 'exclude'].includes(b.value))
|
||||
expect(intersectionButtons[0].checked).toBeTruthy()
|
||||
intersectionButtons[1].dispatchEvent(new MouseEvent('click'))
|
||||
fixture.detectChanges()
|
||||
expect(selectionModel.intersection).toEqual(Intersection.Exclude)
|
||||
expect(changedResult.intersection).toEqual(Intersection.Exclude)
|
||||
expect(changedResult.getSelectedItems()).toEqual([])
|
||||
expect(changedResult.getExcludedItems()).toEqual(items)
|
||||
}))
|
||||
|
||||
it('FilterableDropdownSelectionModel should sort items by state', () => {
|
||||
component.items = items
|
||||
component.selectionModel = selectionModel
|
||||
selectionModel.toggle(items[1].id)
|
||||
selectionModel.apply()
|
||||
expect(selectionModel.itemsSorted).toEqual([nullItem, items[1], items[0]])
|
||||
})
|
||||
})
|
@ -96,7 +96,7 @@ export class FilterableDropdownSelectionModel {
|
||||
toggle(id: number, fireEvent = true) {
|
||||
let state = this.temporarySelectionStates.get(id)
|
||||
if (
|
||||
state == null ||
|
||||
state == undefined ||
|
||||
(state != ToggleableItemState.Selected &&
|
||||
state != ToggleableItemState.Excluded)
|
||||
) {
|
||||
|
@ -0,0 +1,79 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import {
|
||||
ToggleableDropdownButtonComponent,
|
||||
ToggleableItemState,
|
||||
} from './toggleable-dropdown-button.component'
|
||||
import { TagComponent } from '../../tag/tag.component'
|
||||
import { PaperlessTag } from 'src/app/data/paperless-tag'
|
||||
|
||||
describe('ToggleableDropdownButtonComponent', () => {
|
||||
let component: ToggleableDropdownButtonComponent
|
||||
let fixture: ComponentFixture<ToggleableDropdownButtonComponent>
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ToggleableDropdownButtonComponent, TagComponent],
|
||||
providers: [],
|
||||
imports: [],
|
||||
}).compileComponents()
|
||||
|
||||
fixture = TestBed.createComponent(ToggleableDropdownButtonComponent)
|
||||
component = fixture.componentInstance
|
||||
})
|
||||
|
||||
it('should recognize a tag', () => {
|
||||
component.item = {
|
||||
id: 1,
|
||||
name: 'Test Tag',
|
||||
is_inbox_tag: false,
|
||||
} as PaperlessTag
|
||||
|
||||
fixture.detectChanges()
|
||||
expect(component.isTag).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should report toggled state', () => {
|
||||
expect(component.isChecked()).toBeFalsy()
|
||||
expect(component.isPartiallyChecked()).toBeFalsy()
|
||||
expect(component.isExcluded()).toBeFalsy()
|
||||
|
||||
component.state = ToggleableItemState.Selected
|
||||
expect(component.isChecked()).toBeTruthy()
|
||||
expect(component.isPartiallyChecked()).toBeFalsy()
|
||||
expect(component.isExcluded()).toBeFalsy()
|
||||
|
||||
component.state = ToggleableItemState.PartiallySelected
|
||||
expect(component.isPartiallyChecked()).toBeTruthy()
|
||||
expect(component.isChecked()).toBeFalsy()
|
||||
expect(component.isExcluded()).toBeFalsy()
|
||||
|
||||
component.state = ToggleableItemState.Excluded
|
||||
expect(component.isExcluded()).toBeTruthy()
|
||||
expect(component.isChecked()).toBeFalsy()
|
||||
expect(component.isPartiallyChecked()).toBeFalsy()
|
||||
})
|
||||
|
||||
it('should emit exclude event when selected and then toggled', () => {
|
||||
let excludeResult
|
||||
let toggleResult
|
||||
component.state = ToggleableItemState.Selected
|
||||
component.exclude.subscribe(() => (excludeResult = true))
|
||||
component.toggle.subscribe(() => (toggleResult = true))
|
||||
const button = fixture.nativeElement.querySelector('button')
|
||||
button.dispatchEvent(new MouseEvent('click'))
|
||||
expect(excludeResult).toBeTruthy()
|
||||
expect(toggleResult).toBeFalsy()
|
||||
})
|
||||
|
||||
it('should emit toggle event when not selected and then toggled', () => {
|
||||
let excludeResult
|
||||
let toggleResult
|
||||
component.state = ToggleableItemState.Excluded
|
||||
component.exclude.subscribe(() => (excludeResult = true))
|
||||
component.toggle.subscribe(() => (toggleResult = true))
|
||||
const button = fixture.nativeElement.querySelector('button')
|
||||
button.dispatchEvent(new MouseEvent('click'))
|
||||
expect(excludeResult).toBeFalsy()
|
||||
expect(toggleResult).toBeTruthy()
|
||||
})
|
||||
})
|
@ -0,0 +1,55 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { AbstractInputComponent } from './abstract-input'
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<div>
|
||||
<input
|
||||
#inputField
|
||||
type="text"
|
||||
class="form-control"
|
||||
[class.is-invalid]="error"
|
||||
[id]="inputId"
|
||||
[(ngModel)]="value"
|
||||
(change)="onChange(value)"
|
||||
[disabled]="disabled"
|
||||
/>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
class TestComponent extends AbstractInputComponent<string> {
|
||||
constructor() {
|
||||
super()
|
||||
}
|
||||
}
|
||||
|
||||
describe(`AbstractInputComponent`, () => {
|
||||
let component: TestComponent
|
||||
let fixture: ComponentFixture<TestComponent>
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [TestComponent],
|
||||
providers: [],
|
||||
imports: [FormsModule, ReactiveFormsModule],
|
||||
}).compileComponents()
|
||||
|
||||
fixture = TestBed.createComponent(TestComponent)
|
||||
component = fixture.componentInstance
|
||||
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
it('should assign uuid', () => {
|
||||
component.ngOnInit()
|
||||
expect(component.inputId).not.toBeUndefined()
|
||||
})
|
||||
|
||||
it('should support focus', () => {
|
||||
const focusSpy = jest.spyOn(component.inputField.nativeElement, 'focus')
|
||||
component.focus()
|
||||
expect(focusSpy).toHaveBeenCalled()
|
||||
})
|
||||
})
|
@ -1,5 +1,5 @@
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" class="form-check-input" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)" (blur)="onTouched()" [disabled]="disabled">
|
||||
<input #inputField type="checkbox" class="form-check-input" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)" (blur)="onTouched()" [disabled]="disabled">
|
||||
<label class="form-check-label" [for]="inputId">{{title}}</label>
|
||||
<div *ngIf="hint" class="form-text text-muted">{{hint}}</div>
|
||||
</div>
|
||||
|
@ -0,0 +1,39 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { CheckComponent } from './check.component'
|
||||
import {
|
||||
FormsModule,
|
||||
NG_VALUE_ACCESSOR,
|
||||
ReactiveFormsModule,
|
||||
} from '@angular/forms'
|
||||
|
||||
describe('CheckComponent', () => {
|
||||
let component: CheckComponent
|
||||
let fixture: ComponentFixture<CheckComponent>
|
||||
let input: HTMLInputElement
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [CheckComponent],
|
||||
providers: [],
|
||||
imports: [FormsModule, ReactiveFormsModule],
|
||||
}).compileComponents()
|
||||
|
||||
fixture = TestBed.createComponent(CheckComponent)
|
||||
fixture.debugElement.injector.get(NG_VALUE_ACCESSOR)
|
||||
component = fixture.componentInstance
|
||||
fixture.detectChanges()
|
||||
input = component.inputField.nativeElement
|
||||
})
|
||||
|
||||
it('should support use of checkbox', () => {
|
||||
input.checked = true
|
||||
input.dispatchEvent(new Event('change'))
|
||||
fixture.detectChanges()
|
||||
expect(component.value).toBeTruthy()
|
||||
|
||||
input.checked = false
|
||||
input.dispatchEvent(new Event('change'))
|
||||
fixture.detectChanges()
|
||||
expect(component.value).toBeFalsy()
|
||||
})
|
||||
})
|
@ -1,6 +1,5 @@
|
||||
import { Component, forwardRef, Input, OnInit } from '@angular/core'
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { Component, forwardRef } from '@angular/core'
|
||||
import { NG_VALUE_ACCESSOR } from '@angular/forms'
|
||||
import { AbstractInputComponent } from '../abstract-input'
|
||||
|
||||
@Component({
|
||||
|
@ -11,7 +11,7 @@
|
||||
|
||||
</ng-template>
|
||||
|
||||
<input class="form-control" [class.is-invalid]="error" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)" [autoClose]="'outside'" [ngbPopover]="popContent" placement="bottom" popoverClass="shadow">
|
||||
<input #inputField class="form-control" [class.is-invalid]="error" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)" [autoClose]="'outside'" [ngbPopover]="popContent" placement="bottom" popoverClass="shadow">
|
||||
|
||||
<button class="btn btn-outline-secondary" type="button" (click)="randomize()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-dice-5" viewBox="0 0 16 16">
|
||||
|
@ -0,0 +1,72 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import {
|
||||
FormsModule,
|
||||
NG_VALUE_ACCESSOR,
|
||||
ReactiveFormsModule,
|
||||
} from '@angular/forms'
|
||||
import { ColorComponent } from './color.component'
|
||||
import { NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { ColorSliderModule } from 'ngx-color/slider'
|
||||
|
||||
describe('ColorComponent', () => {
|
||||
let component: ColorComponent
|
||||
let fixture: ComponentFixture<ColorComponent>
|
||||
let input: HTMLInputElement
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ColorComponent],
|
||||
providers: [],
|
||||
imports: [
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NgbPopoverModule,
|
||||
ColorSliderModule,
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
fixture = TestBed.createComponent(ColorComponent)
|
||||
fixture.debugElement.injector.get(NG_VALUE_ACCESSOR)
|
||||
component = fixture.componentInstance
|
||||
fixture.detectChanges()
|
||||
input = component.inputField.nativeElement
|
||||
})
|
||||
|
||||
it('should support use of input', () => {
|
||||
input.value = '#ff0000'
|
||||
component.colorChanged(input.value)
|
||||
fixture.detectChanges()
|
||||
expect(component.value).toEqual('#ff0000')
|
||||
})
|
||||
|
||||
it('should set swatch color', () => {
|
||||
const swatch: HTMLSpanElement = fixture.nativeElement.querySelector(
|
||||
'span.input-group-text'
|
||||
)
|
||||
expect(swatch.style.backgroundColor).toEqual('')
|
||||
component.value = '#ff0000'
|
||||
fixture.detectChanges()
|
||||
expect(swatch.style.backgroundColor).toEqual('rgb(255, 0, 0)')
|
||||
})
|
||||
|
||||
it('should show color slider popover', () => {
|
||||
component.value = '#ff0000'
|
||||
input.dispatchEvent(new MouseEvent('click'))
|
||||
fixture.detectChanges()
|
||||
expect(
|
||||
fixture.nativeElement.querySelector('ngb-popover-window')
|
||||
).not.toBeUndefined()
|
||||
expect(
|
||||
fixture.nativeElement.querySelector('color-slider')
|
||||
).not.toBeUndefined()
|
||||
fixture.nativeElement
|
||||
.querySelector('color-slider')
|
||||
.dispatchEvent(new Event('change'))
|
||||
})
|
||||
|
||||
it('should allow randomize color and update value', () => {
|
||||
expect(component.value).toBeUndefined()
|
||||
component.randomize()
|
||||
expect(component.value).not.toBeUndefined()
|
||||
})
|
||||
})
|
@ -1,7 +1,7 @@
|
||||
<div class="mb-3">
|
||||
<label class="form-label" [for]="inputId">{{title}}</label>
|
||||
<div class="input-group" [class.is-invalid]="error">
|
||||
<input class="form-control" [class.is-invalid]="error" [placeholder]="placeholder" [id]="inputId" maxlength="10"
|
||||
<input #inputField class="form-control" [class.is-invalid]="error" [placeholder]="placeholder" [id]="inputId" maxlength="10"
|
||||
(dateSelect)="onChange(value)" (change)="onChange(value)" (keypress)="onKeyPress($event)" (paste)="onPaste($event)"
|
||||
name="dp" [(ngModel)]="value" ngbDatepicker #datePicker="ngbDatepicker" #datePickerContent="ngModel" [disabled]="disabled">
|
||||
<button class="btn btn-outline-secondary calendar" (click)="datePicker.toggle()" type="button" [disabled]="disabled">
|
||||
|
@ -0,0 +1,103 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import {
|
||||
FormsModule,
|
||||
NG_VALUE_ACCESSOR,
|
||||
ReactiveFormsModule,
|
||||
} from '@angular/forms'
|
||||
import { DateComponent } from './date.component'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import {
|
||||
NgbDateParserFormatter,
|
||||
NgbDatepickerModule,
|
||||
} from '@ng-bootstrap/ng-bootstrap'
|
||||
import { RouterTestingModule } from '@angular/router/testing'
|
||||
import { LocalizedDateParserFormatter } from 'src/app/utils/ngb-date-parser-formatter'
|
||||
|
||||
describe('DateComponent', () => {
|
||||
let component: DateComponent
|
||||
let fixture: ComponentFixture<DateComponent>
|
||||
let input: HTMLInputElement
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [DateComponent],
|
||||
providers: [
|
||||
{
|
||||
provide: NgbDateParserFormatter,
|
||||
useClass: LocalizedDateParserFormatter,
|
||||
},
|
||||
],
|
||||
imports: [
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
HttpClientTestingModule,
|
||||
NgbDatepickerModule,
|
||||
RouterTestingModule,
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
fixture = TestBed.createComponent(DateComponent)
|
||||
fixture.debugElement.injector.get(NG_VALUE_ACCESSOR)
|
||||
component = fixture.componentInstance
|
||||
fixture.detectChanges()
|
||||
input = component.inputField.nativeElement
|
||||
})
|
||||
|
||||
it('should support use of input field', () => {
|
||||
input.value = '5/14/20'
|
||||
input.dispatchEvent(new Event('change'))
|
||||
fixture.detectChanges()
|
||||
expect(component.value).toEqual({ day: 14, month: 5, year: 2020 })
|
||||
})
|
||||
|
||||
it('should use localzed placeholder from settings', () => {
|
||||
component.ngOnInit()
|
||||
expect(component.placeholder).toEqual('mm/dd/yyyy')
|
||||
})
|
||||
|
||||
it('should support suggestions', () => {
|
||||
expect(component.value).toBeUndefined()
|
||||
component.suggestions = ['2023-05-31', '2014-05-14']
|
||||
fixture.detectChanges()
|
||||
const suggestionAnchor: HTMLAnchorElement =
|
||||
fixture.nativeElement.querySelector('a')
|
||||
suggestionAnchor.click()
|
||||
expect(component.value).toEqual({ day: 31, month: 5, year: 2023 })
|
||||
})
|
||||
|
||||
it('should limit keyboard events', () => {
|
||||
let event: KeyboardEvent = new KeyboardEvent('keypress', {
|
||||
key: '9',
|
||||
})
|
||||
let eventSpy = jest.spyOn(event, 'preventDefault')
|
||||
input.dispatchEvent(event)
|
||||
expect(eventSpy).not.toHaveBeenCalled()
|
||||
|
||||
event = new KeyboardEvent('keypress', {
|
||||
key: '{',
|
||||
})
|
||||
eventSpy = jest.spyOn(event, 'preventDefault')
|
||||
input.dispatchEvent(event)
|
||||
expect(eventSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should support paste', () => {
|
||||
expect(component.value).toBeUndefined()
|
||||
const date = '5/4/20'
|
||||
const clipboardData = {
|
||||
dropEffect: null,
|
||||
effectAllowed: null,
|
||||
files: null,
|
||||
items: null,
|
||||
types: null,
|
||||
clearData: null,
|
||||
getData: () => date,
|
||||
setData: null,
|
||||
setDragImage: null,
|
||||
}
|
||||
const event = new Event('paste')
|
||||
event['clipboardData'] = clipboardData
|
||||
input.dispatchEvent(event)
|
||||
expect(component.value).toEqual({ day: 4, month: 5, year: 2020 })
|
||||
})
|
||||
})
|
@ -1,7 +1,7 @@
|
||||
<div class="mb-3">
|
||||
<label class="form-label" [for]="inputId">{{title}}</label>
|
||||
<div class="input-group" [class.is-invalid]="error">
|
||||
<input type="number" class="form-control" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)" [class.is-invalid]="error" [disabled]="disabled">
|
||||
<input #inputField type="number" class="form-control" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)" [class.is-invalid]="error" [disabled]="disabled">
|
||||
<button *ngIf="showAdd" class="btn btn-outline-secondary" type="button" id="button-addon1" (click)="nextAsn()" [disabled]="disabled">+1</button>
|
||||
</div>
|
||||
<div class="invalid-feedback">
|
||||
|
@ -0,0 +1,79 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import {
|
||||
FormsModule,
|
||||
NG_VALUE_ACCESSOR,
|
||||
ReactiveFormsModule,
|
||||
} from '@angular/forms'
|
||||
import { NumberComponent } from './number.component'
|
||||
import { DocumentService } from 'src/app/services/rest/document.service'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { of } from 'rxjs'
|
||||
|
||||
describe('NumberComponent', () => {
|
||||
let component: NumberComponent
|
||||
let fixture: ComponentFixture<NumberComponent>
|
||||
let input: HTMLInputElement
|
||||
let documentService: DocumentService
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [NumberComponent],
|
||||
providers: [DocumentService],
|
||||
imports: [FormsModule, ReactiveFormsModule, HttpClientTestingModule],
|
||||
}).compileComponents()
|
||||
|
||||
fixture = TestBed.createComponent(NumberComponent)
|
||||
fixture.debugElement.injector.get(NG_VALUE_ACCESSOR)
|
||||
component = fixture.componentInstance
|
||||
documentService = TestBed.inject(DocumentService)
|
||||
fixture.detectChanges()
|
||||
input = component.inputField.nativeElement
|
||||
})
|
||||
|
||||
// TODO: why doesnt this work?
|
||||
// it('should support use of input field', () => {
|
||||
// expect(component.value).toBeUndefined()
|
||||
// input.stepUp()
|
||||
// console.log(input.value);
|
||||
|
||||
// input.dispatchEvent(new Event('change'))
|
||||
// fixture.detectChanges()
|
||||
// expect(component.value).toEqual('3')
|
||||
// })
|
||||
|
||||
it('should support +1 ASN', () => {
|
||||
const listAllSpy = jest.spyOn(documentService, 'listFiltered')
|
||||
listAllSpy
|
||||
.mockReturnValueOnce(
|
||||
of({
|
||||
count: 1,
|
||||
all: [1],
|
||||
results: [
|
||||
{
|
||||
id: 1,
|
||||
archive_serial_number: 1000,
|
||||
},
|
||||
],
|
||||
})
|
||||
)
|
||||
.mockReturnValueOnce(
|
||||
of({
|
||||
count: 0,
|
||||
all: [],
|
||||
results: [],
|
||||
})
|
||||
)
|
||||
expect(component.value).toBeUndefined()
|
||||
component.nextAsn()
|
||||
expect(component.value).toEqual(1001)
|
||||
|
||||
// this time results are empty
|
||||
component.value = undefined
|
||||
component.nextAsn()
|
||||
expect(component.value).toEqual(1)
|
||||
|
||||
component.value = 1002
|
||||
component.nextAsn()
|
||||
expect(component.value).toEqual(1002)
|
||||
})
|
||||
})
|
@ -0,0 +1,36 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import {
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NG_VALUE_ACCESSOR,
|
||||
} from '@angular/forms'
|
||||
import { PasswordComponent } from './password.component'
|
||||
|
||||
describe('PasswordComponent', () => {
|
||||
let component: PasswordComponent
|
||||
let fixture: ComponentFixture<PasswordComponent>
|
||||
let input: HTMLInputElement
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [PasswordComponent],
|
||||
providers: [],
|
||||
imports: [FormsModule, ReactiveFormsModule],
|
||||
}).compileComponents()
|
||||
|
||||
fixture = TestBed.createComponent(PasswordComponent)
|
||||
fixture.debugElement.injector.get(NG_VALUE_ACCESSOR)
|
||||
component = fixture.componentInstance
|
||||
fixture.detectChanges()
|
||||
input = component.inputField.nativeElement
|
||||
})
|
||||
|
||||
it('should support use of input field', () => {
|
||||
expect(component.value).toBeUndefined()
|
||||
// TODO: why doesnt this work?
|
||||
// input.value = 'foo'
|
||||
// input.dispatchEvent(new Event('change'))
|
||||
// fixture.detectChanges()
|
||||
// expect(component.value).toEqual('foo')
|
||||
})
|
||||
})
|
@ -0,0 +1,66 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import {
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NG_VALUE_ACCESSOR,
|
||||
} from '@angular/forms'
|
||||
import { PermissionsFormComponent } from './permissions-form.component'
|
||||
import { SelectComponent } from '../../select/select.component'
|
||||
import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { PermissionsGroupComponent } from '../permissions-group/permissions-group.component'
|
||||
import { PermissionsUserComponent } from '../permissions-user/permissions-user.component'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { NgSelectModule } from '@ng-select/ng-select'
|
||||
|
||||
describe('PermissionsFormComponent', () => {
|
||||
let component: PermissionsFormComponent
|
||||
let fixture: ComponentFixture<PermissionsFormComponent>
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
PermissionsFormComponent,
|
||||
SelectComponent,
|
||||
PermissionsGroupComponent,
|
||||
PermissionsUserComponent,
|
||||
],
|
||||
providers: [],
|
||||
imports: [
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NgbAccordionModule,
|
||||
HttpClientTestingModule,
|
||||
NgSelectModule,
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
fixture = TestBed.createComponent(PermissionsFormComponent)
|
||||
fixture.debugElement.injector.get(NG_VALUE_ACCESSOR)
|
||||
component = fixture.componentInstance
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
it('should support use of select for owner', () => {
|
||||
const changeSpy = jest.spyOn(component, 'onChange')
|
||||
component.ngOnInit()
|
||||
component.users = [
|
||||
{
|
||||
id: 2,
|
||||
username: 'foo',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
username: 'bar',
|
||||
},
|
||||
]
|
||||
component.form.get('owner').patchValue(2)
|
||||
fixture.detectChanges()
|
||||
expect(changeSpy).toHaveBeenCalledWith({
|
||||
owner: 2,
|
||||
set_permissions: {
|
||||
view: { users: [], groups: [] },
|
||||
change: { users: [], groups: [] },
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
@ -0,0 +1,59 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import {
|
||||
FormsModule,
|
||||
NG_VALUE_ACCESSOR,
|
||||
ReactiveFormsModule,
|
||||
} from '@angular/forms'
|
||||
import { PermissionsGroupComponent } from './permissions-group.component'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { NgSelectModule } from '@ng-select/ng-select'
|
||||
import { GroupService } from 'src/app/services/rest/group.service'
|
||||
import { of } from 'rxjs'
|
||||
|
||||
describe('PermissionsGroupComponent', () => {
|
||||
let component: PermissionsGroupComponent
|
||||
let fixture: ComponentFixture<PermissionsGroupComponent>
|
||||
let groupService: GroupService
|
||||
let groupServiceSpy
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [PermissionsGroupComponent],
|
||||
providers: [GroupService],
|
||||
imports: [
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
HttpClientTestingModule,
|
||||
NgSelectModule,
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
groupService = TestBed.inject(GroupService)
|
||||
groupServiceSpy = jest.spyOn(groupService, 'listAll').mockReturnValue(
|
||||
of({
|
||||
count: 2,
|
||||
all: [2, 3],
|
||||
results: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Group 2',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Group 3',
|
||||
},
|
||||
],
|
||||
})
|
||||
)
|
||||
fixture = TestBed.createComponent(PermissionsGroupComponent)
|
||||
fixture.debugElement.injector.get(NG_VALUE_ACCESSOR)
|
||||
component = fixture.componentInstance
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
it('should get groups, support use of select', () => {
|
||||
component.writeValue({ id: 2, name: 'Group 2' })
|
||||
expect(component.value).toEqual({ id: 2, name: 'Group 2' })
|
||||
expect(groupServiceSpy).toHaveBeenCalled()
|
||||
})
|
||||
})
|
@ -0,0 +1,60 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import {
|
||||
FormsModule,
|
||||
NG_VALUE_ACCESSOR,
|
||||
ReactiveFormsModule,
|
||||
} from '@angular/forms'
|
||||
import { PermissionsUserComponent } from './permissions-user.component'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { NgSelectModule } from '@ng-select/ng-select'
|
||||
import { GroupService } from 'src/app/services/rest/group.service'
|
||||
import { of } from 'rxjs'
|
||||
import { UserService } from 'src/app/services/rest/user.service'
|
||||
|
||||
describe('PermissionsUserComponent', () => {
|
||||
let component: PermissionsUserComponent
|
||||
let fixture: ComponentFixture<PermissionsUserComponent>
|
||||
let userService: UserService
|
||||
let userServiceSpy
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [PermissionsUserComponent],
|
||||
providers: [UserService],
|
||||
imports: [
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
HttpClientTestingModule,
|
||||
NgSelectModule,
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
userService = TestBed.inject(UserService)
|
||||
userServiceSpy = jest.spyOn(userService, 'listAll').mockReturnValue(
|
||||
of({
|
||||
count: 2,
|
||||
all: [2, 3],
|
||||
results: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'User 2',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'User 3',
|
||||
},
|
||||
],
|
||||
})
|
||||
)
|
||||
fixture = TestBed.createComponent(PermissionsUserComponent)
|
||||
fixture.debugElement.injector.get(NG_VALUE_ACCESSOR)
|
||||
component = fixture.componentInstance
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
it('should get users, support use of select', () => {
|
||||
component.writeValue({ id: 2, name: 'User 2' })
|
||||
expect(component.value).toEqual({ id: 2, name: 'User 2' })
|
||||
expect(userServiceSpy).toHaveBeenCalled()
|
||||
})
|
||||
})
|
@ -0,0 +1,121 @@
|
||||
import {
|
||||
ComponentFixture,
|
||||
TestBed,
|
||||
fakeAsync,
|
||||
tick,
|
||||
} from '@angular/core/testing'
|
||||
import {
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NG_VALUE_ACCESSOR,
|
||||
} from '@angular/forms'
|
||||
import { SelectComponent } from './select.component'
|
||||
import { PaperlessTag } from 'src/app/data/paperless-tag'
|
||||
import {
|
||||
DEFAULT_MATCHING_ALGORITHM,
|
||||
MATCH_ALL,
|
||||
} from 'src/app/data/matching-model'
|
||||
import { NgSelectModule } from '@ng-select/ng-select'
|
||||
import { RouterTestingModule } from '@angular/router/testing'
|
||||
|
||||
const items: PaperlessTag[] = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Tag1',
|
||||
is_inbox_tag: false,
|
||||
matching_algorithm: DEFAULT_MATCHING_ALGORITHM,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Tag2',
|
||||
is_inbox_tag: true,
|
||||
matching_algorithm: MATCH_ALL,
|
||||
match: 'str',
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
name: 'Tag10',
|
||||
is_inbox_tag: false,
|
||||
matching_algorithm: DEFAULT_MATCHING_ALGORITHM,
|
||||
},
|
||||
]
|
||||
|
||||
describe('SelectComponent', () => {
|
||||
let component: SelectComponent
|
||||
let fixture: ComponentFixture<SelectComponent>
|
||||
let input: HTMLInputElement
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [SelectComponent],
|
||||
providers: [],
|
||||
imports: [
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NgSelectModule,
|
||||
RouterTestingModule,
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
fixture = TestBed.createComponent(SelectComponent)
|
||||
fixture.debugElement.injector.get(NG_VALUE_ACCESSOR)
|
||||
component = fixture.componentInstance
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
it('should support private items', () => {
|
||||
component.value = 3
|
||||
component.items = items
|
||||
expect(component.items).toContainEqual({
|
||||
id: 3,
|
||||
name: 'Private',
|
||||
private: true,
|
||||
})
|
||||
|
||||
component.checkForPrivateItems([4, 5])
|
||||
expect(component.items).toContainEqual({
|
||||
id: 4,
|
||||
name: 'Private',
|
||||
private: true,
|
||||
})
|
||||
expect(component.items).toContainEqual({
|
||||
id: 5,
|
||||
name: 'Private',
|
||||
private: true,
|
||||
})
|
||||
})
|
||||
|
||||
it('should support suggestions', () => {
|
||||
expect(component.value).toBeUndefined()
|
||||
component.items = items
|
||||
component.suggestions = [1, 2]
|
||||
fixture.detectChanges()
|
||||
const suggestionAnchor: HTMLAnchorElement =
|
||||
fixture.nativeElement.querySelector('a')
|
||||
suggestionAnchor.click()
|
||||
expect(component.value).toEqual(1)
|
||||
})
|
||||
|
||||
it('should support create new and emit the value', () => {
|
||||
expect(component.allowCreateNew).toBeFalsy()
|
||||
component.items = items
|
||||
let createNewVal
|
||||
component.createNew.subscribe((v) => (createNewVal = v))
|
||||
expect(component.allowCreateNew).toBeTruthy()
|
||||
component.onSearch({ term: 'foo' })
|
||||
component.addItem(undefined)
|
||||
expect(createNewVal).toEqual('foo')
|
||||
component.addItem('bar')
|
||||
expect(createNewVal).toEqual('bar')
|
||||
component.onSearch({ term: 'baz' })
|
||||
component.clickNew()
|
||||
expect(createNewVal).toEqual('baz')
|
||||
})
|
||||
|
||||
it('should clear search term on blur after delay', fakeAsync(() => {
|
||||
const clearSpy = jest.spyOn(component, 'clearLastSearchTerm')
|
||||
component.onBlur()
|
||||
tick(3000)
|
||||
expect(clearSpy).toHaveBeenCalled()
|
||||
}))
|
||||
})
|
@ -0,0 +1,140 @@
|
||||
import {
|
||||
ComponentFixture,
|
||||
TestBed,
|
||||
fakeAsync,
|
||||
tick,
|
||||
} from '@angular/core/testing'
|
||||
import {
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NG_VALUE_ACCESSOR,
|
||||
} from '@angular/forms'
|
||||
import { TagsComponent } from './tags.component'
|
||||
import { PaperlessTag } from 'src/app/data/paperless-tag'
|
||||
import {
|
||||
DEFAULT_MATCHING_ALGORITHM,
|
||||
MATCH_ALL,
|
||||
} from 'src/app/data/matching-model'
|
||||
import { NgSelectModule } from '@ng-select/ng-select'
|
||||
import { RouterTestingModule } from '@angular/router/testing'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { of } from 'rxjs'
|
||||
import { TagService } from 'src/app/services/rest/tag.service'
|
||||
import {
|
||||
NgbModal,
|
||||
NgbModalModule,
|
||||
NgbModalRef,
|
||||
} from '@ng-bootstrap/ng-bootstrap'
|
||||
|
||||
const tags: PaperlessTag[] = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Tag1',
|
||||
is_inbox_tag: false,
|
||||
matching_algorithm: DEFAULT_MATCHING_ALGORITHM,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Tag2',
|
||||
is_inbox_tag: true,
|
||||
matching_algorithm: MATCH_ALL,
|
||||
match: 'str',
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
name: 'Tag10',
|
||||
is_inbox_tag: false,
|
||||
matching_algorithm: DEFAULT_MATCHING_ALGORITHM,
|
||||
},
|
||||
]
|
||||
|
||||
describe('TagsComponent', () => {
|
||||
let component: TagsComponent
|
||||
let fixture: ComponentFixture<TagsComponent>
|
||||
let input: HTMLInputElement
|
||||
let modalService: NgbModal
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [TagsComponent],
|
||||
providers: [
|
||||
{
|
||||
provide: TagService,
|
||||
useValue: {
|
||||
listAll: () => of(tags),
|
||||
},
|
||||
},
|
||||
],
|
||||
imports: [
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NgSelectModule,
|
||||
RouterTestingModule,
|
||||
HttpClientTestingModule,
|
||||
NgbModalModule,
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
modalService = TestBed.inject(NgbModal)
|
||||
fixture = TestBed.createComponent(TagsComponent)
|
||||
fixture.debugElement.injector.get(NG_VALUE_ACCESSOR)
|
||||
component = fixture.componentInstance
|
||||
fixture.detectChanges()
|
||||
|
||||
window.PointerEvent = MouseEvent as any
|
||||
})
|
||||
|
||||
it('should support suggestions', () => {
|
||||
expect(component.value).toBeUndefined()
|
||||
component.value = []
|
||||
component.tags = tags
|
||||
component.suggestions = [1, 2]
|
||||
fixture.detectChanges()
|
||||
const suggestionAnchor: HTMLAnchorElement =
|
||||
fixture.nativeElement.querySelector('a')
|
||||
suggestionAnchor.click()
|
||||
expect(component.value).toEqual([1])
|
||||
})
|
||||
|
||||
it('should support create new and open a modal', () => {
|
||||
let activeInstances: NgbModalRef[]
|
||||
modalService.activeInstances.subscribe((v) => (activeInstances = v))
|
||||
component.createTag('foo')
|
||||
expect(modalService.hasOpenModals()).toBeTruthy()
|
||||
expect(activeInstances[0].componentInstance.object.name).toEqual('foo')
|
||||
})
|
||||
|
||||
it('should support create new using last search term and open a modal', () => {
|
||||
let activeInstances: NgbModalRef[]
|
||||
modalService.activeInstances.subscribe((v) => (activeInstances = v))
|
||||
component.onSearch({ term: 'bar' })
|
||||
component.createTag()
|
||||
expect(modalService.hasOpenModals()).toBeTruthy()
|
||||
expect(activeInstances[0].componentInstance.object.name).toEqual('bar')
|
||||
})
|
||||
|
||||
it('should clear search term on blur after delay', fakeAsync(() => {
|
||||
const clearSpy = jest.spyOn(component, 'clearLastSearchTerm')
|
||||
component.onBlur()
|
||||
tick(3000)
|
||||
expect(clearSpy).toHaveBeenCalled()
|
||||
}))
|
||||
|
||||
it('support remove tags', () => {
|
||||
component.tags = tags
|
||||
component.value = [1, 2]
|
||||
component.removeTag(new PointerEvent('point'), 2)
|
||||
expect(component.value).toEqual([1])
|
||||
|
||||
component.disabled = true
|
||||
component.removeTag(new PointerEvent('point'), 1)
|
||||
expect(component.value).toEqual([1])
|
||||
})
|
||||
|
||||
it('should get tags', () => {
|
||||
expect(component.getTag(2)).toBeNull()
|
||||
component.tags = tags
|
||||
expect(component.getTag(2)).toEqual(tags[1])
|
||||
expect(component.getTag(4)).toBeUndefined()
|
||||
})
|
||||
})
|
@ -11,6 +11,7 @@ import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { PaperlessTag } from 'src/app/data/paperless-tag'
|
||||
import { TagEditDialogComponent } from '../../edit-dialog/tag-edit-dialog/tag-edit-dialog.component'
|
||||
import { TagService } from 'src/app/services/rest/tag.service'
|
||||
import { EditDialogMode } from '../../edit-dialog/edit-dialog.component'
|
||||
|
||||
@Component({
|
||||
providers: [
|
||||
@ -105,7 +106,7 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
|
||||
var modal = this.modalService.open(TagEditDialogComponent, {
|
||||
backdrop: 'static',
|
||||
})
|
||||
modal.componentInstance.dialogMode = 'create'
|
||||
modal.componentInstance.dialogMode = EditDialogMode.CREATE
|
||||
if (name) modal.componentInstance.object = { name: name }
|
||||
else if (this._lastSearchTerm)
|
||||
modal.componentInstance.object = { name: this._lastSearchTerm }
|
||||
|
@ -0,0 +1,36 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import {
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NG_VALUE_ACCESSOR,
|
||||
} from '@angular/forms'
|
||||
import { TextComponent } from './text.component'
|
||||
|
||||
describe('TextComponent', () => {
|
||||
let component: TextComponent
|
||||
let fixture: ComponentFixture<TextComponent>
|
||||
let input: HTMLInputElement
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [TextComponent],
|
||||
providers: [],
|
||||
imports: [FormsModule, ReactiveFormsModule],
|
||||
}).compileComponents()
|
||||
|
||||
fixture = TestBed.createComponent(TextComponent)
|
||||
fixture.debugElement.injector.get(NG_VALUE_ACCESSOR)
|
||||
component = fixture.componentInstance
|
||||
fixture.detectChanges()
|
||||
input = component.inputField.nativeElement
|
||||
})
|
||||
|
||||
it('should support use of input field', () => {
|
||||
expect(component.value).toBeUndefined()
|
||||
// TODO: why doesnt this work?
|
||||
// input.value = 'foo'
|
||||
// input.dispatchEvent(new Event('change'))
|
||||
// fixture.detectChanges()
|
||||
// expect(component.value).toEqual('foo')
|
||||
})
|
||||
})
|
@ -0,0 +1,36 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { Title } from '@angular/platform-browser'
|
||||
import { PageHeaderComponent } from './page-header.component'
|
||||
import { environment } from 'src/environments/environment'
|
||||
|
||||
describe('PageHeaderComponent', () => {
|
||||
let component: PageHeaderComponent
|
||||
let fixture: ComponentFixture<PageHeaderComponent>
|
||||
let titleService: Title
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [PageHeaderComponent],
|
||||
providers: [],
|
||||
imports: [],
|
||||
}).compileComponents()
|
||||
|
||||
titleService = TestBed.inject(Title)
|
||||
fixture = TestBed.createComponent(PageHeaderComponent)
|
||||
component = fixture.componentInstance
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
it('should display title + subtitle', () => {
|
||||
component.title = 'Foo'
|
||||
component.subTitle = 'Bar'
|
||||
fixture.detectChanges()
|
||||
expect(fixture.nativeElement.textContent).toContain('FooBar')
|
||||
})
|
||||
|
||||
it('should set html title', () => {
|
||||
const titleSpy = jest.spyOn(titleService, 'setTitle')
|
||||
component.title = 'Foo Bar'
|
||||
expect(titleSpy).toHaveBeenCalledWith(`Foo Bar - ${environment.appTitle}`)
|
||||
})
|
||||
})
|
@ -0,0 +1,90 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { PermissionsDialogComponent } from './permissions-dialog.component'
|
||||
import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
|
||||
import { UserService } from 'src/app/services/rest/user.service'
|
||||
import { of } from 'rxjs'
|
||||
import { PermissionsFormComponent } from '../input/permissions/permissions-form/permissions-form.component'
|
||||
import { SelectComponent } from '../input/select/select.component'
|
||||
import { NgSelectModule } from '@ng-select/ng-select'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { PermissionsUserComponent } from '../input/permissions/permissions-user/permissions-user.component'
|
||||
import { PermissionsGroupComponent } from '../input/permissions/permissions-group/permissions-group.component'
|
||||
|
||||
const set_permissions = {
|
||||
owner: 10,
|
||||
set_permissions: {
|
||||
view: {
|
||||
users: [1],
|
||||
groups: [],
|
||||
},
|
||||
edit: {
|
||||
users: [1],
|
||||
groups: [],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
describe('PermissionsDialogComponent', () => {
|
||||
let component: PermissionsDialogComponent
|
||||
let fixture: ComponentFixture<PermissionsDialogComponent>
|
||||
let modal: NgbActiveModal
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
PermissionsDialogComponent,
|
||||
SafeHtmlPipe,
|
||||
SelectComponent,
|
||||
PermissionsFormComponent,
|
||||
PermissionsUserComponent,
|
||||
PermissionsGroupComponent,
|
||||
],
|
||||
providers: [
|
||||
NgbActiveModal,
|
||||
{
|
||||
provide: UserService,
|
||||
useValue: {
|
||||
listAll: () =>
|
||||
of({
|
||||
results: [
|
||||
{
|
||||
id: 1,
|
||||
username: 'user1',
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
username: 'user10',
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
},
|
||||
],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
NgSelectModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NgbModule,
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
modal = TestBed.inject(NgbActiveModal)
|
||||
fixture = TestBed.createComponent(PermissionsDialogComponent)
|
||||
component = fixture.componentInstance
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
it('should return permissions', () => {
|
||||
component.form.get('permissions_form').setValue(set_permissions)
|
||||
expect(component.permissions).toEqual(set_permissions)
|
||||
})
|
||||
|
||||
it('should close modal on cancel', () => {
|
||||
const closeSpy = jest.spyOn(modal, 'close')
|
||||
component.cancelClicked()
|
||||
expect(closeSpy).toHaveBeenCalled()
|
||||
})
|
||||
})
|
@ -0,0 +1,157 @@
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { NgSelectModule } from '@ng-select/ng-select'
|
||||
import { of } from 'rxjs'
|
||||
import { PermissionsService } from 'src/app/services/permissions.service'
|
||||
import { UserService } from 'src/app/services/rest/user.service'
|
||||
import {
|
||||
OwnerFilterType,
|
||||
PermissionsFilterDropdownComponent,
|
||||
PermissionsSelectionModel,
|
||||
} from './permissions-filter-dropdown.component'
|
||||
import { ClearableBadgeComponent } from '../clearable-badge/clearable-badge.component'
|
||||
import { SettingsService } from 'src/app/services/settings.service'
|
||||
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
||||
|
||||
const currentUserID = 13
|
||||
|
||||
describe('PermissionsFilterDropdownComponent', () => {
|
||||
let component: PermissionsFilterDropdownComponent
|
||||
let fixture: ComponentFixture<PermissionsFilterDropdownComponent>
|
||||
let ownerFilterSetResult: PermissionsSelectionModel
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
PermissionsFilterDropdownComponent,
|
||||
ClearableBadgeComponent,
|
||||
IfPermissionsDirective,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: UserService,
|
||||
useValue: {
|
||||
listAll: () =>
|
||||
of({
|
||||
results: [
|
||||
{
|
||||
id: 1,
|
||||
username: 'user1',
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
username: 'user10',
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: PermissionsService,
|
||||
useValue: {
|
||||
currentUserCan: () => true,
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: SettingsService,
|
||||
useValue: {
|
||||
currentUser: {
|
||||
id: currentUserID,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
NgSelectModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NgbModule,
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
fixture = TestBed.createComponent(PermissionsFilterDropdownComponent)
|
||||
component = fixture.componentInstance
|
||||
component.ownerFilterSet.subscribe(
|
||||
(model) => (ownerFilterSetResult = model)
|
||||
)
|
||||
component.selectionModel = new PermissionsSelectionModel()
|
||||
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
it('should report is active', () => {
|
||||
component.setFilter(OwnerFilterType.NONE)
|
||||
expect(component.isActive).toBeFalsy()
|
||||
component.setFilter(OwnerFilterType.OTHERS)
|
||||
expect(component.isActive).toBeTruthy()
|
||||
component.setFilter(OwnerFilterType.NONE)
|
||||
component.selectionModel.hideUnowned = true
|
||||
expect(component.isActive).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should support reset', () => {
|
||||
component.setFilter(OwnerFilterType.OTHERS)
|
||||
expect(component.selectionModel.ownerFilter).not.toEqual(
|
||||
OwnerFilterType.NONE
|
||||
)
|
||||
component.reset()
|
||||
expect(component.selectionModel.ownerFilter).toEqual(OwnerFilterType.NONE)
|
||||
})
|
||||
|
||||
it('should toggle owner filter type when users selected', () => {
|
||||
component.selectionModel.ownerFilter = OwnerFilterType.NONE
|
||||
|
||||
// this would normally be done by select component
|
||||
component.selectionModel.includeUsers = [12]
|
||||
component.onUserSelect()
|
||||
expect(component.selectionModel.ownerFilter).toEqual(OwnerFilterType.OTHERS)
|
||||
|
||||
// this would normally be done by select component
|
||||
component.selectionModel.includeUsers = null
|
||||
component.onUserSelect()
|
||||
|
||||
expect(component.selectionModel.ownerFilter).toEqual(OwnerFilterType.NONE)
|
||||
})
|
||||
it('should emit a selection model depending on the type of owner filter set', () => {
|
||||
component.selectionModel.ownerFilter = OwnerFilterType.NONE
|
||||
|
||||
component.setFilter(OwnerFilterType.SELF)
|
||||
expect(ownerFilterSetResult).toEqual({
|
||||
excludeUsers: [],
|
||||
hideUnowned: false,
|
||||
includeUsers: [],
|
||||
ownerFilter: OwnerFilterType.SELF,
|
||||
userID: currentUserID,
|
||||
})
|
||||
|
||||
component.setFilter(OwnerFilterType.NOT_SELF)
|
||||
expect(ownerFilterSetResult).toEqual({
|
||||
excludeUsers: [currentUserID],
|
||||
hideUnowned: false,
|
||||
includeUsers: [],
|
||||
ownerFilter: OwnerFilterType.NOT_SELF,
|
||||
userID: null,
|
||||
})
|
||||
|
||||
component.setFilter(OwnerFilterType.NONE)
|
||||
expect(ownerFilterSetResult).toEqual({
|
||||
excludeUsers: [],
|
||||
hideUnowned: false,
|
||||
includeUsers: [],
|
||||
ownerFilter: OwnerFilterType.NONE,
|
||||
userID: null,
|
||||
})
|
||||
|
||||
component.setFilter(OwnerFilterType.UNOWNED)
|
||||
expect(ownerFilterSetResult).toEqual({
|
||||
excludeUsers: [],
|
||||
hideUnowned: false,
|
||||
includeUsers: [],
|
||||
ownerFilter: OwnerFilterType.UNOWNED,
|
||||
userID: null,
|
||||
})
|
||||
})
|
||||
})
|
@ -0,0 +1,96 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { PermissionsSelectComponent } from './permissions-select.component'
|
||||
import {
|
||||
FormsModule,
|
||||
NG_VALUE_ACCESSOR,
|
||||
ReactiveFormsModule,
|
||||
} from '@angular/forms'
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import {
|
||||
PermissionAction,
|
||||
PermissionType,
|
||||
} from 'src/app/services/permissions.service'
|
||||
import { By } from '@angular/platform-browser'
|
||||
|
||||
const permissions = [
|
||||
'add_document',
|
||||
'view_document',
|
||||
'change_document',
|
||||
'delete_document',
|
||||
'change_tag',
|
||||
'view_documenttype',
|
||||
]
|
||||
|
||||
const inheritedPermissions = ['change_tag', 'view_documenttype']
|
||||
|
||||
describe('PermissionsSelectComponent', () => {
|
||||
let component: PermissionsSelectComponent
|
||||
let fixture: ComponentFixture<PermissionsSelectComponent>
|
||||
let permissionsChangeResult: Permissions
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [PermissionsSelectComponent],
|
||||
providers: [],
|
||||
imports: [FormsModule, ReactiveFormsModule, NgbModule],
|
||||
}).compileComponents()
|
||||
|
||||
fixture = TestBed.createComponent(PermissionsSelectComponent)
|
||||
fixture.debugElement.injector.get(NG_VALUE_ACCESSOR)
|
||||
component = fixture.componentInstance
|
||||
component.registerOnChange((r) => (permissionsChangeResult = r))
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
it('should create controls for all PermissionType and PermissionAction', () => {
|
||||
expect(Object.values(component.form.controls)).toHaveLength(
|
||||
Object.keys(PermissionType).length
|
||||
)
|
||||
for (var type in component.form.controls) {
|
||||
expect(
|
||||
Object.values(component.form.controls[type].controls)
|
||||
).toHaveLength(Object.keys(PermissionAction).length)
|
||||
}
|
||||
// coverage
|
||||
component.registerOnTouched(() => {})
|
||||
component.setDisabledState(true)
|
||||
})
|
||||
|
||||
it('should allow toggle all on / off', () => {
|
||||
component.ngOnInit()
|
||||
expect(component.typesWithAllActions.values).toHaveLength(0)
|
||||
component.toggleAll({ target: { checked: true } }, 'Tag')
|
||||
expect(component.typesWithAllActions).toContain('Tag')
|
||||
component.toggleAll({ target: { checked: false } }, 'Tag')
|
||||
expect(component.typesWithAllActions.values).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('should update on permissions set', () => {
|
||||
component.ngOnInit()
|
||||
component.writeValue(permissions)
|
||||
expect(permissionsChangeResult).toEqual(permissions)
|
||||
expect(component.typesWithAllActions).toContain('Document')
|
||||
})
|
||||
|
||||
it('should update checkboxes on permissions set', () => {
|
||||
component.ngOnInit()
|
||||
component.writeValue(permissions)
|
||||
fixture.detectChanges()
|
||||
const input1 = fixture.debugElement.query(By.css('input#Document_Add'))
|
||||
expect(input1.nativeElement.checked).toBeTruthy()
|
||||
const input2 = fixture.debugElement.query(By.css('input#Tag_Change'))
|
||||
expect(input2.nativeElement.checked).toBeTruthy()
|
||||
})
|
||||
|
||||
it('disable checkboxes when permissions are inherited', () => {
|
||||
component.ngOnInit()
|
||||
component.inheritedPermissions = inheritedPermissions
|
||||
expect(component.isInherited('Document', 'Add')).toBeFalsy()
|
||||
expect(component.isInherited('Document')).toBeFalsy()
|
||||
expect(component.isInherited('Tag', 'Change')).toBeTruthy()
|
||||
const input1 = fixture.debugElement.query(By.css('input#Document_Add'))
|
||||
expect(input1.nativeElement.disabled).toBeFalsy()
|
||||
const input2 = fixture.debugElement.query(By.css('input#Tag_Change'))
|
||||
expect(input2.nativeElement.disabled).toBeTruthy()
|
||||
})
|
||||
})
|
@ -0,0 +1,31 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { NgSelectModule } from '@ng-select/ng-select'
|
||||
import { SelectComponent } from '../input/select/select.component'
|
||||
import { SelectDialogComponent } from './select-dialog.component'
|
||||
|
||||
describe('SelectDialogComponent', () => {
|
||||
let component: SelectDialogComponent
|
||||
let fixture: ComponentFixture<SelectDialogComponent>
|
||||
let modal: NgbActiveModal
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [SelectDialogComponent, SelectComponent],
|
||||
providers: [NgbActiveModal],
|
||||
imports: [NgSelectModule, FormsModule, ReactiveFormsModule],
|
||||
}).compileComponents()
|
||||
|
||||
modal = TestBed.inject(NgbActiveModal)
|
||||
fixture = TestBed.createComponent(SelectDialogComponent)
|
||||
component = fixture.componentInstance
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
it('should close modal on cancel', () => {
|
||||
const closeSpy = jest.spyOn(modal, 'close')
|
||||
component.cancelClicked()
|
||||
expect(closeSpy).toHaveBeenCalled()
|
||||
})
|
||||
})
|
51
src-ui/src/app/components/common/tag/tag.component.spec.ts
Normal file
51
src-ui/src/app/components/common/tag/tag.component.spec.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { TagComponent } from './tag.component'
|
||||
import { PaperlessTag } from 'src/app/data/paperless-tag'
|
||||
import { By } from '@angular/platform-browser'
|
||||
|
||||
const tag: PaperlessTag = {
|
||||
id: 1,
|
||||
color: '#ff0000',
|
||||
name: 'Tag1',
|
||||
}
|
||||
|
||||
describe('TagComponent', () => {
|
||||
let component: TagComponent
|
||||
let fixture: ComponentFixture<TagComponent>
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [TagComponent],
|
||||
providers: [],
|
||||
imports: [],
|
||||
}).compileComponents()
|
||||
|
||||
fixture = TestBed.createComponent(TagComponent)
|
||||
component = fixture.componentInstance
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
it('should create tag with background color', () => {
|
||||
component.tag = tag
|
||||
fixture.detectChanges()
|
||||
expect(
|
||||
fixture.debugElement.query(By.css('span')).nativeElement.style
|
||||
.backgroundColor
|
||||
).toEqual('rgb(255, 0, 0)')
|
||||
})
|
||||
|
||||
it('should handle private tags', () => {
|
||||
expect(
|
||||
fixture.debugElement.query(By.css('span')).nativeElement.textContent
|
||||
).toEqual('Private')
|
||||
})
|
||||
|
||||
it('should support clickable option', () => {
|
||||
component.tag = tag
|
||||
fixture.detectChanges()
|
||||
expect(fixture.debugElement.query(By.css('a.badge'))).toBeNull()
|
||||
component.clickable = true
|
||||
fixture.detectChanges()
|
||||
expect(fixture.debugElement.query(By.css('a.badge'))).not.toBeNull()
|
||||
})
|
||||
})
|
@ -0,0 +1,94 @@
|
||||
import {
|
||||
TestBed,
|
||||
discardPeriodicTasks,
|
||||
fakeAsync,
|
||||
flush,
|
||||
} from '@angular/core/testing'
|
||||
import { ToastService } from 'src/app/services/toast.service'
|
||||
import { ToastsComponent } from './toasts.component'
|
||||
import { ComponentFixture } from '@angular/core/testing'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { of } from 'rxjs'
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
|
||||
describe('ToastsComponent', () => {
|
||||
let component: ToastsComponent
|
||||
let fixture: ComponentFixture<ToastsComponent>
|
||||
let toastService: ToastService
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ToastsComponent],
|
||||
imports: [HttpClientTestingModule, NgbModule],
|
||||
providers: [
|
||||
{
|
||||
provide: ToastService,
|
||||
useValue: {
|
||||
getToasts: () =>
|
||||
of([
|
||||
{
|
||||
title: 'Title',
|
||||
content: 'content',
|
||||
delay: 5000,
|
||||
},
|
||||
{
|
||||
title: 'Error',
|
||||
content: 'Error content',
|
||||
delay: 5000,
|
||||
error: new Error('Error message'),
|
||||
},
|
||||
]),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
fixture = TestBed.createComponent(ToastsComponent)
|
||||
component = fixture.componentInstance
|
||||
|
||||
toastService = TestBed.inject(ToastService)
|
||||
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
it('should call getToasts and return toasts', fakeAsync(() => {
|
||||
const spy = jest.spyOn(toastService, 'getToasts').mockReset()
|
||||
|
||||
component.ngOnInit()
|
||||
fixture.detectChanges()
|
||||
|
||||
expect(spy).toHaveBeenCalled()
|
||||
expect(component.toasts).toContainEqual({
|
||||
title: 'Title',
|
||||
content: 'content',
|
||||
delay: 5000,
|
||||
})
|
||||
|
||||
component.ngOnDestroy()
|
||||
flush()
|
||||
discardPeriodicTasks()
|
||||
}))
|
||||
|
||||
it('should show a toast', fakeAsync(() => {
|
||||
component.ngOnInit()
|
||||
fixture.detectChanges()
|
||||
|
||||
expect(fixture.nativeElement.textContent).toContain('Title')
|
||||
|
||||
component.ngOnDestroy()
|
||||
flush()
|
||||
discardPeriodicTasks()
|
||||
}))
|
||||
|
||||
it('should show an error if given with toast', fakeAsync(() => {
|
||||
component.ngOnInit()
|
||||
fixture.detectChanges()
|
||||
|
||||
expect(fixture.nativeElement.querySelector('details')).not.toBeNull()
|
||||
expect(fixture.nativeElement.textContent).toContain('Error message')
|
||||
|
||||
component.ngOnDestroy()
|
||||
flush()
|
||||
discardPeriodicTasks()
|
||||
}))
|
||||
})
|
@ -15,7 +15,7 @@ export class ToastsComponent implements OnInit, OnDestroy {
|
||||
toasts: Toast[] = []
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.subscription.unsubscribe()
|
||||
this.subscription?.unsubscribe()
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
117
src-ui/src/app/components/dashboard/dashboard.component.spec.ts
Normal file
117
src-ui/src/app/components/dashboard/dashboard.component.spec.ts
Normal file
@ -0,0 +1,117 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { NgbAlertModule, NgbAlert } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { PermissionsGuard } from 'src/app/guards/permissions.guard'
|
||||
import { DashboardComponent } from './dashboard.component'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { SettingsService } from 'src/app/services/settings.service'
|
||||
import { StatisticsWidgetComponent } from './widgets/statistics-widget/statistics-widget.component'
|
||||
import { PageHeaderComponent } from '../common/page-header/page-header.component'
|
||||
import { WidgetFrameComponent } from './widgets/widget-frame/widget-frame.component'
|
||||
import { UploadFileWidgetComponent } from './widgets/upload-file-widget/upload-file-widget.component'
|
||||
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
|
||||
import { PermissionsService } from 'src/app/services/permissions.service'
|
||||
import { By } from '@angular/platform-browser'
|
||||
import { SavedViewWidgetComponent } from './widgets/saved-view-widget/saved-view-widget.component'
|
||||
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
||||
import { NgxFileDropModule } from 'ngx-file-drop'
|
||||
import { RouterTestingModule } from '@angular/router/testing'
|
||||
import { TourNgBootstrapModule, TourService } from 'ngx-ui-tour-ng-bootstrap'
|
||||
|
||||
describe('DashboardComponent', () => {
|
||||
let component: DashboardComponent
|
||||
let fixture: ComponentFixture<DashboardComponent>
|
||||
let settingsService: SettingsService
|
||||
let tourService: TourService
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
DashboardComponent,
|
||||
StatisticsWidgetComponent,
|
||||
PageHeaderComponent,
|
||||
WidgetFrameComponent,
|
||||
UploadFileWidgetComponent,
|
||||
IfPermissionsDirective,
|
||||
SavedViewWidgetComponent,
|
||||
],
|
||||
providers: [
|
||||
PermissionsGuard,
|
||||
{
|
||||
provide: PermissionsService,
|
||||
useValue: {
|
||||
currentUserCan: () => true,
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: SavedViewService,
|
||||
useValue: {
|
||||
dashboardViews: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'saved view 1',
|
||||
show_on_dashboard: true,
|
||||
sort_field: 'added',
|
||||
sort_reverse: true,
|
||||
filter_rules: [],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'saved view 2',
|
||||
show_on_dashboard: true,
|
||||
sort_field: 'created',
|
||||
sort_reverse: true,
|
||||
filter_rules: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
imports: [
|
||||
NgbAlertModule,
|
||||
HttpClientTestingModule,
|
||||
NgxFileDropModule,
|
||||
RouterTestingModule,
|
||||
TourNgBootstrapModule,
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
settingsService = TestBed.inject(SettingsService)
|
||||
settingsService.currentUser = {
|
||||
first_name: 'Foo',
|
||||
last_name: 'Bar',
|
||||
}
|
||||
tourService = TestBed.inject(TourService)
|
||||
fixture = TestBed.createComponent(DashboardComponent)
|
||||
component = fixture.componentInstance
|
||||
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
it('should show a welcome message', () => {
|
||||
expect(component.subtitle).toEqual(`Hello Foo, welcome to Paperless-ngx`)
|
||||
settingsService.currentUser = {
|
||||
id: 1,
|
||||
}
|
||||
expect(component.subtitle).toEqual(`Welcome to Paperless-ngx`)
|
||||
})
|
||||
|
||||
it('should show dashboard widgets', () => {
|
||||
expect(
|
||||
fixture.debugElement.queryAll(By.directive(SavedViewWidgetComponent))
|
||||
).toHaveLength(2)
|
||||
})
|
||||
|
||||
it('should end tour service if still running and welcome widget dismissed', () => {
|
||||
jest.spyOn(tourService, 'getStatus').mockReturnValueOnce(1)
|
||||
const endSpy = jest.spyOn(tourService, 'end')
|
||||
component.completeTour()
|
||||
expect(endSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should save tour completion if it was stopped and welcome widget dismissed', () => {
|
||||
jest.spyOn(tourService, 'getStatus').mockReturnValueOnce(0)
|
||||
const settingsCompleteTourSpy = jest.spyOn(settingsService, 'completeTour')
|
||||
component.completeTour()
|
||||
expect(settingsCompleteTourSpy).toHaveBeenCalled()
|
||||
})
|
||||
})
|
@ -0,0 +1,165 @@
|
||||
import { DatePipe } from '@angular/common'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { Router } from '@angular/router'
|
||||
import { RouterTestingModule } from '@angular/router/testing'
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { of, Subject } from 'rxjs'
|
||||
import { routes } from 'src/app/app-routing.module'
|
||||
import { FILTER_HAS_TAGS_ALL } from 'src/app/data/filter-rule-type'
|
||||
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view'
|
||||
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
||||
import { PermissionsGuard } from 'src/app/guards/permissions.guard'
|
||||
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
|
||||
import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
|
||||
import {
|
||||
ConsumerStatusService,
|
||||
FileStatus,
|
||||
} from 'src/app/services/consumer-status.service'
|
||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
||||
import { PermissionsService } from 'src/app/services/permissions.service'
|
||||
import { DocumentService } from 'src/app/services/rest/document.service'
|
||||
import { WidgetFrameComponent } from '../widget-frame/widget-frame.component'
|
||||
import { SavedViewWidgetComponent } from './saved-view-widget.component'
|
||||
|
||||
const savedView: PaperlessSavedView = {
|
||||
id: 1,
|
||||
name: 'Saved View 1',
|
||||
sort_field: 'added',
|
||||
sort_reverse: true,
|
||||
show_in_sidebar: true,
|
||||
show_on_dashboard: true,
|
||||
filter_rules: [
|
||||
{
|
||||
rule_type: FILTER_HAS_TAGS_ALL,
|
||||
value: '1,2',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const documentResults = [
|
||||
{
|
||||
id: 2,
|
||||
title: 'doc2',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'doc3',
|
||||
},
|
||||
]
|
||||
|
||||
describe('SavedViewWidgetComponent', () => {
|
||||
let component: SavedViewWidgetComponent
|
||||
let fixture: ComponentFixture<SavedViewWidgetComponent>
|
||||
let documentService: DocumentService
|
||||
let consumerStatusService: ConsumerStatusService
|
||||
let documentListViewService: DocumentListViewService
|
||||
let router: Router
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
SavedViewWidgetComponent,
|
||||
WidgetFrameComponent,
|
||||
IfPermissionsDirective,
|
||||
CustomDatePipe,
|
||||
DocumentTitlePipe,
|
||||
],
|
||||
providers: [
|
||||
PermissionsGuard,
|
||||
DocumentService,
|
||||
{
|
||||
provide: PermissionsService,
|
||||
useValue: {
|
||||
currentUserCan: () => true,
|
||||
},
|
||||
},
|
||||
CustomDatePipe,
|
||||
DatePipe,
|
||||
],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
NgbModule,
|
||||
RouterTestingModule.withRoutes(routes),
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
documentService = TestBed.inject(DocumentService)
|
||||
consumerStatusService = TestBed.inject(ConsumerStatusService)
|
||||
documentListViewService = TestBed.inject(DocumentListViewService)
|
||||
router = TestBed.inject(Router)
|
||||
fixture = TestBed.createComponent(SavedViewWidgetComponent)
|
||||
component = fixture.componentInstance
|
||||
component.savedView = savedView
|
||||
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
it('should show a list of documents', () => {
|
||||
jest.spyOn(documentService, 'listFiltered').mockReturnValue(
|
||||
of({
|
||||
all: [2, 3],
|
||||
count: 2,
|
||||
results: documentResults,
|
||||
})
|
||||
)
|
||||
component.ngOnInit()
|
||||
fixture.detectChanges()
|
||||
expect(fixture.debugElement.nativeElement.textContent).toContain('doc2')
|
||||
expect(fixture.debugElement.nativeElement.textContent).toContain('doc3')
|
||||
})
|
||||
|
||||
it('should call api endpoint and load results', () => {
|
||||
const listAllSpy = jest.spyOn(documentService, 'listFiltered')
|
||||
listAllSpy.mockReturnValue(
|
||||
of({
|
||||
all: [2, 3],
|
||||
count: 2,
|
||||
results: documentResults,
|
||||
})
|
||||
)
|
||||
component.ngOnInit()
|
||||
expect(listAllSpy).toHaveBeenCalledWith(
|
||||
1,
|
||||
10,
|
||||
savedView.sort_field,
|
||||
savedView.sort_reverse,
|
||||
savedView.filter_rules,
|
||||
{
|
||||
truncate_content: true,
|
||||
}
|
||||
)
|
||||
fixture.detectChanges()
|
||||
expect(component.documents).toEqual(documentResults)
|
||||
})
|
||||
|
||||
it('should reload on document consumption finished', () => {
|
||||
const fileStatusSubject = new Subject<FileStatus>()
|
||||
jest
|
||||
.spyOn(consumerStatusService, 'onDocumentConsumptionFinished')
|
||||
.mockReturnValue(fileStatusSubject)
|
||||
const reloadSpy = jest.spyOn(component, 'reload')
|
||||
component.ngOnInit()
|
||||
fileStatusSubject.next(new FileStatus())
|
||||
expect(reloadSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should navigate on showAll', () => {
|
||||
const routerSpy = jest.spyOn(router, 'navigate')
|
||||
component.showAll()
|
||||
expect(routerSpy).toHaveBeenCalledWith(['view', savedView.id])
|
||||
savedView.show_in_sidebar = false
|
||||
component.showAll()
|
||||
expect(routerSpy).toHaveBeenCalledWith(['documents'], {
|
||||
queryParams: { view: savedView.id },
|
||||
})
|
||||
})
|
||||
|
||||
it('should navigate via quickfilter on click tag', () => {
|
||||
const qfSpy = jest.spyOn(documentListViewService, 'quickFilter')
|
||||
component.clickTag({ id: 11, name: 'Tag11' }, new MouseEvent('click'))
|
||||
expect(qfSpy).toHaveBeenCalledWith([
|
||||
{ rule_type: FILTER_HAS_TAGS_ALL, value: '11' },
|
||||
])
|
||||
})
|
||||
})
|
@ -0,0 +1,110 @@
|
||||
import { TestBed } from '@angular/core/testing'
|
||||
import { StatisticsWidgetComponent } from './statistics-widget.component'
|
||||
import { ComponentFixture } from '@angular/core/testing'
|
||||
import {
|
||||
HttpClientTestingModule,
|
||||
HttpTestingController,
|
||||
} from '@angular/common/http/testing'
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { WidgetFrameComponent } from '../widget-frame/widget-frame.component'
|
||||
import { environment } from 'src/environments/environment'
|
||||
import { RouterTestingModule } from '@angular/router/testing'
|
||||
import { routes } from 'src/app/app-routing.module'
|
||||
import { PermissionsGuard } from 'src/app/guards/permissions.guard'
|
||||
|
||||
describe('StatisticsWidgetComponent', () => {
|
||||
let component: StatisticsWidgetComponent
|
||||
let fixture: ComponentFixture<StatisticsWidgetComponent>
|
||||
let httpTestingController: HttpTestingController
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [StatisticsWidgetComponent, WidgetFrameComponent],
|
||||
providers: [PermissionsGuard],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
NgbModule,
|
||||
RouterTestingModule.withRoutes(routes),
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
fixture = TestBed.createComponent(StatisticsWidgetComponent)
|
||||
component = fixture.componentInstance
|
||||
|
||||
httpTestingController = TestBed.inject(HttpTestingController)
|
||||
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
it('should call api statistics endpoint', () => {
|
||||
const req = httpTestingController.expectOne(
|
||||
`${environment.apiBaseUrl}statistics/`
|
||||
)
|
||||
expect(req.request.method).toEqual('GET')
|
||||
})
|
||||
|
||||
it('should display inbox link with count', () => {
|
||||
const mockStats = {
|
||||
documents_total: 200,
|
||||
documents_inbox: 18,
|
||||
inbox_tag: 10,
|
||||
}
|
||||
|
||||
const req = httpTestingController.expectOne(
|
||||
`${environment.apiBaseUrl}statistics/`
|
||||
)
|
||||
|
||||
req.flush(mockStats)
|
||||
fixture.detectChanges()
|
||||
|
||||
const goToInboxSpy = jest.spyOn(component, 'goToInbox')
|
||||
|
||||
expect(fixture.nativeElement.textContent.replace(/\s/g, '')).toContain(
|
||||
'inbox:18'
|
||||
)
|
||||
const link = fixture.nativeElement.querySelector('a') as HTMLAnchorElement
|
||||
expect(link).not.toBeNull()
|
||||
link.click()
|
||||
expect(goToInboxSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should display mime types with counts', () => {
|
||||
const mockStats = {
|
||||
documents_total: 200,
|
||||
documents_inbox: 18,
|
||||
inbox_tag: 10,
|
||||
document_file_type_counts: [
|
||||
{
|
||||
mime_type: 'application/pdf',
|
||||
mime_type_count: 160,
|
||||
},
|
||||
{
|
||||
mime_type: 'text/plain',
|
||||
mime_type_count: 20,
|
||||
},
|
||||
{
|
||||
mime_type: 'text/csv',
|
||||
mime_type_count: 20,
|
||||
},
|
||||
],
|
||||
character_count: 162312,
|
||||
}
|
||||
|
||||
const req = httpTestingController.expectOne(
|
||||
`${environment.apiBaseUrl}statistics/`
|
||||
)
|
||||
|
||||
req.flush(mockStats)
|
||||
fixture.detectChanges()
|
||||
|
||||
expect(fixture.nativeElement.textContent.replace(/\s/g, '')).toContain(
|
||||
'PDF(80%)'
|
||||
)
|
||||
expect(fixture.nativeElement.textContent.replace(/\s/g, '')).toContain(
|
||||
'TXT(10%)'
|
||||
)
|
||||
expect(fixture.nativeElement.textContent.replace(/\s/g, '')).toContain(
|
||||
'CSV(10%)'
|
||||
)
|
||||
})
|
||||
})
|
@ -0,0 +1,173 @@
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { By } from '@angular/platform-browser'
|
||||
import { RouterTestingModule } from '@angular/router/testing'
|
||||
import {
|
||||
NgbModule,
|
||||
NgbAlertModule,
|
||||
NgbAlert,
|
||||
NgbCollapse,
|
||||
} from '@ng-bootstrap/ng-bootstrap'
|
||||
import { NgxFileDropModule } from 'ngx-file-drop'
|
||||
import { routes } from 'src/app/app-routing.module'
|
||||
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
||||
import { PermissionsGuard } from 'src/app/guards/permissions.guard'
|
||||
import {
|
||||
ConsumerStatusService,
|
||||
FileStatus,
|
||||
FileStatusPhase,
|
||||
} from 'src/app/services/consumer-status.service'
|
||||
import { PermissionsService } from 'src/app/services/permissions.service'
|
||||
import { UploadDocumentsService } from 'src/app/services/upload-documents.service'
|
||||
import { WidgetFrameComponent } from '../widget-frame/widget-frame.component'
|
||||
import { UploadFileWidgetComponent } from './upload-file-widget.component'
|
||||
|
||||
describe('UploadFileWidgetComponent', () => {
|
||||
let component: UploadFileWidgetComponent
|
||||
let fixture: ComponentFixture<UploadFileWidgetComponent>
|
||||
let consumerStatusService: ConsumerStatusService
|
||||
let uploadDocumentsService: UploadDocumentsService
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
UploadFileWidgetComponent,
|
||||
WidgetFrameComponent,
|
||||
IfPermissionsDirective,
|
||||
],
|
||||
providers: [
|
||||
PermissionsGuard,
|
||||
{
|
||||
provide: PermissionsService,
|
||||
useValue: {
|
||||
currentUserCan: () => true,
|
||||
},
|
||||
},
|
||||
],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
NgbModule,
|
||||
RouterTestingModule.withRoutes(routes),
|
||||
NgxFileDropModule,
|
||||
NgbAlertModule,
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
consumerStatusService = TestBed.inject(ConsumerStatusService)
|
||||
uploadDocumentsService = TestBed.inject(UploadDocumentsService)
|
||||
fixture = TestBed.createComponent(UploadFileWidgetComponent)
|
||||
component = fixture.componentInstance
|
||||
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
it('should support drop files', () => {
|
||||
const uploadSpy = jest.spyOn(uploadDocumentsService, 'uploadFiles')
|
||||
component.dropped([])
|
||||
expect(uploadSpy).toHaveBeenCalled()
|
||||
// coverage
|
||||
component.fileLeave(null)
|
||||
component.fileOver(null)
|
||||
})
|
||||
|
||||
it('should generate stats summary', () => {
|
||||
mockConsumerStatuses(consumerStatusService)
|
||||
expect(component.getStatusSummary()).toEqual(
|
||||
'Processing: 6, Failed: 1, Added: 4'
|
||||
)
|
||||
})
|
||||
|
||||
it('should report an upload progress summary', () => {
|
||||
mockConsumerStatuses(consumerStatusService)
|
||||
expect(component.getTotalUploadProgress()).toEqual(0.75)
|
||||
})
|
||||
|
||||
it('should change color by status phase', () => {
|
||||
const processingStatus = new FileStatus()
|
||||
processingStatus.phase = FileStatusPhase.PROCESSING
|
||||
expect(component.getStatusColor(processingStatus)).toEqual('primary')
|
||||
const failedStatus = new FileStatus()
|
||||
failedStatus.phase = FileStatusPhase.FAILED
|
||||
expect(component.getStatusColor(failedStatus)).toEqual('danger')
|
||||
const successStatus = new FileStatus()
|
||||
successStatus.phase = FileStatusPhase.SUCCESS
|
||||
expect(component.getStatusColor(successStatus)).toEqual('success')
|
||||
})
|
||||
|
||||
it('should enforce a maximum number of alerts', () => {
|
||||
mockConsumerStatuses(consumerStatusService)
|
||||
fixture.detectChanges()
|
||||
// 5 total, 1 hidden
|
||||
expect(fixture.debugElement.queryAll(By.directive(NgbAlert))).toHaveLength(
|
||||
6
|
||||
)
|
||||
expect(
|
||||
fixture.debugElement
|
||||
.query(By.directive(NgbCollapse))
|
||||
.queryAll(By.directive(NgbAlert))
|
||||
).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('should allow dismissing an alert', () => {
|
||||
const dismissSpy = jest.spyOn(consumerStatusService, 'dismiss')
|
||||
component.dismiss(new FileStatus())
|
||||
expect(dismissSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should allow dismissing all alerts', () => {
|
||||
const dismissSpy = jest.spyOn(consumerStatusService, 'dismissCompleted')
|
||||
component.dismissCompleted()
|
||||
expect(dismissSpy).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
function mockConsumerStatuses(consumerStatusService) {
|
||||
const partialUpload1 = new FileStatus()
|
||||
partialUpload1.currentPhaseProgress = 50
|
||||
partialUpload1.currentPhaseMaxProgress = 50
|
||||
const partialUpload2 = new FileStatus()
|
||||
partialUpload2.currentPhaseProgress = 25
|
||||
partialUpload2.currentPhaseMaxProgress = 50
|
||||
jest
|
||||
.spyOn(consumerStatusService, 'getConsumerStatus')
|
||||
.mockImplementation((phase) => {
|
||||
switch (phase) {
|
||||
case FileStatusPhase.FAILED:
|
||||
return [new FileStatus()]
|
||||
case FileStatusPhase.PROCESSING:
|
||||
return [new FileStatus(), new FileStatus()]
|
||||
case FileStatusPhase.STARTED:
|
||||
return [new FileStatus(), new FileStatus(), new FileStatus()]
|
||||
case FileStatusPhase.SUCCESS:
|
||||
return [
|
||||
new FileStatus(),
|
||||
new FileStatus(),
|
||||
new FileStatus(),
|
||||
new FileStatus(),
|
||||
]
|
||||
case FileStatusPhase.UPLOADING:
|
||||
return [partialUpload1, partialUpload2]
|
||||
default:
|
||||
return [
|
||||
new FileStatus(),
|
||||
new FileStatus(),
|
||||
new FileStatus(),
|
||||
new FileStatus(),
|
||||
new FileStatus(),
|
||||
new FileStatus(),
|
||||
]
|
||||
}
|
||||
})
|
||||
jest
|
||||
.spyOn(consumerStatusService, 'getConsumerStatusNotCompleted')
|
||||
.mockImplementation(() => {
|
||||
return [
|
||||
new FileStatus(),
|
||||
new FileStatus(),
|
||||
new FileStatus(),
|
||||
new FileStatus(),
|
||||
new FileStatus(),
|
||||
new FileStatus(),
|
||||
]
|
||||
})
|
||||
}
|
@ -69,9 +69,6 @@ export class UploadFileWidgetComponent extends ComponentWithPermissions {
|
||||
return this.consumerStatusService.getConsumerStatus(FileStatusPhase.SUCCESS)
|
||||
}
|
||||
|
||||
getStatusCompleted() {
|
||||
return this.consumerStatusService.getConsumerStatusCompleted()
|
||||
}
|
||||
getTotalUploadProgress() {
|
||||
let current = 0
|
||||
let max = 0
|
||||
|
@ -0,0 +1,33 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { By } from '@angular/platform-browser'
|
||||
import { NgbAlertModule, NgbAlert } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { PermissionsGuard } from 'src/app/guards/permissions.guard'
|
||||
import { WidgetFrameComponent } from '../widget-frame/widget-frame.component'
|
||||
import { WelcomeWidgetComponent } from './welcome-widget.component'
|
||||
|
||||
describe('WelcomeWidgetComponent', () => {
|
||||
let component: WelcomeWidgetComponent
|
||||
let fixture: ComponentFixture<WelcomeWidgetComponent>
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [WelcomeWidgetComponent, WidgetFrameComponent],
|
||||
providers: [PermissionsGuard],
|
||||
imports: [NgbAlertModule],
|
||||
}).compileComponents()
|
||||
|
||||
fixture = TestBed.createComponent(WelcomeWidgetComponent)
|
||||
component = fixture.componentInstance
|
||||
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
it('should be dismissable', () => {
|
||||
let dismissResult
|
||||
component.dismiss.subscribe(() => (dismissResult = true))
|
||||
fixture.debugElement
|
||||
.query(By.directive(NgbAlert))
|
||||
.triggerEventHandler('closed')
|
||||
expect(dismissResult).toBeTruthy()
|
||||
})
|
||||
})
|
@ -0,0 +1,53 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { By } from '@angular/platform-browser'
|
||||
import { NgbAlertModule, NgbAlert } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { PermissionsGuard } from 'src/app/guards/permissions.guard'
|
||||
import { WidgetFrameComponent } from './widget-frame.component'
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<div>
|
||||
<button
|
||||
*appIfObjectPermissions="{
|
||||
object: { id: 2, owner: user1 },
|
||||
action: 'view'
|
||||
}"
|
||||
>
|
||||
Some Text
|
||||
</button>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
class TestComponent extends WidgetFrameComponent {}
|
||||
|
||||
describe('WidgetFrameComponent', () => {
|
||||
let component: WidgetFrameComponent
|
||||
let fixture: ComponentFixture<WidgetFrameComponent>
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [WidgetFrameComponent, WidgetFrameComponent],
|
||||
providers: [PermissionsGuard],
|
||||
imports: [NgbAlertModule],
|
||||
}).compileComponents()
|
||||
|
||||
fixture = TestBed.createComponent(WidgetFrameComponent)
|
||||
component = fixture.componentInstance
|
||||
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
it('should show title', () => {
|
||||
component.title = 'Foo'
|
||||
fixture.detectChanges()
|
||||
expect(fixture.debugElement.nativeElement.textContent).toContain('Foo')
|
||||
})
|
||||
|
||||
it('should show loading indicator', () => {
|
||||
expect(fixture.debugElement.query(By.css('.spinner-border'))).toBeNull()
|
||||
component.loading = true
|
||||
fixture.detectChanges()
|
||||
expect(fixture.debugElement.query(By.css('.spinner-border'))).not.toBeNull()
|
||||
})
|
||||
})
|
@ -0,0 +1,58 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { ActivatedRoute, Router, convertToParamMap } from '@angular/router'
|
||||
import { of } from 'rxjs'
|
||||
import { DocumentService } from 'src/app/services/rest/document.service'
|
||||
import { DocumentAsnComponent } from './document-asn.component'
|
||||
import { RouterTestingModule } from '@angular/router/testing'
|
||||
import { FilterRule } from 'src/app/data/filter-rule'
|
||||
import { routes } from 'src/app/app-routing.module'
|
||||
import { PermissionsGuard } from 'src/app/guards/permissions.guard'
|
||||
|
||||
describe('DocumentAsnComponent', () => {
|
||||
let component: DocumentAsnComponent
|
||||
let fixture: ComponentFixture<DocumentAsnComponent>
|
||||
let router: Router
|
||||
let activatedRoute: ActivatedRoute
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [DocumentAsnComponent],
|
||||
providers: [
|
||||
{
|
||||
provide: DocumentService,
|
||||
useValue: {
|
||||
listAllFilteredIds: (rules: FilterRule[]) =>
|
||||
rules[0].value === '1234' ? of([1]) : of([]),
|
||||
},
|
||||
},
|
||||
PermissionsGuard,
|
||||
],
|
||||
imports: [RouterTestingModule.withRoutes(routes)],
|
||||
}).compileComponents()
|
||||
|
||||
router = TestBed.inject(Router)
|
||||
activatedRoute = TestBed.inject(ActivatedRoute)
|
||||
fixture = TestBed.createComponent(DocumentAsnComponent)
|
||||
component = fixture.componentInstance
|
||||
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
it('should navigate on valid asn', () => {
|
||||
jest
|
||||
.spyOn(activatedRoute, 'paramMap', 'get')
|
||||
.mockReturnValue(of(convertToParamMap({ id: '1234' })))
|
||||
const navigateSpy = jest.spyOn(router, 'navigate')
|
||||
component.ngOnInit()
|
||||
expect(navigateSpy).toHaveBeenCalledWith(['documents', 1])
|
||||
})
|
||||
|
||||
it('should 404 on invalid asn', () => {
|
||||
jest
|
||||
.spyOn(activatedRoute, 'paramMap', 'get')
|
||||
.mockReturnValue(of(convertToParamMap({ id: '5578' })))
|
||||
const navigateSpy = jest.spyOn(router, 'navigate')
|
||||
component.ngOnInit()
|
||||
expect(navigateSpy).toHaveBeenCalledWith(['404'])
|
||||
})
|
||||
})
|
@ -0,0 +1,770 @@
|
||||
import { DatePipe } from '@angular/common'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import {
|
||||
ComponentFixture,
|
||||
TestBed,
|
||||
fakeAsync,
|
||||
tick,
|
||||
discardPeriodicTasks,
|
||||
} from '@angular/core/testing'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { By } from '@angular/platform-browser'
|
||||
import { Router, ActivatedRoute, convertToParamMap } from '@angular/router'
|
||||
import { RouterTestingModule } from '@angular/router/testing'
|
||||
import {
|
||||
NgbModal,
|
||||
NgbModule,
|
||||
NgbModalModule,
|
||||
NgbModalRef,
|
||||
NgbDateStruct,
|
||||
} from '@ng-bootstrap/ng-bootstrap'
|
||||
import { NgSelectModule } from '@ng-select/ng-select'
|
||||
import { PdfViewerComponent } from 'ng2-pdf-viewer'
|
||||
import { of, throwError } from 'rxjs'
|
||||
import { routes } from 'src/app/app-routing.module'
|
||||
import {
|
||||
FILTER_FULLTEXT_MORELIKE,
|
||||
FILTER_CORRESPONDENT,
|
||||
FILTER_DOCUMENT_TYPE,
|
||||
FILTER_STORAGE_PATH,
|
||||
FILTER_HAS_TAGS_ALL,
|
||||
FILTER_CREATED_AFTER,
|
||||
FILTER_CREATED_BEFORE,
|
||||
} from 'src/app/data/filter-rule-type'
|
||||
import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'
|
||||
import { PaperlessDocument } from 'src/app/data/paperless-document'
|
||||
import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'
|
||||
import { PaperlessStoragePath } from 'src/app/data/paperless-storage-path'
|
||||
import { PaperlessTag } from 'src/app/data/paperless-tag'
|
||||
import { IfOwnerDirective } from 'src/app/directives/if-owner.directive'
|
||||
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
||||
import { PermissionsGuard } from 'src/app/guards/permissions.guard'
|
||||
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
|
||||
import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
|
||||
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
|
||||
import { SafeUrlPipe } from 'src/app/pipes/safeurl.pipe'
|
||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
||||
import { OpenDocumentsService } from 'src/app/services/open-documents.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 { DocumentService } from 'src/app/services/rest/document.service'
|
||||
import { StoragePathService } from 'src/app/services/rest/storage-path.service'
|
||||
import { UserService } from 'src/app/services/rest/user.service'
|
||||
import { SettingsService } from 'src/app/services/settings.service'
|
||||
import { ToastService } from 'src/app/services/toast.service'
|
||||
import { ConfirmDialogComponent } from '../common/confirm-dialog/confirm-dialog.component'
|
||||
import { CorrespondentEditDialogComponent } from '../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component'
|
||||
import { DocumentTypeEditDialogComponent } from '../common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component'
|
||||
import { StoragePathEditDialogComponent } from '../common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component'
|
||||
import { DateComponent } from '../common/input/date/date.component'
|
||||
import { NumberComponent } from '../common/input/number/number.component'
|
||||
import { PermissionsFormComponent } from '../common/input/permissions/permissions-form/permissions-form.component'
|
||||
import { SelectComponent } from '../common/input/select/select.component'
|
||||
import { TagsComponent } from '../common/input/tags/tags.component'
|
||||
import { TextComponent } from '../common/input/text/text.component'
|
||||
import { PageHeaderComponent } from '../common/page-header/page-header.component'
|
||||
import { DocumentNotesComponent } from '../document-notes/document-notes.component'
|
||||
import { DocumentDetailComponent } from './document-detail.component'
|
||||
|
||||
const doc: PaperlessDocument = {
|
||||
id: 3,
|
||||
title: 'Doc 3',
|
||||
correspondent: 11,
|
||||
document_type: 21,
|
||||
storage_path: 31,
|
||||
tags: [41, 42, 43],
|
||||
content: 'text content',
|
||||
added: new Date(),
|
||||
created: new Date(),
|
||||
archive_serial_number: null,
|
||||
original_file_name: 'file.pdf',
|
||||
owner: null,
|
||||
user_can_change: true,
|
||||
notes: [
|
||||
{
|
||||
created: new Date(),
|
||||
note: 'note 1',
|
||||
user: 1,
|
||||
},
|
||||
{
|
||||
created: new Date(),
|
||||
note: 'note 2',
|
||||
user: 2,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
describe('DocumentDetailComponent', () => {
|
||||
let component: DocumentDetailComponent
|
||||
let fixture: ComponentFixture<DocumentDetailComponent>
|
||||
let router: Router
|
||||
let activatedRoute: ActivatedRoute
|
||||
let documentService: DocumentService
|
||||
let openDocumentsService: OpenDocumentsService
|
||||
let modalService: NgbModal
|
||||
let toastService: ToastService
|
||||
let documentListViewService: DocumentListViewService
|
||||
let settingsService: SettingsService
|
||||
|
||||
let currentUserCan = true
|
||||
let currentUserHasObjectPermissions = true
|
||||
let currentUserOwnsObject = true
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
DocumentDetailComponent,
|
||||
DocumentTitlePipe,
|
||||
PageHeaderComponent,
|
||||
IfPermissionsDirective,
|
||||
TagsComponent,
|
||||
SelectComponent,
|
||||
TextComponent,
|
||||
NumberComponent,
|
||||
DateComponent,
|
||||
DocumentNotesComponent,
|
||||
CustomDatePipe,
|
||||
DocumentTypeEditDialogComponent,
|
||||
CorrespondentEditDialogComponent,
|
||||
StoragePathEditDialogComponent,
|
||||
IfOwnerDirective,
|
||||
PermissionsFormComponent,
|
||||
SafeHtmlPipe,
|
||||
ConfirmDialogComponent,
|
||||
PdfViewerComponent,
|
||||
SafeUrlPipe,
|
||||
],
|
||||
providers: [
|
||||
DocumentTitlePipe,
|
||||
{
|
||||
provide: CorrespondentService,
|
||||
useValue: {
|
||||
listAll: () =>
|
||||
of({
|
||||
results: [
|
||||
{
|
||||
id: 11,
|
||||
name: 'Correspondent11',
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: DocumentTypeService,
|
||||
useValue: {
|
||||
listAll: () =>
|
||||
of({
|
||||
results: [
|
||||
{
|
||||
id: 21,
|
||||
name: 'DocumentType21',
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: StoragePathService,
|
||||
useValue: {
|
||||
listAll: () =>
|
||||
of({
|
||||
results: [
|
||||
{
|
||||
id: 31,
|
||||
name: 'StoragePath31',
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: UserService,
|
||||
useValue: {
|
||||
listAll: () =>
|
||||
of({
|
||||
results: [
|
||||
{
|
||||
id: 1,
|
||||
username: 'user1',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
username: 'user2',
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: PermissionsService,
|
||||
useValue: {
|
||||
currentUserCan: () => currentUserCan,
|
||||
currentUserHasObjectPermissions: () =>
|
||||
currentUserHasObjectPermissions,
|
||||
currentUserOwnsObject: () => currentUserOwnsObject,
|
||||
},
|
||||
},
|
||||
PermissionsGuard,
|
||||
CustomDatePipe,
|
||||
DatePipe,
|
||||
],
|
||||
imports: [
|
||||
RouterTestingModule.withRoutes(routes),
|
||||
HttpClientTestingModule,
|
||||
NgbModule,
|
||||
NgSelectModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NgbModalModule,
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
router = TestBed.inject(Router)
|
||||
activatedRoute = TestBed.inject(ActivatedRoute)
|
||||
jest
|
||||
.spyOn(activatedRoute, 'paramMap', 'get')
|
||||
.mockReturnValue(of(convertToParamMap({ id: 3 })))
|
||||
openDocumentsService = TestBed.inject(OpenDocumentsService)
|
||||
documentService = TestBed.inject(DocumentService)
|
||||
modalService = TestBed.inject(NgbModal)
|
||||
toastService = TestBed.inject(ToastService)
|
||||
documentListViewService = TestBed.inject(DocumentListViewService)
|
||||
settingsService = TestBed.inject(SettingsService)
|
||||
fixture = TestBed.createComponent(DocumentDetailComponent)
|
||||
component = fixture.componentInstance
|
||||
})
|
||||
|
||||
it('should load four tabs via url params', () => {
|
||||
jest
|
||||
.spyOn(activatedRoute, 'paramMap', 'get')
|
||||
.mockReturnValue(of(convertToParamMap({ id: 3, section: 'notes' })))
|
||||
jest.spyOn(openDocumentsService, 'getOpenDocument').mockReturnValue(null)
|
||||
jest
|
||||
.spyOn(openDocumentsService, 'openDocument')
|
||||
.mockReturnValueOnce(of(true))
|
||||
fixture.detectChanges()
|
||||
expect(component.activeNavID).toEqual(5) // DocumentDetailNavIDs.Notes
|
||||
})
|
||||
|
||||
it('should change url on tab switch', () => {
|
||||
initNormally()
|
||||
const navigateSpy = jest.spyOn(router, 'navigate')
|
||||
component.nav.select(5)
|
||||
component.nav.navChange.next({
|
||||
activeId: 1,
|
||||
nextId: 5,
|
||||
preventDefault: () => {},
|
||||
})
|
||||
fixture.detectChanges()
|
||||
expect(navigateSpy).toHaveBeenCalledWith(['documents', 3, 'notes'])
|
||||
})
|
||||
|
||||
it('should update title after debounce', fakeAsync(() => {
|
||||
initNormally()
|
||||
component.titleInput.value = 'Foo Bar'
|
||||
component.titleSubject.next('Foo Bar')
|
||||
tick(1000)
|
||||
expect(component.documentForm.get('title').value).toEqual('Foo Bar')
|
||||
discardPeriodicTasks()
|
||||
}))
|
||||
|
||||
it('should update title before doc change if wasnt updated via debounce', fakeAsync(() => {
|
||||
initNormally()
|
||||
component.titleInput.value = 'Foo Bar'
|
||||
component.titleInput.inputField.nativeElement.dispatchEvent(
|
||||
new Event('change')
|
||||
)
|
||||
tick(1000)
|
||||
expect(component.documentForm.get('title').value).toEqual('Foo Bar')
|
||||
}))
|
||||
|
||||
it('should load non-open document via param', () => {
|
||||
initNormally()
|
||||
expect(component.document).toEqual(doc)
|
||||
})
|
||||
|
||||
it('should load already-opened document via param', () => {
|
||||
jest.spyOn(documentService, 'get').mockReturnValueOnce(of(doc))
|
||||
jest.spyOn(openDocumentsService, 'getOpenDocument').mockReturnValue(doc)
|
||||
fixture.detectChanges() // calls ngOnInit
|
||||
expect(component.document).toEqual(doc)
|
||||
})
|
||||
|
||||
it('should disable form if user cannot edit', () => {
|
||||
currentUserHasObjectPermissions = false
|
||||
initNormally()
|
||||
expect(component.documentForm.disabled).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should support creating document type', () => {
|
||||
initNormally()
|
||||
let openModal: NgbModalRef
|
||||
modalService.activeInstances.subscribe((modal) => (openModal = modal[0]))
|
||||
const modalSpy = jest.spyOn(modalService, 'open')
|
||||
component.createDocumentType('NewDocType2')
|
||||
expect(modalSpy).toHaveBeenCalled()
|
||||
openModal.componentInstance.succeeded.next({ id: 12, name: 'NewDocType12' })
|
||||
expect(component.documentForm.get('document_type').value).toEqual(12)
|
||||
})
|
||||
|
||||
it('should support creating correspondent', () => {
|
||||
initNormally()
|
||||
let openModal: NgbModalRef
|
||||
modalService.activeInstances.subscribe((modal) => (openModal = modal[0]))
|
||||
const modalSpy = jest.spyOn(modalService, 'open')
|
||||
component.createCorrespondent('NewCorrrespondent12')
|
||||
expect(modalSpy).toHaveBeenCalled()
|
||||
openModal.componentInstance.succeeded.next({
|
||||
id: 12,
|
||||
name: 'NewCorrrespondent12',
|
||||
})
|
||||
expect(component.documentForm.get('correspondent').value).toEqual(12)
|
||||
})
|
||||
|
||||
it('should support creating storage path', () => {
|
||||
initNormally()
|
||||
let openModal: NgbModalRef
|
||||
modalService.activeInstances.subscribe((modal) => (openModal = modal[0]))
|
||||
const modalSpy = jest.spyOn(modalService, 'open')
|
||||
component.createStoragePath('NewStoragePath12')
|
||||
expect(modalSpy).toHaveBeenCalled()
|
||||
openModal.componentInstance.succeeded.next({
|
||||
id: 12,
|
||||
name: 'NewStoragePath12',
|
||||
})
|
||||
expect(component.documentForm.get('storage_path').value).toEqual(12)
|
||||
})
|
||||
|
||||
it('should allow dischard changes', () => {
|
||||
initNormally()
|
||||
component.title = 'Foo Bar'
|
||||
fixture.detectChanges()
|
||||
jest.spyOn(documentService, 'get').mockReturnValueOnce(of(doc))
|
||||
component.discard()
|
||||
fixture.detectChanges()
|
||||
expect(component.title).toEqual(doc.title)
|
||||
expect(openDocumentsService.hasDirty()).toBeFalsy()
|
||||
// this time with error, mostly for coverage
|
||||
component.title = 'Foo Bar'
|
||||
fixture.detectChanges()
|
||||
const navigateSpy = jest.spyOn(router, 'navigate')
|
||||
jest
|
||||
.spyOn(documentService, 'get')
|
||||
.mockReturnValueOnce(throwError(() => new Error('unable to discard')))
|
||||
component.discard()
|
||||
fixture.detectChanges()
|
||||
expect(navigateSpy).toHaveBeenCalledWith(['404'])
|
||||
})
|
||||
|
||||
it('should 404 on invalid id', () => {
|
||||
jest.spyOn(documentService, 'get').mockReturnValueOnce(of(null))
|
||||
const navigateSpy = jest.spyOn(router, 'navigate')
|
||||
fixture.detectChanges()
|
||||
expect(navigateSpy).toHaveBeenCalledWith(['404'])
|
||||
})
|
||||
|
||||
it('should support save, close and show success toast', () => {
|
||||
initNormally()
|
||||
component.title = 'Foo Bar'
|
||||
const closeSpy = jest.spyOn(component, 'close')
|
||||
const updateSpy = jest.spyOn(documentService, 'update')
|
||||
const toastSpy = jest.spyOn(toastService, 'showInfo')
|
||||
updateSpy.mockImplementation((o) => of(doc))
|
||||
component.save()
|
||||
expect(updateSpy).toHaveBeenCalled()
|
||||
expect(closeSpy).toHaveBeenCalled()
|
||||
expect(toastSpy).toHaveBeenCalledWith('Document saved successfully.')
|
||||
})
|
||||
|
||||
it('should show toast error on save if error occurs', () => {
|
||||
currentUserHasObjectPermissions = true
|
||||
initNormally()
|
||||
component.title = 'Foo Bar'
|
||||
const closeSpy = jest.spyOn(component, 'close')
|
||||
const updateSpy = jest.spyOn(documentService, 'update')
|
||||
const toastSpy = jest.spyOn(toastService, 'showError')
|
||||
updateSpy.mockImplementation(() =>
|
||||
throwError(() => new Error('failed to save'))
|
||||
)
|
||||
component.save()
|
||||
expect(updateSpy).toHaveBeenCalled()
|
||||
expect(closeSpy).not.toHaveBeenCalled()
|
||||
expect(toastSpy).toHaveBeenCalledWith(
|
||||
'Error saving document: failed to save'
|
||||
)
|
||||
})
|
||||
|
||||
it('should show error toast on save but close if user can no longer edit', () => {
|
||||
currentUserHasObjectPermissions = false
|
||||
initNormally()
|
||||
component.title = 'Foo Bar'
|
||||
const closeSpy = jest.spyOn(component, 'close')
|
||||
const updateSpy = jest.spyOn(documentService, 'update')
|
||||
const toastSpy = jest.spyOn(toastService, 'showInfo')
|
||||
updateSpy.mockImplementation(() =>
|
||||
throwError(() => new Error('failed to save'))
|
||||
)
|
||||
component.save()
|
||||
expect(updateSpy).toHaveBeenCalled()
|
||||
expect(closeSpy).toHaveBeenCalled()
|
||||
expect(toastSpy).toHaveBeenCalledWith('Document saved successfully.')
|
||||
})
|
||||
|
||||
it('should allow save and next', () => {
|
||||
initNormally()
|
||||
const nextDocId = 100
|
||||
component.title = 'Foo Bar'
|
||||
const updateSpy = jest.spyOn(documentService, 'update')
|
||||
updateSpy.mockReturnValue(of(doc))
|
||||
const nextSpy = jest.spyOn(documentListViewService, 'getNext')
|
||||
nextSpy.mockReturnValue(of(nextDocId))
|
||||
const closeSpy = jest.spyOn(openDocumentsService, 'closeDocument')
|
||||
closeSpy.mockReturnValue(of(true))
|
||||
const navigateSpy = jest.spyOn(router, 'navigate')
|
||||
|
||||
component.saveEditNext()
|
||||
expect(updateSpy).toHaveBeenCalled()
|
||||
expect(navigateSpy).toHaveBeenCalledWith(['documents', nextDocId])
|
||||
expect
|
||||
})
|
||||
|
||||
it('should show toast error on saveAll if error occurs', () => {
|
||||
currentUserHasObjectPermissions = true
|
||||
initNormally()
|
||||
component.title = 'Foo Bar'
|
||||
const closeSpy = jest.spyOn(component, 'close')
|
||||
const updateSpy = jest.spyOn(documentService, 'update')
|
||||
const toastSpy = jest.spyOn(toastService, 'showError')
|
||||
updateSpy.mockImplementation(() =>
|
||||
throwError(() => new Error('failed to save'))
|
||||
)
|
||||
component.saveEditNext()
|
||||
expect(updateSpy).toHaveBeenCalled()
|
||||
expect(closeSpy).not.toHaveBeenCalled()
|
||||
expect(toastSpy).toHaveBeenCalledWith(
|
||||
'Error saving document: failed to save'
|
||||
)
|
||||
})
|
||||
|
||||
it('should allow close and navigate to documents by default', () => {
|
||||
initNormally()
|
||||
const navigateSpy = jest.spyOn(router, 'navigate')
|
||||
component.close()
|
||||
expect(navigateSpy).toHaveBeenCalledWith(['documents'])
|
||||
})
|
||||
|
||||
it('should allow close and navigate to documents by default', () => {
|
||||
initNormally()
|
||||
jest
|
||||
.spyOn(documentListViewService, 'activeSavedViewId', 'get')
|
||||
.mockReturnValue(77)
|
||||
const navigateSpy = jest.spyOn(router, 'navigate')
|
||||
component.close()
|
||||
expect(navigateSpy).toHaveBeenCalledWith(['view', 77])
|
||||
})
|
||||
|
||||
it('should not close if e.g. user-cancelled', () => {
|
||||
initNormally()
|
||||
jest.spyOn(openDocumentsService, 'closeDocument').mockReturnValue(of(false))
|
||||
const navigateSpy = jest.spyOn(router, 'navigate')
|
||||
component.close()
|
||||
expect(navigateSpy).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should support delete, ask for confirmation', () => {
|
||||
initNormally()
|
||||
let openModal: NgbModalRef
|
||||
modalService.activeInstances.subscribe((modal) => (openModal = modal[0]))
|
||||
const modalSpy = jest.spyOn(modalService, 'open')
|
||||
const deleteSpy = jest.spyOn(documentService, 'delete')
|
||||
deleteSpy.mockReturnValue(of(true))
|
||||
component.delete()
|
||||
expect(modalSpy).toHaveBeenCalled()
|
||||
const modalCloseSpy = jest.spyOn(openModal, 'close')
|
||||
openModal.componentInstance.confirmClicked.next()
|
||||
expect(deleteSpy).toHaveBeenCalled()
|
||||
expect(modalCloseSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should allow retry delete if error', () => {
|
||||
initNormally()
|
||||
let openModal: NgbModalRef
|
||||
modalService.activeInstances.subscribe((modal) => (openModal = modal[0]))
|
||||
const modalSpy = jest.spyOn(modalService, 'open')
|
||||
const deleteSpy = jest.spyOn(documentService, 'delete')
|
||||
deleteSpy.mockReturnValueOnce(throwError(() => new Error('one time')))
|
||||
component.delete()
|
||||
expect(modalSpy).toHaveBeenCalled()
|
||||
const modalCloseSpy = jest.spyOn(openModal, 'close')
|
||||
openModal.componentInstance.confirmClicked.next()
|
||||
expect(deleteSpy).toHaveBeenCalled()
|
||||
expect(modalCloseSpy).not.toHaveBeenCalled()
|
||||
deleteSpy.mockReturnValueOnce(of(true))
|
||||
// retry
|
||||
openModal.componentInstance.confirmClicked.next()
|
||||
expect(deleteSpy).toHaveBeenCalled()
|
||||
expect(modalCloseSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should support more like quick filter', () => {
|
||||
initNormally()
|
||||
const qfSpy = jest.spyOn(documentListViewService, 'quickFilter')
|
||||
component.moreLike()
|
||||
expect(qfSpy).toHaveBeenCalledWith([
|
||||
{
|
||||
rule_type: FILTER_FULLTEXT_MORELIKE,
|
||||
value: doc.id.toString(),
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it('should support redo ocr, confirm and close modal after started', () => {
|
||||
initNormally()
|
||||
const bulkEditSpy = jest.spyOn(documentService, 'bulkEdit')
|
||||
bulkEditSpy.mockReturnValue(of(true))
|
||||
let openModal: NgbModalRef
|
||||
modalService.activeInstances.subscribe((modal) => (openModal = modal[0]))
|
||||
const modalSpy = jest.spyOn(modalService, 'open')
|
||||
const toastSpy = jest.spyOn(toastService, 'showInfo')
|
||||
component.redoOcr()
|
||||
const modalCloseSpy = jest.spyOn(openModal, 'close')
|
||||
openModal.componentInstance.confirmClicked.next()
|
||||
expect(bulkEditSpy).toHaveBeenCalledWith([doc.id], 'redo_ocr', {})
|
||||
expect(modalSpy).toHaveBeenCalled()
|
||||
expect(toastSpy).toHaveBeenCalled()
|
||||
expect(modalCloseSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should show error if redo ocr call fails', () => {
|
||||
initNormally()
|
||||
const bulkEditSpy = jest.spyOn(documentService, 'bulkEdit')
|
||||
let openModal: NgbModalRef
|
||||
modalService.activeInstances.subscribe((modal) => (openModal = modal[0]))
|
||||
const toastSpy = jest.spyOn(toastService, 'showError')
|
||||
component.redoOcr()
|
||||
const modalCloseSpy = jest.spyOn(openModal, 'close')
|
||||
bulkEditSpy.mockReturnValue(throwError(() => new Error('error occurred')))
|
||||
openModal.componentInstance.confirmClicked.next()
|
||||
expect(toastSpy).toHaveBeenCalled()
|
||||
expect(modalCloseSpy).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should support next doc', () => {
|
||||
initNormally()
|
||||
const serviceSpy = jest.spyOn(documentListViewService, 'getNext')
|
||||
const routerSpy = jest.spyOn(router, 'navigate')
|
||||
serviceSpy.mockReturnValue(of(100))
|
||||
component.nextDoc()
|
||||
expect(serviceSpy).toHaveBeenCalled()
|
||||
expect(routerSpy).toHaveBeenCalledWith(['documents', 100])
|
||||
})
|
||||
|
||||
it('should support previous doc', () => {
|
||||
initNormally()
|
||||
const serviceSpy = jest.spyOn(documentListViewService, 'getPrevious')
|
||||
const routerSpy = jest.spyOn(router, 'navigate')
|
||||
serviceSpy.mockReturnValue(of(100))
|
||||
component.previousDoc()
|
||||
expect(serviceSpy).toHaveBeenCalled()
|
||||
expect(routerSpy).toHaveBeenCalledWith(['documents', 100])
|
||||
})
|
||||
|
||||
it('should support password-protected PDFs with a password field', () => {
|
||||
initNormally()
|
||||
component.onError({ name: 'PasswordException' }) // normally dispatched by pdf viewer
|
||||
expect(component.requiresPassword).toBeTruthy()
|
||||
fixture.detectChanges()
|
||||
expect(
|
||||
fixture.debugElement.query(By.css('input[type=password]'))
|
||||
).not.toBeUndefined()
|
||||
component.password = 'foo'
|
||||
component.pdfPreviewLoaded({ numPages: 1000 } as any)
|
||||
expect(component.requiresPassword).toBeFalsy()
|
||||
})
|
||||
|
||||
it('should support Enter key in password field', () => {
|
||||
initNormally()
|
||||
component.onError({ name: 'PasswordException' }) // normally dispatched by pdf viewer
|
||||
fixture.detectChanges()
|
||||
expect(component.password).toBeUndefined()
|
||||
const pwField = fixture.debugElement.query(By.css('input[type=password]'))
|
||||
pwField.nativeElement.value = 'foobar'
|
||||
pwField.nativeElement.dispatchEvent(
|
||||
new KeyboardEvent('keyup', { key: 'Enter' })
|
||||
)
|
||||
expect(component.password).toEqual('foobar')
|
||||
})
|
||||
|
||||
it('should update n pages after pdf loaded', () => {
|
||||
initNormally()
|
||||
component.pdfPreviewLoaded({ numPages: 1000 } as any)
|
||||
expect(component.previewNumPages).toEqual(1000)
|
||||
})
|
||||
|
||||
it('should support updating notes dynamically', () => {
|
||||
const notes = [
|
||||
{
|
||||
id: 1,
|
||||
note: 'hello world',
|
||||
},
|
||||
]
|
||||
initNormally()
|
||||
const refreshSpy = jest.spyOn(openDocumentsService, 'refreshDocument')
|
||||
component.notesUpdated(notes) // called by notes component
|
||||
expect(component.document.notes).toEqual(notes)
|
||||
expect(refreshSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should support quick filtering by correspondent', () => {
|
||||
initNormally()
|
||||
const object = {
|
||||
id: 22,
|
||||
name: 'Correspondent22',
|
||||
last_correspondence: new Date(),
|
||||
} as PaperlessCorrespondent
|
||||
const qfSpy = jest.spyOn(documentListViewService, 'quickFilter')
|
||||
component.filterDocuments([object])
|
||||
expect(qfSpy).toHaveBeenCalledWith([
|
||||
{
|
||||
rule_type: FILTER_CORRESPONDENT,
|
||||
value: object.id.toString(),
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it('should support quick filtering by doc type', () => {
|
||||
initNormally()
|
||||
const object = { id: 22, name: 'DocumentType22' } as PaperlessDocumentType
|
||||
const qfSpy = jest.spyOn(documentListViewService, 'quickFilter')
|
||||
component.filterDocuments([object])
|
||||
expect(qfSpy).toHaveBeenCalledWith([
|
||||
{
|
||||
rule_type: FILTER_DOCUMENT_TYPE,
|
||||
value: object.id.toString(),
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it('should support quick filtering by storage path', () => {
|
||||
initNormally()
|
||||
const object = {
|
||||
id: 22,
|
||||
name: 'StoragePath22',
|
||||
path: '/foo/bar/',
|
||||
} as PaperlessStoragePath
|
||||
const qfSpy = jest.spyOn(documentListViewService, 'quickFilter')
|
||||
component.filterDocuments([object])
|
||||
expect(qfSpy).toHaveBeenCalledWith([
|
||||
{
|
||||
rule_type: FILTER_STORAGE_PATH,
|
||||
value: object.id.toString(),
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it('should support quick filtering by all tags', () => {
|
||||
initNormally()
|
||||
const object1 = {
|
||||
id: 22,
|
||||
name: 'Tag22',
|
||||
is_inbox_tag: true,
|
||||
color: '#ff0000',
|
||||
text_color: '#000000',
|
||||
} as PaperlessTag
|
||||
const object2 = {
|
||||
id: 23,
|
||||
name: 'Tag22',
|
||||
is_inbox_tag: true,
|
||||
color: '#ff0000',
|
||||
text_color: '#000000',
|
||||
} as PaperlessTag
|
||||
const qfSpy = jest.spyOn(documentListViewService, 'quickFilter')
|
||||
component.filterDocuments([object1, object2])
|
||||
expect(qfSpy).toHaveBeenCalledWith([
|
||||
{
|
||||
rule_type: FILTER_HAS_TAGS_ALL,
|
||||
value: object1.id.toString(),
|
||||
},
|
||||
{
|
||||
rule_type: FILTER_HAS_TAGS_ALL,
|
||||
value: object2.id.toString(),
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it('should support quick filtering by date after - 1d and before +1d', () => {
|
||||
initNormally()
|
||||
const object = { year: 2023, month: 5, day: 14 } as NgbDateStruct
|
||||
const qfSpy = jest.spyOn(documentListViewService, 'quickFilter')
|
||||
component.filterDocuments([object])
|
||||
expect(qfSpy).toHaveBeenCalledWith([
|
||||
{
|
||||
rule_type: FILTER_CREATED_AFTER,
|
||||
value: '2023-05-13',
|
||||
},
|
||||
{
|
||||
rule_type: FILTER_CREATED_BEFORE,
|
||||
value: '2023-05-15',
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it('should detect RTL languages and add css class to content textarea', () => {
|
||||
initNormally()
|
||||
component.metadata = { lang: 'he' }
|
||||
component.nav.select(2) // content
|
||||
fixture.detectChanges()
|
||||
expect(component.isRTL).toBeTruthy()
|
||||
expect(fixture.debugElement.queryAll(By.css('textarea.rtl'))).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should display built-in pdf viewer if not disabled', () => {
|
||||
initNormally()
|
||||
component.metadata = { has_archive_version: true }
|
||||
jest.spyOn(settingsService, 'get').mockReturnValue(false)
|
||||
expect(component.useNativePdfViewer).toBeFalsy()
|
||||
fixture.detectChanges()
|
||||
expect(fixture.debugElement.query(By.css('pdf-viewer'))).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should display native pdf viewer if enabled', () => {
|
||||
initNormally()
|
||||
component.metadata = { has_archive_version: true }
|
||||
jest.spyOn(settingsService, 'get').mockReturnValue(true)
|
||||
expect(component.useNativePdfViewer).toBeTruthy()
|
||||
fixture.detectChanges()
|
||||
expect(fixture.debugElement.query(By.css('object'))).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should attempt to retrieve metadata', () => {
|
||||
const metadataSpy = jest.spyOn(documentService, 'getMetadata')
|
||||
metadataSpy.mockReturnValue(of({ has_archive_version: true }))
|
||||
initNormally()
|
||||
expect(metadataSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should show an error if failed metadata retrieval', () => {
|
||||
const error = new Error('metadata error')
|
||||
jest
|
||||
.spyOn(documentService, 'getMetadata')
|
||||
.mockReturnValue(throwError(() => error))
|
||||
const toastSpy = jest.spyOn(toastService, 'showError')
|
||||
initNormally()
|
||||
expect(toastSpy).toHaveBeenCalledWith(
|
||||
'Error retrieving metadata',
|
||||
10000,
|
||||
error
|
||||
)
|
||||
})
|
||||
|
||||
function initNormally() {
|
||||
jest.spyOn(documentService, 'get').mockReturnValueOnce(of(doc))
|
||||
jest.spyOn(openDocumentsService, 'getOpenDocument').mockReturnValue(null)
|
||||
jest
|
||||
.spyOn(openDocumentsService, 'openDocument')
|
||||
.mockReturnValueOnce(of(true))
|
||||
fixture.detectChanges()
|
||||
}
|
||||
})
|
@ -40,9 +40,6 @@ import {
|
||||
FILTER_CORRESPONDENT,
|
||||
FILTER_CREATED_AFTER,
|
||||
FILTER_CREATED_BEFORE,
|
||||
FILTER_CREATED_DAY,
|
||||
FILTER_CREATED_MONTH,
|
||||
FILTER_CREATED_YEAR,
|
||||
FILTER_DOCUMENT_TYPE,
|
||||
FILTER_FULLTEXT_MORELIKE,
|
||||
FILTER_HAS_TAGS_ALL,
|
||||
@ -62,8 +59,9 @@ import { UserService } from 'src/app/services/rest/user.service'
|
||||
import { PaperlessDocumentNote } from 'src/app/data/paperless-document-note'
|
||||
import { HttpClient } from '@angular/common/http'
|
||||
import { ComponentWithPermissions } from '../with-permissions/with-permissions.component'
|
||||
import { FilterRule } from 'src/app/data/filter-rule'
|
||||
import { EditDialogMode } from '../common/edit-dialog/edit-dialog.component'
|
||||
import { ObjectWithId } from 'src/app/data/object-with-id'
|
||||
import { FilterRule } from 'src/app/data/filter-rule'
|
||||
import { ISODateAdapter } from 'src/app/utils/ngb-iso-date-adapter'
|
||||
|
||||
enum DocumentDetailNavIDs {
|
||||
@ -438,7 +436,7 @@ export class DocumentDetailComponent
|
||||
var modal = this.modalService.open(DocumentTypeEditDialogComponent, {
|
||||
backdrop: 'static',
|
||||
})
|
||||
modal.componentInstance.dialogMode = 'create'
|
||||
modal.componentInstance.dialogMode = EditDialogMode.CREATE
|
||||
if (newName) modal.componentInstance.object = { name: newName }
|
||||
modal.componentInstance.succeeded
|
||||
.pipe(
|
||||
@ -459,7 +457,7 @@ export class DocumentDetailComponent
|
||||
var modal = this.modalService.open(CorrespondentEditDialogComponent, {
|
||||
backdrop: 'static',
|
||||
})
|
||||
modal.componentInstance.dialogMode = 'create'
|
||||
modal.componentInstance.dialogMode = EditDialogMode.CREATE
|
||||
if (newName) modal.componentInstance.object = { name: newName }
|
||||
modal.componentInstance.succeeded
|
||||
.pipe(
|
||||
@ -482,7 +480,7 @@ export class DocumentDetailComponent
|
||||
var modal = this.modalService.open(StoragePathEditDialogComponent, {
|
||||
backdrop: 'static',
|
||||
})
|
||||
modal.componentInstance.dialogMode = 'create'
|
||||
modal.componentInstance.dialogMode = EditDialogMode.CREATE
|
||||
if (newName) modal.componentInstance.object = { name: newName }
|
||||
modal.componentInstance.succeeded
|
||||
.pipe(
|
||||
|
@ -0,0 +1,51 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { MetadataCollapseComponent } from './metadata-collapse.component'
|
||||
import { NgbCollapseModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
|
||||
const metadata = [
|
||||
{
|
||||
namespace: 'http://ns.adobe.com/pdf/1.3/',
|
||||
prefix: 'pdf',
|
||||
key: 'Producer',
|
||||
value: 'pikepdf 2.2.0',
|
||||
},
|
||||
{
|
||||
namespace: 'http://ns.adobe.com/xap/1.0/',
|
||||
prefix: 'xmp',
|
||||
key: 'ModifyDate',
|
||||
value: '2020-12-21T08:42:26+00:00',
|
||||
},
|
||||
]
|
||||
|
||||
describe('MetadataCollapseComponent', () => {
|
||||
let component: MetadataCollapseComponent
|
||||
let fixture: ComponentFixture<MetadataCollapseComponent>
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [MetadataCollapseComponent],
|
||||
providers: [],
|
||||
imports: [NgbCollapseModule],
|
||||
}).compileComponents()
|
||||
|
||||
fixture = TestBed.createComponent(MetadataCollapseComponent)
|
||||
component = fixture.componentInstance
|
||||
})
|
||||
|
||||
it('should display metadata', () => {
|
||||
component.title = 'Foo'
|
||||
fixture.detectChanges()
|
||||
expect(fixture.debugElement.nativeElement.textContent).toContain('Foo')
|
||||
})
|
||||
|
||||
it('should display metadata', () => {
|
||||
component.metadata = metadata
|
||||
fixture.detectChanges()
|
||||
expect(fixture.debugElement.nativeElement.textContent).toContain(
|
||||
'pikepdf 2.2.0'
|
||||
)
|
||||
expect(fixture.debugElement.nativeElement.textContent).toContain(
|
||||
'ModifyDate'
|
||||
)
|
||||
})
|
||||
})
|
@ -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
|
||||
})
|
||||
})
|
@ -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>')
|
||||
})
|
||||
})
|
@ -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 ? '...' : '')
|
||||
)
|
||||
}
|
||||
|
@ -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()
|
||||
}))
|
||||
})
|
@ -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' },
|
||||
])
|
||||
})
|
||||
})
|
@ -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'
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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()) {
|
||||
|
@ -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()
|
||||
})
|
||||
})
|
@ -0,0 +1,187 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { environment } from 'src/environments/environment'
|
||||
import { DocumentNotesComponent } from './document-notes.component'
|
||||
import { UserService } from 'src/app/services/rest/user.service'
|
||||
import { of, throwError } from 'rxjs'
|
||||
import { DocumentNotesService } from 'src/app/services/rest/document-notes.service'
|
||||
import { ToastService } from 'src/app/services/toast.service'
|
||||
import { PaperlessDocumentNote } from 'src/app/data/paperless-document-note'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
|
||||
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
||||
import { DatePipe } from '@angular/common'
|
||||
import { By } from '@angular/platform-browser'
|
||||
import { PermissionsService } from 'src/app/services/permissions.service'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
|
||||
const notes: PaperlessDocumentNote[] = [
|
||||
{
|
||||
id: 23,
|
||||
note: 'Note 23',
|
||||
user: 1,
|
||||
},
|
||||
{
|
||||
id: 24,
|
||||
note: 'Note 24',
|
||||
user: 1,
|
||||
},
|
||||
{
|
||||
id: 25,
|
||||
note: 'Note 25',
|
||||
user: 2,
|
||||
},
|
||||
{
|
||||
id: 30,
|
||||
note: 'Note 30',
|
||||
user: 3,
|
||||
},
|
||||
]
|
||||
|
||||
describe('DocumentNotesComponent', () => {
|
||||
let component: DocumentNotesComponent
|
||||
let fixture: ComponentFixture<DocumentNotesComponent>
|
||||
let notesService: DocumentNotesService
|
||||
let toastService: ToastService
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
DocumentNotesComponent,
|
||||
CustomDatePipe,
|
||||
IfPermissionsDirective,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: UserService,
|
||||
useValue: {
|
||||
listAll: () =>
|
||||
of({
|
||||
results: [
|
||||
{
|
||||
id: 1,
|
||||
username: 'user1',
|
||||
first_name: 'User1',
|
||||
last_name: 'Lastname1',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
username: 'user2',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
username: 'user3',
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: PermissionsService,
|
||||
useValue: {
|
||||
currentUserCan: () => true,
|
||||
},
|
||||
},
|
||||
CustomDatePipe,
|
||||
DatePipe,
|
||||
],
|
||||
imports: [HttpClientTestingModule, FormsModule, ReactiveFormsModule],
|
||||
}).compileComponents()
|
||||
|
||||
notesService = TestBed.inject(DocumentNotesService)
|
||||
toastService = TestBed.inject(ToastService)
|
||||
fixture = TestBed.createComponent(DocumentNotesComponent)
|
||||
component = fixture.componentInstance
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
it('should display notes with user name / username', () => {
|
||||
component.notes = notes
|
||||
fixture.detectChanges()
|
||||
expect(fixture.debugElement.nativeElement.textContent).toContain(
|
||||
notes[0].note
|
||||
)
|
||||
expect(fixture.debugElement.nativeElement.textContent).toContain(
|
||||
notes[1].note
|
||||
)
|
||||
expect(fixture.debugElement.nativeElement.textContent).toContain(
|
||||
notes[2].note
|
||||
)
|
||||
expect(fixture.debugElement.nativeElement.textContent).toContain(
|
||||
notes[3].note
|
||||
)
|
||||
expect(fixture.debugElement.nativeElement.textContent).toContain(
|
||||
'User1 Lastname1'
|
||||
)
|
||||
expect(fixture.debugElement.nativeElement.textContent).toContain('user2')
|
||||
expect(fixture.debugElement.nativeElement.textContent).toContain('user3')
|
||||
})
|
||||
|
||||
it('should handle note user display in all situations', () => {
|
||||
expect(component.displayName({ id: 1, user: 1 })).toEqual(
|
||||
'User1 Lastname1 (user1)'
|
||||
)
|
||||
expect(component.displayName({ id: 1, user: 2 })).toEqual('user2')
|
||||
expect(component.displayName({ id: 1, user: 4 })).toEqual('')
|
||||
expect(component.displayName({ id: 1 })).toEqual('')
|
||||
})
|
||||
|
||||
it('should support note entry, show error if fails', () => {
|
||||
component.documentId = 12
|
||||
const note = 'This is the new note.'
|
||||
const noteTextArea = fixture.debugElement.query(By.css('textarea'))
|
||||
noteTextArea.nativeElement.value = note
|
||||
noteTextArea.nativeElement.dispatchEvent(new Event('input'))
|
||||
fixture.detectChanges()
|
||||
const addSpy = jest.spyOn(notesService, 'addNote')
|
||||
addSpy.mockReturnValueOnce(throwError(() => new Error('error saving note')))
|
||||
const toastsSpy = jest.spyOn(toastService, 'showError')
|
||||
const addButton = fixture.debugElement.query(By.css('button'))
|
||||
addButton.triggerEventHandler('click')
|
||||
expect(addSpy).toHaveBeenCalledWith(12, note)
|
||||
expect(toastsSpy).toHaveBeenCalled()
|
||||
|
||||
addSpy.mockReturnValueOnce(of([...notes, { id: 31, note, user: 1 }]))
|
||||
addButton.triggerEventHandler('click')
|
||||
fixture.detectChanges()
|
||||
expect(fixture.debugElement.nativeElement.textContent).toContain(note)
|
||||
})
|
||||
|
||||
it('should support note save on ctrl+Enter', () => {
|
||||
component.documentId = 12
|
||||
const note = 'This is the new note.'
|
||||
const noteTextArea = fixture.debugElement.query(By.css('textarea'))
|
||||
noteTextArea.nativeElement.value = note
|
||||
const addSpy = jest.spyOn(component, 'addNote')
|
||||
noteTextArea.nativeElement.dispatchEvent(
|
||||
new KeyboardEvent('keydown', { key: 'Enter', ctrlKey: true })
|
||||
)
|
||||
expect(addSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should support delete note, show error if fails', () => {
|
||||
component.documentId = 12
|
||||
component.notes = notes
|
||||
fixture.detectChanges()
|
||||
const deleteButton = fixture.debugElement.queryAll(By.css('button'))[1] // 0 is add button
|
||||
const deleteSpy = jest.spyOn(notesService, 'deleteNote')
|
||||
const toastsSpy = jest.spyOn(toastService, 'showError')
|
||||
deleteSpy.mockReturnValueOnce(
|
||||
throwError(() => new Error('error deleting note'))
|
||||
)
|
||||
deleteButton.triggerEventHandler('click')
|
||||
expect(deleteSpy).toHaveBeenCalledWith(12, notes[0].id)
|
||||
expect(toastsSpy).toHaveBeenCalled()
|
||||
fixture.detectChanges()
|
||||
expect(fixture.debugElement.nativeElement.textContent).toContain(
|
||||
notes[0].note
|
||||
)
|
||||
|
||||
deleteSpy.mockReturnValueOnce(of(notes.slice(1, 2)))
|
||||
deleteButton.triggerEventHandler('click')
|
||||
expect(deleteSpy).toHaveBeenCalledWith(12, notes[0].id)
|
||||
fixture.detectChanges()
|
||||
expect(fixture.debugElement.nativeElement.textContent).not.toContain(
|
||||
notes[0].note
|
||||
)
|
||||
})
|
||||
})
|
@ -2,7 +2,6 @@ import { Component, Input, Output, EventEmitter } from '@angular/core'
|
||||
import { DocumentNotesService } from 'src/app/services/rest/document-notes.service'
|
||||
import { PaperlessDocumentNote } from 'src/app/data/paperless-document-note'
|
||||
import { FormControl, FormGroup } from '@angular/forms'
|
||||
import { first } from 'rxjs/operators'
|
||||
import { ToastService } from 'src/app/services/toast.service'
|
||||
import { ComponentWithPermissions } from '../with-permissions/with-permissions.component'
|
||||
import { UserService } from 'src/app/services/rest/user.service'
|
||||
@ -89,8 +88,8 @@ export class DocumentNotesComponent extends ComponentWithPermissions {
|
||||
const user = this.users?.find((u) => u.id === note.user)
|
||||
if (!user) return ''
|
||||
const nameComponents = []
|
||||
if (user.first_name) nameComponents.unshift(user.first_name)
|
||||
if (user.last_name) nameComponents.unshift(user.last_name)
|
||||
if (user.first_name) nameComponents.push(user.first_name)
|
||||
if (user.last_name) nameComponents.push(user.last_name)
|
||||
if (user.username) {
|
||||
if (nameComponents.length > 0) nameComponents.push(`(${user.username})`)
|
||||
else nameComponents.push(user.username)
|
||||
|
@ -0,0 +1,70 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { CorrespondentListComponent } from './correspondent-list.component'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { DatePipe } from '@angular/common'
|
||||
import { SortableDirective } from 'src/app/directives/sortable.directive'
|
||||
import { NgbPaginationModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { PageHeaderComponent } from '../../common/page-header/page-header.component'
|
||||
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
||||
import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
|
||||
import { of } from 'rxjs'
|
||||
|
||||
describe('CorrespondentListComponent', () => {
|
||||
let component: CorrespondentListComponent
|
||||
let fixture: ComponentFixture<CorrespondentListComponent>
|
||||
let correspondentsService: CorrespondentService
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
CorrespondentListComponent,
|
||||
SortableDirective,
|
||||
PageHeaderComponent,
|
||||
IfPermissionsDirective,
|
||||
],
|
||||
providers: [DatePipe],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
NgbPaginationModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
correspondentsService = TestBed.inject(CorrespondentService)
|
||||
jest.spyOn(correspondentsService, 'listFiltered').mockReturnValue(
|
||||
of({
|
||||
count: 3,
|
||||
all: [1, 2, 3],
|
||||
results: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Correspondent1',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Correspondent2',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Correspondent3',
|
||||
},
|
||||
],
|
||||
})
|
||||
)
|
||||
fixture = TestBed.createComponent(CorrespondentListComponent)
|
||||
component = fixture.componentInstance
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
// Tests are included in management-list.compontent.spec.ts
|
||||
|
||||
it('should use correct delete message', () => {
|
||||
expect(
|
||||
component.getDeleteMessage({ id: 1, name: 'Correspondent1' })
|
||||
).toEqual(
|
||||
'Do you really want to delete the correspondent "Correspondent1"?'
|
||||
)
|
||||
})
|
||||
})
|
@ -0,0 +1,68 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { DatePipe } from '@angular/common'
|
||||
import { SortableDirective } from 'src/app/directives/sortable.directive'
|
||||
import { NgbPaginationModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { PageHeaderComponent } from '../../common/page-header/page-header.component'
|
||||
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
||||
import { of } from 'rxjs'
|
||||
import { DocumentTypeListComponent } from './document-type-list.component'
|
||||
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
|
||||
|
||||
describe('DocumentTypeListComponent', () => {
|
||||
let component: DocumentTypeListComponent
|
||||
let fixture: ComponentFixture<DocumentTypeListComponent>
|
||||
let documentTypeService: DocumentTypeService
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
DocumentTypeListComponent,
|
||||
SortableDirective,
|
||||
PageHeaderComponent,
|
||||
IfPermissionsDirective,
|
||||
],
|
||||
providers: [DatePipe],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
NgbPaginationModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
documentTypeService = TestBed.inject(DocumentTypeService)
|
||||
jest.spyOn(documentTypeService, 'listFiltered').mockReturnValue(
|
||||
of({
|
||||
count: 3,
|
||||
all: [1, 2, 3],
|
||||
results: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'DocumentType1',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'DocumentType2',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'DocumentType3',
|
||||
},
|
||||
],
|
||||
})
|
||||
)
|
||||
fixture = TestBed.createComponent(DocumentTypeListComponent)
|
||||
component = fixture.componentInstance
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
// Tests are included in management-list.compontent.spec.ts
|
||||
|
||||
it('should use correct delete message', () => {
|
||||
expect(
|
||||
component.getDeleteMessage({ id: 1, name: 'DocumentType1' })
|
||||
).toEqual('Do you really want to delete the document type "DocumentType1"?')
|
||||
})
|
||||
})
|
71
src-ui/src/app/components/manage/logs/logs.component.spec.ts
Normal file
71
src-ui/src/app/components/manage/logs/logs.component.spec.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { LogService } from 'src/app/services/rest/log.service'
|
||||
import { PageHeaderComponent } from '../../common/page-header/page-header.component'
|
||||
import { LogsComponent } from './logs.component'
|
||||
import { of, throwError } from 'rxjs'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { NgbModule, NgbNavLink } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { BrowserModule, By } from '@angular/platform-browser'
|
||||
|
||||
const paperless_logs = [
|
||||
'[2023-05-29 03:05:01,224] [DEBUG] [paperless.tasks] Training data unchanged.',
|
||||
'[2023-05-29 04:05:00,622] [DEBUG] [paperless.classifier] Gathering data from database...',
|
||||
'[2023-05-29 04:05:01,213] [DEBUG] [paperless.tasks] Training data unchanged.',
|
||||
'[2023-06-11 00:30:01,774] [INFO] [paperless.sanity_checker] Document contains no OCR data',
|
||||
'[2023-06-11 00:30:01,774] [WARNING] [paperless.sanity_checker] Made up',
|
||||
'[2023-06-11 00:30:01,774] [ERROR] [paperless.sanity_checker] Document contains no OCR data',
|
||||
'[2023-06-11 00:30:01,774] [CRITICAL] [paperless.sanity_checker] Document contains no OCR data',
|
||||
]
|
||||
const mail_logs = [
|
||||
'[2023-06-09 01:10:00,666] [DEBUG] [paperless_mail] Rule inbox@example.com.Incoming: Searching folder with criteria (SINCE 10-May-2023 UNSEEN)',
|
||||
'[2023-06-09 01:10:01,385] [DEBUG] [paperless_mail] Rule inbox@example.com.Incoming: Processed 3 matching mail(s)',
|
||||
]
|
||||
|
||||
describe('LogsComponent', () => {
|
||||
let component: LogsComponent
|
||||
let fixture: ComponentFixture<LogsComponent>
|
||||
let logService: LogService
|
||||
let logSpy
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [LogsComponent, PageHeaderComponent],
|
||||
providers: [],
|
||||
imports: [HttpClientTestingModule, BrowserModule, NgbModule],
|
||||
}).compileComponents()
|
||||
|
||||
logService = TestBed.inject(LogService)
|
||||
jest.spyOn(logService, 'list').mockReturnValue(of(['paperless', 'mail']))
|
||||
logSpy = jest.spyOn(logService, 'get')
|
||||
logSpy.mockImplementation((id) => {
|
||||
return of(id === 'paperless' ? paperless_logs : mail_logs)
|
||||
})
|
||||
fixture = TestBed.createComponent(LogsComponent)
|
||||
component = fixture.componentInstance
|
||||
window.HTMLElement.prototype.scroll = function () {} // mock scroll
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
it('should display logs with first log initially', () => {
|
||||
expect(logSpy).toHaveBeenCalledWith('paperless')
|
||||
fixture.detectChanges()
|
||||
expect(fixture.debugElement.nativeElement.textContent).toContain(
|
||||
paperless_logs[0]
|
||||
)
|
||||
})
|
||||
|
||||
it('should load log when tab clicked', () => {
|
||||
fixture.debugElement
|
||||
.queryAll(By.directive(NgbNavLink))[1]
|
||||
.nativeElement.dispatchEvent(new MouseEvent('click'))
|
||||
expect(logSpy).toHaveBeenCalledWith('mail')
|
||||
})
|
||||
|
||||
it('should handle error with no logs', () => {
|
||||
logSpy.mockReturnValueOnce(
|
||||
throwError(() => new Error('error getting logs'))
|
||||
)
|
||||
component.reloadLogs()
|
||||
expect(component.logs).toHaveLength(0)
|
||||
})
|
||||
})
|
@ -0,0 +1,232 @@
|
||||
import { DatePipe } from '@angular/common'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import {
|
||||
ComponentFixture,
|
||||
TestBed,
|
||||
fakeAsync,
|
||||
tick,
|
||||
} from '@angular/core/testing'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { By } from '@angular/platform-browser'
|
||||
import {
|
||||
NgbModal,
|
||||
NgbModalModule,
|
||||
NgbModalRef,
|
||||
NgbPaginationModule,
|
||||
} from '@ng-bootstrap/ng-bootstrap'
|
||||
import { of, throwError } from 'rxjs'
|
||||
import { PaperlessTag } from 'src/app/data/paperless-tag'
|
||||
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
||||
import { SortableDirective } from 'src/app/directives/sortable.directive'
|
||||
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
|
||||
import { TagService } from 'src/app/services/rest/tag.service'
|
||||
import { PageHeaderComponent } from '../../common/page-header/page-header.component'
|
||||
import { TagListComponent } from '../tag-list/tag-list.component'
|
||||
import { ManagementListComponent } from './management-list.component'
|
||||
import { PermissionsService } from 'src/app/services/permissions.service'
|
||||
import { ToastService } from 'src/app/services/toast.service'
|
||||
import { EditDialogComponent } from '../../common/edit-dialog/edit-dialog.component'
|
||||
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
|
||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
||||
import { FILTER_HAS_TAGS_ALL } from 'src/app/data/filter-rule-type'
|
||||
import { RouterTestingModule } from '@angular/router/testing'
|
||||
import { routes } from 'src/app/app-routing.module'
|
||||
import { PermissionsGuard } from 'src/app/guards/permissions.guard'
|
||||
import { MATCH_AUTO } from 'src/app/data/matching-model'
|
||||
import { MATCH_NONE } from 'src/app/data/matching-model'
|
||||
import { MATCH_LITERAL } from 'src/app/data/matching-model'
|
||||
|
||||
const tags: PaperlessTag[] = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Tag1 Foo',
|
||||
matching_algorithm: MATCH_LITERAL,
|
||||
match: 'foo',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Tag2',
|
||||
matching_algorithm: MATCH_NONE,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Tag3',
|
||||
matching_algorithm: MATCH_AUTO,
|
||||
},
|
||||
]
|
||||
|
||||
describe('ManagementListComponent', () => {
|
||||
let component: ManagementListComponent<PaperlessTag>
|
||||
let fixture: ComponentFixture<ManagementListComponent<PaperlessTag>>
|
||||
let tagService: TagService
|
||||
let modalService: NgbModal
|
||||
let toastService: ToastService
|
||||
let documentListViewService: DocumentListViewService
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
TagListComponent,
|
||||
SortableDirective,
|
||||
PageHeaderComponent,
|
||||
IfPermissionsDirective,
|
||||
SafeHtmlPipe,
|
||||
ConfirmDialogComponent,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: PermissionsService,
|
||||
useValue: {
|
||||
currentUserCan: () => true,
|
||||
currentUserHasObjectPermissions: () => true,
|
||||
currentUserOwnsObject: () => true,
|
||||
},
|
||||
},
|
||||
DatePipe,
|
||||
PermissionsGuard,
|
||||
],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
NgbPaginationModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NgbModalModule,
|
||||
RouterTestingModule.withRoutes(routes),
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
tagService = TestBed.inject(TagService)
|
||||
jest
|
||||
.spyOn(tagService, 'listFiltered')
|
||||
.mockImplementation(
|
||||
(page, pageSize, sortField, sortReverse, nameFilter, fullPerms) => {
|
||||
const results = nameFilter
|
||||
? tags.filter((t) => t.name.toLowerCase().includes(nameFilter))
|
||||
: tags
|
||||
return of({
|
||||
count: results.length,
|
||||
all: results.map((o) => o.id),
|
||||
results,
|
||||
})
|
||||
}
|
||||
)
|
||||
modalService = TestBed.inject(NgbModal)
|
||||
toastService = TestBed.inject(ToastService)
|
||||
documentListViewService = TestBed.inject(DocumentListViewService)
|
||||
fixture = TestBed.createComponent(TagListComponent)
|
||||
component = fixture.componentInstance
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
// These tests are shared among all management list components
|
||||
|
||||
it('should support filtering, clear on Esc key', fakeAsync(() => {
|
||||
const nameFilterInput = fixture.debugElement.query(By.css('input'))
|
||||
nameFilterInput.nativeElement.value = 'foo'
|
||||
// nameFilterInput.nativeElement.dispatchEvent(new Event('input'))
|
||||
component.nameFilter = 'foo' // subject normally triggered by ngModel
|
||||
tick(400) // debounce
|
||||
fixture.detectChanges()
|
||||
expect(component.data).toEqual([tags[0]])
|
||||
|
||||
nameFilterInput.nativeElement.dispatchEvent(
|
||||
new KeyboardEvent('keyup', { code: 'Escape' })
|
||||
)
|
||||
tick(400) // debounce
|
||||
fixture.detectChanges()
|
||||
expect(component.nameFilter).toBeNull()
|
||||
expect(component.data).toEqual(tags)
|
||||
}))
|
||||
|
||||
it('should support create, show notification on error / success', () => {
|
||||
let modal: NgbModalRef
|
||||
modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
|
||||
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
||||
const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
|
||||
const reloadSpy = jest.spyOn(component, 'reloadData')
|
||||
|
||||
const createButton = fixture.debugElement.queryAll(By.css('button'))[0]
|
||||
createButton.triggerEventHandler('click')
|
||||
|
||||
expect(modal).not.toBeUndefined()
|
||||
const editDialog =
|
||||
modal.componentInstance as EditDialogComponent<PaperlessTag>
|
||||
|
||||
// fail first
|
||||
editDialog.failed.emit({ error: 'error creating item' })
|
||||
expect(toastErrorSpy).toHaveBeenCalled()
|
||||
expect(reloadSpy).not.toHaveBeenCalled()
|
||||
|
||||
// succeed
|
||||
editDialog.succeeded.emit()
|
||||
expect(toastInfoSpy).toHaveBeenCalled()
|
||||
expect(reloadSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should support edit, show notification on error / success', () => {
|
||||
let modal: NgbModalRef
|
||||
modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
|
||||
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
||||
const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
|
||||
const reloadSpy = jest.spyOn(component, 'reloadData')
|
||||
|
||||
const editButton = fixture.debugElement.queryAll(By.css('button'))[3]
|
||||
editButton.triggerEventHandler('click')
|
||||
|
||||
expect(modal).not.toBeUndefined()
|
||||
const editDialog =
|
||||
modal.componentInstance as EditDialogComponent<PaperlessTag>
|
||||
expect(editDialog.object).toEqual(tags[0])
|
||||
|
||||
// fail first
|
||||
editDialog.failed.emit({ error: 'error editing item' })
|
||||
expect(toastErrorSpy).toHaveBeenCalled()
|
||||
expect(reloadSpy).not.toHaveBeenCalled()
|
||||
|
||||
// succeed
|
||||
editDialog.succeeded.emit()
|
||||
expect(toastInfoSpy).toHaveBeenCalled()
|
||||
expect(reloadSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should support delete, show notification on error / success', () => {
|
||||
let modal: NgbModalRef
|
||||
modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
|
||||
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
||||
const deleteSpy = jest.spyOn(tagService, 'delete')
|
||||
const reloadSpy = jest.spyOn(component, 'reloadData')
|
||||
|
||||
const deleteButton = fixture.debugElement.queryAll(By.css('button'))[4]
|
||||
deleteButton.triggerEventHandler('click')
|
||||
|
||||
expect(modal).not.toBeUndefined()
|
||||
const editDialog = modal.componentInstance as ConfirmDialogComponent
|
||||
|
||||
// fail first
|
||||
deleteSpy.mockReturnValueOnce(throwError(() => new Error('error deleting')))
|
||||
editDialog.confirmClicked.emit()
|
||||
expect(toastErrorSpy).toHaveBeenCalled()
|
||||
expect(reloadSpy).not.toHaveBeenCalled()
|
||||
|
||||
// succeed
|
||||
deleteSpy.mockReturnValueOnce(of(true))
|
||||
editDialog.confirmClicked.emit()
|
||||
expect(reloadSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should support quick filter for objects', () => {
|
||||
const qfSpy = jest.spyOn(documentListViewService, 'quickFilter')
|
||||
const filterButton = fixture.debugElement.queryAll(By.css('button'))[2]
|
||||
filterButton.triggerEventHandler('click')
|
||||
expect(qfSpy).toHaveBeenCalledWith([
|
||||
{ rule_type: FILTER_HAS_TAGS_ALL, value: tags[0].id.toString() },
|
||||
]) // subclasses set the filter rule type
|
||||
})
|
||||
|
||||
it('should reload on sort', () => {
|
||||
const reloadSpy = jest.spyOn(component, 'reloadData')
|
||||
const sortable = fixture.debugElement.query(By.directive(SortableDirective))
|
||||
sortable.triggerEventHandler('click')
|
||||
expect(reloadSpy).toHaveBeenCalled()
|
||||
})
|
||||
})
|
@ -28,7 +28,10 @@ import {
|
||||
import { AbstractNameFilterService } from 'src/app/services/rest/abstract-name-filter-service'
|
||||
import { ToastService } from 'src/app/services/toast.service'
|
||||
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
|
||||
import { EditDialogComponent } from '../../common/edit-dialog/edit-dialog.component'
|
||||
import {
|
||||
EditDialogComponent,
|
||||
EditDialogMode,
|
||||
} from '../../common/edit-dialog/edit-dialog.component'
|
||||
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
|
||||
|
||||
export interface ManagementListColumn {
|
||||
@ -135,7 +138,7 @@ export abstract class ManagementListComponent<T extends ObjectWithId>
|
||||
var activeModal = this.modalService.open(this.editDialogComponent, {
|
||||
backdrop: 'static',
|
||||
})
|
||||
activeModal.componentInstance.dialogMode = 'create'
|
||||
activeModal.componentInstance.dialogMode = EditDialogMode.CREATE
|
||||
activeModal.componentInstance.succeeded.subscribe(() => {
|
||||
this.reloadData()
|
||||
this.toastService.showInfo(
|
||||
@ -156,7 +159,7 @@ export abstract class ManagementListComponent<T extends ObjectWithId>
|
||||
backdrop: 'static',
|
||||
})
|
||||
activeModal.componentInstance.object = object
|
||||
activeModal.componentInstance.dialogMode = 'edit'
|
||||
activeModal.componentInstance.dialogMode = EditDialogMode.EDIT
|
||||
activeModal.componentInstance.succeeded.subscribe(() => {
|
||||
this.reloadData()
|
||||
this.toastService.showInfo(
|
||||
@ -172,9 +175,7 @@ export abstract class ManagementListComponent<T extends ObjectWithId>
|
||||
})
|
||||
}
|
||||
|
||||
getDeleteMessage(object: T) {
|
||||
return $localize`Do you really want to delete the ${this.typeName}?`
|
||||
}
|
||||
abstract getDeleteMessage(object: T)
|
||||
|
||||
filterDocuments(object: ObjectWithId) {
|
||||
this.documentListViewService.quickFilter([
|
||||
|
@ -0,0 +1,484 @@
|
||||
import { ViewportScroller, DatePipe } from '@angular/common'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import {
|
||||
ComponentFixture,
|
||||
TestBed,
|
||||
fakeAsync,
|
||||
tick,
|
||||
} from '@angular/core/testing'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { By } from '@angular/platform-browser'
|
||||
import { Router, ActivatedRoute, convertToParamMap } from '@angular/router'
|
||||
import { RouterTestingModule } from '@angular/router/testing'
|
||||
import {
|
||||
NgbModal,
|
||||
NgbModule,
|
||||
NgbNavLink,
|
||||
NgbModalRef,
|
||||
} from '@ng-bootstrap/ng-bootstrap'
|
||||
import { of, throwError } from 'rxjs'
|
||||
import { routes } from 'src/app/app-routing.module'
|
||||
import { PaperlessMailAccount } from 'src/app/data/paperless-mail-account'
|
||||
import { PaperlessMailRule } from 'src/app/data/paperless-mail-rule'
|
||||
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view'
|
||||
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
|
||||
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
||||
import { PermissionsGuard } from 'src/app/guards/permissions.guard'
|
||||
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
|
||||
import { PermissionsService } from 'src/app/services/permissions.service'
|
||||
import { GroupService } from 'src/app/services/rest/group.service'
|
||||
import { MailAccountService } from 'src/app/services/rest/mail-account.service'
|
||||
import { MailRuleService } from 'src/app/services/rest/mail-rule.service'
|
||||
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
|
||||
import { UserService } from 'src/app/services/rest/user.service'
|
||||
import { SettingsService } from 'src/app/services/settings.service'
|
||||
import { ToastService, Toast } from 'src/app/services/toast.service'
|
||||
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
|
||||
import { GroupEditDialogComponent } from '../../common/edit-dialog/group-edit-dialog/group-edit-dialog.component'
|
||||
import { MailAccountEditDialogComponent } from '../../common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component'
|
||||
import { MailRuleEditDialogComponent } from '../../common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component'
|
||||
import { UserEditDialogComponent } from '../../common/edit-dialog/user-edit-dialog/user-edit-dialog.component'
|
||||
import { CheckComponent } from '../../common/input/check/check.component'
|
||||
import { ColorComponent } from '../../common/input/color/color.component'
|
||||
import { PageHeaderComponent } from '../../common/page-header/page-header.component'
|
||||
import { SettingsComponent } from './settings.component'
|
||||
|
||||
const savedViews = [
|
||||
{ id: 1, name: 'view1' },
|
||||
{ id: 2, name: 'view2' },
|
||||
]
|
||||
const users = [
|
||||
{ id: 1, username: 'user1' },
|
||||
{ id: 2, username: 'user2' },
|
||||
]
|
||||
const groups = [
|
||||
{ id: 1, name: 'group1' },
|
||||
{ id: 2, name: 'group2' },
|
||||
]
|
||||
const mailAccounts = [
|
||||
{ id: 1, name: 'account1' },
|
||||
{ id: 2, name: 'account2' },
|
||||
]
|
||||
const mailRules = [
|
||||
{ id: 1, name: 'rule1' },
|
||||
{ id: 2, name: 'rule2' },
|
||||
]
|
||||
|
||||
describe('SettingsComponent', () => {
|
||||
let component: SettingsComponent
|
||||
let fixture: ComponentFixture<SettingsComponent>
|
||||
let modalService: NgbModal
|
||||
let router: Router
|
||||
let settingsService: SettingsService
|
||||
let savedViewService: SavedViewService
|
||||
let activatedRoute: ActivatedRoute
|
||||
let viewportScroller: ViewportScroller
|
||||
let toastService: ToastService
|
||||
let userService: UserService
|
||||
let groupService: GroupService
|
||||
let mailAccountService: MailAccountService
|
||||
let mailRuleService: MailRuleService
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
SettingsComponent,
|
||||
PageHeaderComponent,
|
||||
IfPermissionsDirective,
|
||||
CustomDatePipe,
|
||||
ConfirmDialogComponent,
|
||||
CheckComponent,
|
||||
ColorComponent,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: PermissionsService,
|
||||
useValue: {
|
||||
currentUserCan: () => true,
|
||||
},
|
||||
},
|
||||
CustomDatePipe,
|
||||
DatePipe,
|
||||
PermissionsGuard,
|
||||
],
|
||||
imports: [
|
||||
NgbModule,
|
||||
HttpClientTestingModule,
|
||||
RouterTestingModule.withRoutes(routes),
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
modalService = TestBed.inject(NgbModal)
|
||||
router = TestBed.inject(Router)
|
||||
activatedRoute = TestBed.inject(ActivatedRoute)
|
||||
viewportScroller = TestBed.inject(ViewportScroller)
|
||||
toastService = TestBed.inject(ToastService)
|
||||
settingsService = TestBed.inject(SettingsService)
|
||||
userService = TestBed.inject(UserService)
|
||||
jest.spyOn(userService, 'listAll').mockReturnValue(
|
||||
of({
|
||||
all: users.map((u) => u.id),
|
||||
count: users.length,
|
||||
results: users.concat([]),
|
||||
})
|
||||
)
|
||||
groupService = TestBed.inject(GroupService)
|
||||
jest.spyOn(groupService, 'listAll').mockReturnValue(
|
||||
of({
|
||||
all: groups.map((g) => g.id),
|
||||
count: groups.length,
|
||||
results: groups.concat([]),
|
||||
})
|
||||
)
|
||||
savedViewService = TestBed.inject(SavedViewService)
|
||||
jest.spyOn(savedViewService, 'listAll').mockReturnValue(
|
||||
of({
|
||||
all: savedViews.map((v) => v.id),
|
||||
count: savedViews.length,
|
||||
results: (savedViews as PaperlessSavedView[]).concat([]),
|
||||
})
|
||||
)
|
||||
mailAccountService = TestBed.inject(MailAccountService)
|
||||
jest.spyOn(mailAccountService, 'listAll').mockReturnValue(
|
||||
of({
|
||||
all: mailAccounts.map((a) => a.id),
|
||||
count: mailAccounts.length,
|
||||
results: (mailAccounts as PaperlessMailAccount[]).concat([]),
|
||||
})
|
||||
)
|
||||
mailRuleService = TestBed.inject(MailRuleService)
|
||||
jest.spyOn(mailRuleService, 'listAll').mockReturnValue(
|
||||
of({
|
||||
all: mailRules.map((r) => r.id),
|
||||
count: mailRules.length,
|
||||
results: (mailRules as PaperlessMailRule[]).concat([]),
|
||||
})
|
||||
)
|
||||
|
||||
fixture = TestBed.createComponent(SettingsComponent)
|
||||
component = fixture.componentInstance
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
it('should support tabbed settings & change URL, prevent navigation if dirty confirmation rejected', () => {
|
||||
const navigateSpy = jest.spyOn(router, 'navigate')
|
||||
const tabButtons = fixture.debugElement.queryAll(By.directive(NgbNavLink))
|
||||
tabButtons[1].nativeElement.dispatchEvent(new MouseEvent('click'))
|
||||
expect(navigateSpy).toHaveBeenCalledWith(['settings', 'notifications'])
|
||||
tabButtons[2].nativeElement.dispatchEvent(new MouseEvent('click'))
|
||||
expect(navigateSpy).toHaveBeenCalledWith(['settings', 'savedviews'])
|
||||
tabButtons[3].nativeElement.dispatchEvent(new MouseEvent('click'))
|
||||
expect(navigateSpy).toHaveBeenCalledWith(['settings', 'mail'])
|
||||
tabButtons[4].nativeElement.dispatchEvent(new MouseEvent('click'))
|
||||
expect(navigateSpy).toHaveBeenCalledWith(['settings', 'usersgroups'])
|
||||
|
||||
const initSpy = jest.spyOn(component, 'initialize')
|
||||
component.isDirty = true // mock dirty
|
||||
navigateSpy.mockResolvedValueOnce(false) // nav rejected cause dirty
|
||||
tabButtons[0].nativeElement.dispatchEvent(new MouseEvent('click'))
|
||||
expect(navigateSpy).toHaveBeenCalledWith(['settings', 'general'])
|
||||
expect(initSpy).not.toHaveBeenCalled()
|
||||
|
||||
navigateSpy.mockResolvedValueOnce(true) // nav accepted even though dirty
|
||||
tabButtons[1].nativeElement.dispatchEvent(new MouseEvent('click'))
|
||||
expect(navigateSpy).toHaveBeenCalledWith(['settings', 'notifications'])
|
||||
expect(initSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should support direct link to tab by URL, scroll if needed', () => {
|
||||
jest
|
||||
.spyOn(activatedRoute, 'paramMap', 'get')
|
||||
.mockReturnValue(of(convertToParamMap({ section: 'mail' })))
|
||||
activatedRoute.snapshot.fragment = '#mail'
|
||||
const scrollSpy = jest.spyOn(viewportScroller, 'scrollToAnchor')
|
||||
component.ngOnInit()
|
||||
expect(component.activeNavID).toEqual(4) // Mail
|
||||
component.ngAfterViewInit()
|
||||
expect(scrollSpy).toHaveBeenCalledWith('#mail')
|
||||
})
|
||||
|
||||
it('should lazy load tab data', () => {
|
||||
const tabButtons = fixture.debugElement.queryAll(By.directive(NgbNavLink))
|
||||
|
||||
expect(component.savedViews).toBeUndefined()
|
||||
tabButtons[2].nativeElement.dispatchEvent(
|
||||
new MouseEvent('mouseover', { bubbles: true })
|
||||
)
|
||||
expect(component.savedViews).not.toBeUndefined()
|
||||
|
||||
expect(component.mailAccounts).toBeUndefined()
|
||||
tabButtons[3].nativeElement.dispatchEvent(
|
||||
new MouseEvent('mouseover', { bubbles: true })
|
||||
)
|
||||
expect(component.mailAccounts).not.toBeUndefined()
|
||||
|
||||
expect(component.users).toBeUndefined()
|
||||
tabButtons[4].nativeElement.dispatchEvent(
|
||||
new MouseEvent('mouseover', { bubbles: true })
|
||||
)
|
||||
expect(component.users).not.toBeUndefined()
|
||||
})
|
||||
|
||||
it('should support save saved views, show error', () => {
|
||||
component.maybeInitializeTab(3) // SavedViews
|
||||
|
||||
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
||||
const toastSpy = jest.spyOn(toastService, 'show')
|
||||
const savedViewPatchSpy = jest.spyOn(savedViewService, 'patchMany')
|
||||
|
||||
// saved views error first
|
||||
savedViewPatchSpy.mockReturnValueOnce(
|
||||
throwError(() => new Error('unable to save saved views'))
|
||||
)
|
||||
component.saveSettings()
|
||||
expect(toastErrorSpy).toHaveBeenCalled()
|
||||
expect(savedViewPatchSpy).toHaveBeenCalled()
|
||||
toastSpy.mockClear()
|
||||
toastErrorSpy.mockClear()
|
||||
savedViewPatchSpy.mockClear()
|
||||
|
||||
// succeed saved views
|
||||
savedViewPatchSpy.mockReturnValueOnce(
|
||||
of(savedViews as PaperlessSavedView[])
|
||||
)
|
||||
component.saveSettings()
|
||||
expect(toastErrorSpy).not.toHaveBeenCalled()
|
||||
expect(savedViewPatchSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should support save local settings updating appearance settings and calling API, show error', () => {
|
||||
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
||||
const toastSpy = jest.spyOn(toastService, 'show')
|
||||
const storeSpy = jest.spyOn(settingsService, 'storeSettings')
|
||||
const appearanceSettingsSpy = jest.spyOn(
|
||||
settingsService,
|
||||
'updateAppearanceSettings'
|
||||
)
|
||||
const setSpy = jest.spyOn(settingsService, 'set')
|
||||
|
||||
// error first
|
||||
storeSpy.mockReturnValueOnce(
|
||||
throwError(() => new Error('unable to save settings'))
|
||||
)
|
||||
component.saveSettings()
|
||||
expect(toastErrorSpy).toHaveBeenCalled()
|
||||
expect(storeSpy).toHaveBeenCalled()
|
||||
expect(appearanceSettingsSpy).not.toHaveBeenCalled()
|
||||
expect(setSpy).toHaveBeenCalledTimes(19)
|
||||
|
||||
// succeed
|
||||
storeSpy.mockReturnValueOnce(of(true))
|
||||
component.saveSettings()
|
||||
expect(toastSpy).toHaveBeenCalled()
|
||||
expect(appearanceSettingsSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should offer reload if settings changes require', () => {
|
||||
let toast: Toast
|
||||
toastService.getToasts().subscribe((t) => (toast = t[0]))
|
||||
component.initialize(true) // reset
|
||||
component.store.getValue()['displayLanguage'] = 'en-US'
|
||||
component.store.getValue()['updateCheckingEnabled'] = false
|
||||
component.settingsForm.value.displayLanguage = 'en-GB'
|
||||
component.settingsForm.value.updateCheckingEnabled = true
|
||||
jest.spyOn(settingsService, 'storeSettings').mockReturnValueOnce(of(true))
|
||||
component.saveSettings()
|
||||
expect(toast.actionName).toEqual('Reload now')
|
||||
})
|
||||
|
||||
it('should allow setting theme color, visually apply change immediately but not save', () => {
|
||||
const appearanceSpy = jest.spyOn(
|
||||
settingsService,
|
||||
'updateAppearanceSettings'
|
||||
)
|
||||
const colorInput = fixture.debugElement.query(By.directive(ColorComponent))
|
||||
colorInput.query(By.css('input')).nativeElement.value = '#ff0000'
|
||||
colorInput
|
||||
.query(By.css('input'))
|
||||
.nativeElement.dispatchEvent(new Event('change'))
|
||||
fixture.detectChanges()
|
||||
expect(appearanceSpy).toHaveBeenCalled()
|
||||
expect(settingsService.get(SETTINGS_KEYS.THEME_COLOR)).toEqual('')
|
||||
component.clearThemeColor()
|
||||
})
|
||||
|
||||
it('should support delete saved view', () => {
|
||||
component.maybeInitializeTab(3) // SavedViews
|
||||
const toastSpy = jest.spyOn(toastService, 'showInfo')
|
||||
const deleteSpy = jest.spyOn(savedViewService, 'delete')
|
||||
deleteSpy.mockReturnValue(of(true))
|
||||
component.deleteSavedView(savedViews[0] as PaperlessSavedView)
|
||||
expect(deleteSpy).toHaveBeenCalled()
|
||||
expect(toastSpy).toHaveBeenCalledWith(
|
||||
`Saved view "${savedViews[0].name}" deleted.`
|
||||
)
|
||||
})
|
||||
|
||||
it('should support edit / create user, show error if needed', () => {
|
||||
let modal: NgbModalRef
|
||||
modalService.activeInstances.subscribe((refs) => (modal = refs[0]))
|
||||
component.editUser(users[0])
|
||||
const editDialog = modal.componentInstance as UserEditDialogComponent
|
||||
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
||||
const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
|
||||
editDialog.failed.emit()
|
||||
expect(toastErrorSpy).toBeCalled()
|
||||
settingsService.currentUser = users[1] // simulate logged in as different user
|
||||
editDialog.succeeded.emit(users[0])
|
||||
expect(toastInfoSpy).toHaveBeenCalledWith(
|
||||
`Saved user "${users[0].username}".`
|
||||
)
|
||||
})
|
||||
|
||||
it('should support delete user, show error if needed', () => {
|
||||
let modal: NgbModalRef
|
||||
modalService.activeInstances.subscribe((refs) => (modal = refs[0]))
|
||||
component.deleteUser(users[0])
|
||||
const deleteDialog = modal.componentInstance as ConfirmDialogComponent
|
||||
const deleteSpy = jest.spyOn(userService, 'delete')
|
||||
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
||||
const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
|
||||
const listAllSpy = jest.spyOn(userService, 'listAll')
|
||||
deleteSpy.mockReturnValueOnce(
|
||||
throwError(() => new Error('error deleting user'))
|
||||
)
|
||||
deleteDialog.confirm()
|
||||
expect(toastErrorSpy).toBeCalled()
|
||||
deleteSpy.mockReturnValueOnce(of(true))
|
||||
deleteDialog.confirm()
|
||||
expect(listAllSpy).toHaveBeenCalled()
|
||||
expect(toastInfoSpy).toHaveBeenCalledWith('Deleted user')
|
||||
})
|
||||
|
||||
it('should logout current user if password changed, after delay', fakeAsync(() => {
|
||||
let modal: NgbModalRef
|
||||
modalService.activeInstances.subscribe((refs) => (modal = refs[0]))
|
||||
component.editUser(users[0])
|
||||
const editDialog = modal.componentInstance as UserEditDialogComponent
|
||||
editDialog.passwordIsSet = true
|
||||
settingsService.currentUser = users[0] // simulate logged in as same user
|
||||
editDialog.succeeded.emit(users[0])
|
||||
fixture.detectChanges()
|
||||
Object.defineProperty(window, 'location', {
|
||||
value: {
|
||||
href: 'http://localhost/',
|
||||
},
|
||||
writable: true, // possibility to override
|
||||
})
|
||||
tick(2600)
|
||||
expect(window.location.href).toContain('logout')
|
||||
}))
|
||||
|
||||
it('should support edit / create group, show error if needed', () => {
|
||||
let modal: NgbModalRef
|
||||
modalService.activeInstances.subscribe((refs) => (modal = refs[0]))
|
||||
component.editGroup(groups[0])
|
||||
const editDialog = modal.componentInstance as GroupEditDialogComponent
|
||||
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
||||
const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
|
||||
editDialog.failed.emit()
|
||||
expect(toastErrorSpy).toBeCalled()
|
||||
editDialog.succeeded.emit(groups[0])
|
||||
expect(toastInfoSpy).toHaveBeenCalledWith(
|
||||
`Saved group "${groups[0].name}".`
|
||||
)
|
||||
})
|
||||
|
||||
it('should support delete group, show error if needed', () => {
|
||||
let modal: NgbModalRef
|
||||
modalService.activeInstances.subscribe((refs) => (modal = refs[0]))
|
||||
component.deleteGroup(users[0])
|
||||
const deleteDialog = modal.componentInstance as ConfirmDialogComponent
|
||||
const deleteSpy = jest.spyOn(groupService, 'delete')
|
||||
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
||||
const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
|
||||
const listAllSpy = jest.spyOn(groupService, 'listAll')
|
||||
deleteSpy.mockReturnValueOnce(
|
||||
throwError(() => new Error('error deleting group'))
|
||||
)
|
||||
deleteDialog.confirm()
|
||||
expect(toastErrorSpy).toBeCalled()
|
||||
deleteSpy.mockReturnValueOnce(of(true))
|
||||
deleteDialog.confirm()
|
||||
expect(listAllSpy).toHaveBeenCalled()
|
||||
expect(toastInfoSpy).toHaveBeenCalledWith('Deleted group')
|
||||
})
|
||||
|
||||
it('should get group name', () => {
|
||||
component.maybeInitializeTab(5) // UsersGroups
|
||||
expect(component.getGroupName(1)).toEqual(groups[0].name)
|
||||
expect(component.getGroupName(11)).toEqual('')
|
||||
})
|
||||
|
||||
it('should support edit / create mail account, show error if needed', () => {
|
||||
let modal: NgbModalRef
|
||||
modalService.activeInstances.subscribe((refs) => (modal = refs[0]))
|
||||
component.editMailAccount(mailAccounts[0] as PaperlessMailAccount)
|
||||
const editDialog = modal.componentInstance as MailAccountEditDialogComponent
|
||||
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
||||
const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
|
||||
editDialog.failed.emit()
|
||||
expect(toastErrorSpy).toBeCalled()
|
||||
editDialog.succeeded.emit(mailAccounts[0])
|
||||
expect(toastInfoSpy).toHaveBeenCalledWith(
|
||||
`Saved account "${mailAccounts[0].name}".`
|
||||
)
|
||||
})
|
||||
|
||||
it('should support delete mail account, show error if needed', () => {
|
||||
let modal: NgbModalRef
|
||||
modalService.activeInstances.subscribe((refs) => (modal = refs[0]))
|
||||
component.deleteMailAccount(mailAccounts[0] as PaperlessMailAccount)
|
||||
const deleteDialog = modal.componentInstance as ConfirmDialogComponent
|
||||
const deleteSpy = jest.spyOn(mailAccountService, 'delete')
|
||||
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
||||
const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
|
||||
const listAllSpy = jest.spyOn(mailAccountService, 'listAll')
|
||||
deleteSpy.mockReturnValueOnce(
|
||||
throwError(() => new Error('error deleting mail account'))
|
||||
)
|
||||
deleteDialog.confirm()
|
||||
expect(toastErrorSpy).toBeCalled()
|
||||
deleteSpy.mockReturnValueOnce(of(true))
|
||||
deleteDialog.confirm()
|
||||
expect(listAllSpy).toHaveBeenCalled()
|
||||
expect(toastInfoSpy).toHaveBeenCalledWith('Deleted mail account')
|
||||
})
|
||||
|
||||
it('should support edit / create mail rule, show error if needed', () => {
|
||||
let modal: NgbModalRef
|
||||
modalService.activeInstances.subscribe((refs) => (modal = refs[0]))
|
||||
component.editMailRule(mailRules[0] as PaperlessMailRule)
|
||||
const editDialog = modal.componentInstance as MailRuleEditDialogComponent
|
||||
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
||||
const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
|
||||
editDialog.failed.emit()
|
||||
expect(toastErrorSpy).toBeCalled()
|
||||
editDialog.succeeded.emit(mailRules[0])
|
||||
expect(toastInfoSpy).toHaveBeenCalledWith(
|
||||
`Saved rule "${mailRules[0].name}".`
|
||||
)
|
||||
})
|
||||
|
||||
it('should support delete mail rule, show error if needed', () => {
|
||||
let modal: NgbModalRef
|
||||
modalService.activeInstances.subscribe((refs) => (modal = refs[0]))
|
||||
component.deleteMailRule(mailRules[0] as PaperlessMailRule)
|
||||
const deleteDialog = modal.componentInstance as ConfirmDialogComponent
|
||||
const deleteSpy = jest.spyOn(mailRuleService, 'delete')
|
||||
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
||||
const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
|
||||
const listAllSpy = jest.spyOn(mailRuleService, 'listAll')
|
||||
deleteSpy.mockReturnValueOnce(
|
||||
throwError(() => new Error('error deleting mail rule'))
|
||||
)
|
||||
deleteDialog.confirm()
|
||||
expect(toastErrorSpy).toBeCalled()
|
||||
deleteSpy.mockReturnValueOnce(of(true))
|
||||
deleteDialog.confirm()
|
||||
expect(listAllSpy).toHaveBeenCalled()
|
||||
expect(toastInfoSpy).toHaveBeenCalledWith('Deleted mail rule')
|
||||
})
|
||||
})
|
@ -44,6 +44,7 @@ import { MailAccountService } from 'src/app/services/rest/mail-account.service'
|
||||
import { MailRuleService } from 'src/app/services/rest/mail-rule.service'
|
||||
import { MailAccountEditDialogComponent } from '../../common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component'
|
||||
import { MailRuleEditDialogComponent } from '../../common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component'
|
||||
import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component'
|
||||
|
||||
enum SettingsNavIDs {
|
||||
General = 1,
|
||||
@ -225,9 +226,9 @@ export class SettingsComponent
|
||||
|
||||
onNavChange(navChangeEvent: NgbNavChangeEvent) {
|
||||
this.maybeInitializeTab(navChangeEvent.nextId)
|
||||
const [foundNavIDkey, foundNavIDValue] = Object.entries(
|
||||
SettingsNavIDs
|
||||
).find(([navIDkey, navIDValue]) => navIDValue == navChangeEvent.nextId)
|
||||
const [foundNavIDkey] = Object.entries(SettingsNavIDs).find(
|
||||
([, navIDValue]) => navIDValue == navChangeEvent.nextId
|
||||
)
|
||||
if (foundNavIDkey)
|
||||
// if its dirty we need to wait for confirmation
|
||||
this.router
|
||||
@ -579,8 +580,8 @@ export class SettingsComponent
|
||||
delay: 5000,
|
||||
}
|
||||
if (reloadRequired) {
|
||||
;(savedToast.content = $localize`Settings were saved successfully. Reload is required to apply some changes.`),
|
||||
(savedToast.actionName = $localize`Reload now`)
|
||||
savedToast.content = $localize`Settings were saved successfully. Reload is required to apply some changes.`
|
||||
savedToast.actionName = $localize`Reload now`
|
||||
savedToast.action = () => {
|
||||
location.reload()
|
||||
}
|
||||
@ -646,7 +647,9 @@ export class SettingsComponent
|
||||
backdrop: 'static',
|
||||
size: 'xl',
|
||||
})
|
||||
modal.componentInstance.dialogMode = user ? 'edit' : 'create'
|
||||
modal.componentInstance.dialogMode = user
|
||||
? EditDialogMode.EDIT
|
||||
: EditDialogMode.CREATE
|
||||
modal.componentInstance.object = user
|
||||
modal.componentInstance.succeeded
|
||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||
@ -718,7 +721,9 @@ export class SettingsComponent
|
||||
backdrop: 'static',
|
||||
size: 'lg',
|
||||
})
|
||||
modal.componentInstance.dialogMode = group ? 'edit' : 'create'
|
||||
modal.componentInstance.dialogMode = group
|
||||
? EditDialogMode.EDIT
|
||||
: EditDialogMode.CREATE
|
||||
modal.componentInstance.object = group
|
||||
modal.componentInstance.succeeded
|
||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||
@ -780,7 +785,9 @@ export class SettingsComponent
|
||||
backdrop: 'static',
|
||||
size: 'xl',
|
||||
})
|
||||
modal.componentInstance.dialogMode = account ? 'edit' : 'create'
|
||||
modal.componentInstance.dialogMode = account
|
||||
? EditDialogMode.EDIT
|
||||
: EditDialogMode.CREATE
|
||||
modal.componentInstance.object = account
|
||||
modal.componentInstance.succeeded
|
||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||
@ -842,7 +849,9 @@ export class SettingsComponent
|
||||
backdrop: 'static',
|
||||
size: 'xl',
|
||||
})
|
||||
modal.componentInstance.dialogMode = rule ? 'edit' : 'create'
|
||||
modal.componentInstance.dialogMode = rule
|
||||
? EditDialogMode.EDIT
|
||||
: EditDialogMode.CREATE
|
||||
modal.componentInstance.object = rule
|
||||
modal.componentInstance.succeeded
|
||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||
|
@ -0,0 +1,68 @@
|
||||
import { DatePipe } from '@angular/common'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { NgbPaginationModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { of } from 'rxjs'
|
||||
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
||||
import { SortableDirective } from 'src/app/directives/sortable.directive'
|
||||
import { StoragePathService } from 'src/app/services/rest/storage-path.service'
|
||||
import { PageHeaderComponent } from '../../common/page-header/page-header.component'
|
||||
import { StoragePathListComponent } from './storage-path-list.component'
|
||||
|
||||
describe('StoragePathListComponent', () => {
|
||||
let component: StoragePathListComponent
|
||||
let fixture: ComponentFixture<StoragePathListComponent>
|
||||
let storagePathService: StoragePathService
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
StoragePathListComponent,
|
||||
SortableDirective,
|
||||
PageHeaderComponent,
|
||||
IfPermissionsDirective,
|
||||
],
|
||||
providers: [DatePipe],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
NgbPaginationModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
storagePathService = TestBed.inject(StoragePathService)
|
||||
jest.spyOn(storagePathService, 'listFiltered').mockReturnValue(
|
||||
of({
|
||||
count: 3,
|
||||
all: [1, 2, 3],
|
||||
results: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'StoragePath1',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'StoragePath2',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'StoragePath3',
|
||||
},
|
||||
],
|
||||
})
|
||||
)
|
||||
fixture = TestBed.createComponent(StoragePathListComponent)
|
||||
component = fixture.componentInstance
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
// Tests are included in management-list.compontent.spec.ts
|
||||
|
||||
it('should use correct delete message', () => {
|
||||
expect(component.getDeleteMessage({ id: 1, name: 'StoragePath1' })).toEqual(
|
||||
'Do you really want to delete the storage path "StoragePath1"?'
|
||||
)
|
||||
})
|
||||
})
|
@ -0,0 +1,70 @@
|
||||
import { DatePipe } from '@angular/common'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { NgbPaginationModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { of } from 'rxjs'
|
||||
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
||||
import { SortableDirective } from 'src/app/directives/sortable.directive'
|
||||
import { TagService } from 'src/app/services/rest/tag.service'
|
||||
import { PageHeaderComponent } from '../../common/page-header/page-header.component'
|
||||
import { TagListComponent } from './tag-list.component'
|
||||
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
|
||||
|
||||
describe('TagListComponent', () => {
|
||||
let component: TagListComponent
|
||||
let fixture: ComponentFixture<TagListComponent>
|
||||
let tagService: TagService
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
TagListComponent,
|
||||
SortableDirective,
|
||||
PageHeaderComponent,
|
||||
IfPermissionsDirective,
|
||||
SafeHtmlPipe,
|
||||
],
|
||||
providers: [DatePipe],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
NgbPaginationModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
tagService = TestBed.inject(TagService)
|
||||
jest.spyOn(tagService, 'listFiltered').mockReturnValue(
|
||||
of({
|
||||
count: 3,
|
||||
all: [1, 2, 3],
|
||||
results: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Tag1',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Tag2',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Tag3',
|
||||
},
|
||||
],
|
||||
})
|
||||
)
|
||||
fixture = TestBed.createComponent(TagListComponent)
|
||||
component = fixture.componentInstance
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
// Tests are included in management-list.compontent.spec.ts
|
||||
|
||||
it('should use correct delete message', () => {
|
||||
expect(component.getDeleteMessage({ id: 1, name: 'Tag1' })).toEqual(
|
||||
'Do you really want to delete the tag "Tag1"?'
|
||||
)
|
||||
})
|
||||
})
|
@ -107,25 +107,25 @@
|
||||
|
||||
<ul ngbNav #nav="ngbNav" [(activeId)]="activeTab" class="nav-tabs" (hidden)="duringTabChange($event)">
|
||||
<li ngbNavItem="failed">
|
||||
<a ngbNavLink i18n>Failed <span *ngIf="tasksService.failedFileTasks.length > 0" class="badge bg-danger ms-1">{{tasksService.failedFileTasks.length}}</span></a>
|
||||
<a ngbNavLink i18n>Failed<span *ngIf="tasksService.failedFileTasks.length > 0" class="badge bg-danger ms-2">{{tasksService.failedFileTasks.length}}</span></a>
|
||||
<ng-template ngbNavContent>
|
||||
<ng-container [ngTemplateOutlet]="tasksTemplate" [ngTemplateOutletContext]="{tasks:tasksService.failedFileTasks}"></ng-container>
|
||||
</ng-template>
|
||||
</li>
|
||||
<li ngbNavItem="completed">
|
||||
<a ngbNavLink i18n>Complete <span *ngIf="tasksService.completedFileTasks.length > 0" class="badge bg-secondary ms-1">{{tasksService.completedFileTasks.length}}</span></a>
|
||||
<a ngbNavLink i18n>Complete<span *ngIf="tasksService.completedFileTasks.length > 0" class="badge bg-secondary ms-2">{{tasksService.completedFileTasks.length}}</span></a>
|
||||
<ng-template ngbNavContent>
|
||||
<ng-container [ngTemplateOutlet]="tasksTemplate" [ngTemplateOutletContext]="{tasks:tasksService.completedFileTasks}"></ng-container>
|
||||
</ng-template>
|
||||
</li>
|
||||
<li ngbNavItem="started">
|
||||
<a ngbNavLink i18n>Started <span *ngIf="tasksService.startedFileTasks.length > 0" class="badge bg-secondary ms-1">{{tasksService.startedFileTasks.length}}</span></a>
|
||||
<a ngbNavLink i18n>Started<span *ngIf="tasksService.startedFileTasks.length > 0" class="badge bg-secondary ms-2">{{tasksService.startedFileTasks.length}}</span></a>
|
||||
<ng-template ngbNavContent>
|
||||
<ng-container [ngTemplateOutlet]="tasksTemplate" [ngTemplateOutletContext]="{tasks:tasksService.startedFileTasks}"></ng-container>
|
||||
</ng-template>
|
||||
</li>
|
||||
<li ngbNavItem="queued">
|
||||
<a ngbNavLink i18n>Queued <span *ngIf="tasksService.queuedFileTasks.length > 0" class="badge bg-secondary ms-1">{{tasksService.queuedFileTasks.length}}</span></a>
|
||||
<a ngbNavLink i18n>Queued<span *ngIf="tasksService.queuedFileTasks.length > 0" class="badge bg-secondary ms-2">{{tasksService.queuedFileTasks.length}}</span></a>
|
||||
<ng-template ngbNavContent>
|
||||
<ng-container [ngTemplateOutlet]="tasksTemplate" [ngTemplateOutletContext]="{tasks:tasksService.queuedFileTasks}"></ng-container>
|
||||
</ng-template>
|
||||
|
272
src-ui/src/app/components/manage/tasks/tasks.component.spec.ts
Normal file
272
src-ui/src/app/components/manage/tasks/tasks.component.spec.ts
Normal file
@ -0,0 +1,272 @@
|
||||
import { DatePipe } from '@angular/common'
|
||||
import {
|
||||
HttpTestingController,
|
||||
HttpClientTestingModule,
|
||||
} from '@angular/common/http/testing'
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { By } from '@angular/platform-browser'
|
||||
import { Router } from '@angular/router'
|
||||
import { RouterTestingModule } from '@angular/router/testing'
|
||||
import {
|
||||
NgbModal,
|
||||
NgbModule,
|
||||
NgbNavItem,
|
||||
NgbModalRef,
|
||||
} from '@ng-bootstrap/ng-bootstrap'
|
||||
import { routes } from 'src/app/app-routing.module'
|
||||
import {
|
||||
PaperlessTask,
|
||||
PaperlessTaskType,
|
||||
PaperlessTaskStatus,
|
||||
} from 'src/app/data/paperless-task'
|
||||
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
||||
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
|
||||
import { PermissionsService } from 'src/app/services/permissions.service'
|
||||
import { TasksService } from 'src/app/services/tasks.service'
|
||||
import { environment } from 'src/environments/environment'
|
||||
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
|
||||
import { PageHeaderComponent } from '../../common/page-header/page-header.component'
|
||||
import { TasksComponent } from './tasks.component'
|
||||
import { PermissionsGuard } from 'src/app/guards/permissions.guard'
|
||||
|
||||
const tasks: PaperlessTask[] = [
|
||||
{
|
||||
id: 467,
|
||||
task_id: '11ca1a5b-9f81-442c-b2c8-7e4ae53657f1',
|
||||
task_file_name: 'test.pdf',
|
||||
date_created: new Date('2023-03-01T10:26:03.093116Z'),
|
||||
date_done: new Date('2023-03-01T10:26:07.223048Z'),
|
||||
type: PaperlessTaskType.File,
|
||||
status: PaperlessTaskStatus.Failed,
|
||||
result: 'test.pd: Not consuming test.pdf: It is a duplicate of test (#100)',
|
||||
acknowledged: false,
|
||||
related_document: null,
|
||||
},
|
||||
{
|
||||
id: 466,
|
||||
task_id: '10ca1a5b-3c08-442c-b2c8-7e4ae53657f1',
|
||||
task_file_name: '191092.pdf',
|
||||
date_created: new Date('2023-03-01T09:26:03.093116Z'),
|
||||
date_done: new Date('2023-03-01T09:26:07.223048Z'),
|
||||
type: PaperlessTaskType.File,
|
||||
status: PaperlessTaskStatus.Failed,
|
||||
result:
|
||||
'191092.pd: Not consuming 191092.pdf: It is a duplicate of 191092 (#311)',
|
||||
acknowledged: false,
|
||||
related_document: null,
|
||||
},
|
||||
{
|
||||
id: 465,
|
||||
task_id: '3612d477-bb04-44e3-985b-ac580dd496d8',
|
||||
task_file_name: 'Scan Jun 6, 2023 at 3.19 PM.pdf',
|
||||
date_created: new Date('2023-06-06T15:22:05.722323-07:00'),
|
||||
date_done: new Date('2023-06-06T15:22:14.564305-07:00'),
|
||||
type: PaperlessTaskType.File,
|
||||
status: PaperlessTaskStatus.Pending,
|
||||
result: null,
|
||||
acknowledged: false,
|
||||
related_document: null,
|
||||
},
|
||||
{
|
||||
id: 464,
|
||||
task_id: '2eac4716-2aa6-4dcd-9953-264e11656d7e',
|
||||
task_file_name: 'paperless-mail-l4dkg8ir',
|
||||
date_created: new Date('2023-06-04T11:24:32.898089-07:00'),
|
||||
date_done: new Date('2023-06-04T11:24:44.678605-07:00'),
|
||||
type: PaperlessTaskType.File,
|
||||
status: PaperlessTaskStatus.Complete,
|
||||
result: 'Success. New document id 422 created',
|
||||
acknowledged: false,
|
||||
related_document: 422,
|
||||
},
|
||||
{
|
||||
id: 463,
|
||||
task_id: '28125528-1575-4d6b-99e6-168906e8fa5c',
|
||||
task_file_name: 'onlinePaymentSummary.pdf',
|
||||
date_created: new Date('2023-06-01T13:49:51.631305-07:00'),
|
||||
date_done: new Date('2023-06-01T13:49:54.190220-07:00'),
|
||||
type: PaperlessTaskType.File,
|
||||
status: PaperlessTaskStatus.Complete,
|
||||
result: 'Success. New document id 421 created',
|
||||
acknowledged: false,
|
||||
related_document: 421,
|
||||
},
|
||||
{
|
||||
id: 462,
|
||||
task_id: 'a5b9ca47-0c8e-490f-a04c-6db5d5fc09e5',
|
||||
task_file_name: 'paperless-mail-_rrpmqk6',
|
||||
date_created: new Date('2023-06-07T02:54:35.694916Z'),
|
||||
date_done: null,
|
||||
type: PaperlessTaskType.File,
|
||||
status: PaperlessTaskStatus.Started,
|
||||
result: null,
|
||||
acknowledged: false,
|
||||
related_document: null,
|
||||
},
|
||||
]
|
||||
|
||||
describe('TasksComponent', () => {
|
||||
let component: TasksComponent
|
||||
let fixture: ComponentFixture<TasksComponent>
|
||||
let tasksService: TasksService
|
||||
let modalService: NgbModal
|
||||
let router: Router
|
||||
let httpTestingController: HttpTestingController
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
TasksComponent,
|
||||
PageHeaderComponent,
|
||||
IfPermissionsDirective,
|
||||
CustomDatePipe,
|
||||
ConfirmDialogComponent,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: PermissionsService,
|
||||
useValue: {
|
||||
currentUserCan: () => true,
|
||||
},
|
||||
},
|
||||
CustomDatePipe,
|
||||
DatePipe,
|
||||
PermissionsGuard,
|
||||
],
|
||||
imports: [
|
||||
NgbModule,
|
||||
HttpClientTestingModule,
|
||||
RouterTestingModule.withRoutes(routes),
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
tasksService = TestBed.inject(TasksService)
|
||||
httpTestingController = TestBed.inject(HttpTestingController)
|
||||
modalService = TestBed.inject(NgbModal)
|
||||
router = TestBed.inject(Router)
|
||||
fixture = TestBed.createComponent(TasksComponent)
|
||||
component = fixture.componentInstance
|
||||
fixture.detectChanges()
|
||||
httpTestingController
|
||||
.expectOne(`${environment.apiBaseUrl}tasks/`)
|
||||
.flush(tasks)
|
||||
})
|
||||
|
||||
it('should display file tasks in 4 tabs by status', () => {
|
||||
const tabButtons = fixture.debugElement.queryAll(By.directive(NgbNavItem))
|
||||
|
||||
let currentTasksLength = tasks.filter(
|
||||
(t) => t.status === PaperlessTaskStatus.Failed
|
||||
).length
|
||||
component.activeTab = 'failed'
|
||||
fixture.detectChanges()
|
||||
expect(tabButtons[0].nativeElement.textContent).toEqual(
|
||||
`Failed${currentTasksLength}`
|
||||
)
|
||||
expect(
|
||||
fixture.debugElement.queryAll(By.css('input[type="checkbox"]'))
|
||||
).toHaveLength(currentTasksLength + 1)
|
||||
|
||||
currentTasksLength = tasks.filter(
|
||||
(t) => t.status === PaperlessTaskStatus.Complete
|
||||
).length
|
||||
component.activeTab = 'completed'
|
||||
fixture.detectChanges()
|
||||
expect(tabButtons[1].nativeElement.textContent).toEqual(
|
||||
`Complete${currentTasksLength}`
|
||||
)
|
||||
|
||||
currentTasksLength = tasks.filter(
|
||||
(t) => t.status === PaperlessTaskStatus.Started
|
||||
).length
|
||||
component.activeTab = 'started'
|
||||
fixture.detectChanges()
|
||||
expect(tabButtons[2].nativeElement.textContent).toEqual(
|
||||
`Started${currentTasksLength}`
|
||||
)
|
||||
|
||||
currentTasksLength = tasks.filter(
|
||||
(t) => t.status === PaperlessTaskStatus.Pending
|
||||
).length
|
||||
component.activeTab = 'queued'
|
||||
fixture.detectChanges()
|
||||
expect(tabButtons[3].nativeElement.textContent).toEqual(
|
||||
`Queued${currentTasksLength}`
|
||||
)
|
||||
})
|
||||
|
||||
it('should to go page 1 between tab switch', () => {
|
||||
component.page = 10
|
||||
component.duringTabChange(2)
|
||||
expect(component.page).toEqual(1)
|
||||
})
|
||||
|
||||
it('should support expanding / collapsing one task at a time', () => {
|
||||
component.expandTask(tasks[0])
|
||||
expect(component.expandedTask).toEqual(tasks[0].id)
|
||||
component.expandTask(tasks[1])
|
||||
expect(component.expandedTask).toEqual(tasks[1].id)
|
||||
component.expandTask(tasks[1])
|
||||
expect(component.expandedTask).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should support dismiss single task', () => {
|
||||
const dismissSpy = jest.spyOn(tasksService, 'dismissTasks')
|
||||
component.dismissTask(tasks[0])
|
||||
expect(dismissSpy).toHaveBeenCalledWith(new Set([tasks[0].id]))
|
||||
})
|
||||
|
||||
it('should support dismiss specific checked tasks', () => {
|
||||
component.toggleSelected(tasks[0])
|
||||
component.toggleSelected(tasks[1])
|
||||
component.toggleSelected(tasks[3])
|
||||
component.toggleSelected(tasks[3]) // uncheck, for coverage
|
||||
const selected = new Set([tasks[0].id, tasks[1].id])
|
||||
expect(component.selectedTasks).toEqual(selected)
|
||||
let modal: NgbModalRef
|
||||
modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
|
||||
const dismissSpy = jest.spyOn(tasksService, 'dismissTasks')
|
||||
fixture.detectChanges()
|
||||
component.dismissTasks()
|
||||
expect(modal).not.toBeUndefined()
|
||||
modal.componentInstance.confirmClicked.emit()
|
||||
expect(dismissSpy).toHaveBeenCalledWith(selected)
|
||||
})
|
||||
|
||||
it('should support dismiss all tasks', () => {
|
||||
let modal: NgbModalRef
|
||||
modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
|
||||
const dismissSpy = jest.spyOn(tasksService, 'dismissTasks')
|
||||
component.dismissTasks()
|
||||
expect(modal).not.toBeUndefined()
|
||||
modal.componentInstance.confirmClicked.emit()
|
||||
expect(dismissSpy).toHaveBeenCalledWith(new Set(tasks.map((t) => t.id)))
|
||||
})
|
||||
|
||||
it('should support toggle all tasks', () => {
|
||||
const toggleCheck = fixture.debugElement.query(
|
||||
By.css('input[type=checkbox]')
|
||||
)
|
||||
toggleCheck.nativeElement.dispatchEvent(new MouseEvent('click'))
|
||||
fixture.detectChanges()
|
||||
expect(component.selectedTasks).toEqual(
|
||||
new Set(
|
||||
tasks
|
||||
.filter((t) => t.status === PaperlessTaskStatus.Failed)
|
||||
.map((t) => t.id)
|
||||
)
|
||||
)
|
||||
toggleCheck.nativeElement.dispatchEvent(new MouseEvent('click'))
|
||||
fixture.detectChanges()
|
||||
expect(component.selectedTasks).toEqual(new Set())
|
||||
})
|
||||
|
||||
it('should support dismiss and open a document', () => {
|
||||
const routerSpy = jest.spyOn(router, 'navigate')
|
||||
component.dismissAndGo(tasks[3])
|
||||
expect(routerSpy).toHaveBeenCalledWith([
|
||||
'documents',
|
||||
tasks[3].related_document,
|
||||
])
|
||||
})
|
||||
})
|
@ -1,6 +1,6 @@
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core'
|
||||
import { Router } from '@angular/router'
|
||||
import { NgbModal, NgbNavChangeEvent } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { Subject, first } from 'rxjs'
|
||||
import { PaperlessTask } from 'src/app/data/paperless-task'
|
||||
import { TasksService } from 'src/app/services/tasks.service'
|
||||
@ -51,8 +51,8 @@ export class TasksComponent
|
||||
}
|
||||
|
||||
dismissTasks(task: PaperlessTask = undefined) {
|
||||
let tasks = task ? new Set([task.id]) : this.selectedTasks
|
||||
if (!task && this.selectedTasks.size == 0)
|
||||
let tasks = task ? new Set([task.id]) : new Set(this.selectedTasks.values())
|
||||
if (!task && tasks.size == 0)
|
||||
tasks = new Set(this.tasksService.allFileTasks.map((t) => t.id))
|
||||
if (tasks.size > 1) {
|
||||
let modal = this.modalService.open(ConfirmDialogComponent, {
|
||||
@ -91,7 +91,7 @@ export class TasksComponent
|
||||
}
|
||||
|
||||
get currentTasks(): PaperlessTask[] {
|
||||
let tasks: PaperlessTask[]
|
||||
let tasks: PaperlessTask[] = []
|
||||
switch (this.activeTab) {
|
||||
case 'queued':
|
||||
tasks = this.tasksService.queuedFileTasks
|
||||
@ -105,8 +105,6 @@ export class TasksComponent
|
||||
case 'failed':
|
||||
tasks = this.tasksService.failedFileTasks
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
return tasks
|
||||
}
|
||||
|
@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { NotFoundComponent } from './not-found.component'
|
||||
|
||||
describe('NotFoundComponent', () => {
|
||||
let component: NotFoundComponent
|
||||
let fixture: ComponentFixture<NotFoundComponent>
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [NotFoundComponent],
|
||||
}).compileComponents()
|
||||
|
||||
fixture = TestBed.createComponent(NotFoundComponent)
|
||||
component = fixture.componentInstance
|
||||
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
it('should create component', () => {
|
||||
expect(component).toBeTruthy()
|
||||
expect(fixture.nativeElement.textContent).toContain('404 Not Found')
|
||||
})
|
||||
})
|
@ -0,0 +1,18 @@
|
||||
import { TestBed } from '@angular/core/testing'
|
||||
import { ComponentWithPermissions } from './with-permissions.component'
|
||||
|
||||
describe('ComponentWithPermissions', () => {
|
||||
let component: ComponentWithPermissions
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ComponentWithPermissions],
|
||||
})
|
||||
})
|
||||
|
||||
it('should include permissions classes', () => {
|
||||
component = new ComponentWithPermissions()
|
||||
expect(component.PermissionAction).not.toBeNull()
|
||||
expect(component.PermissionType).not.toBeNull()
|
||||
})
|
||||
})
|
@ -1,49 +1,3 @@
|
||||
import {
|
||||
FILTER_FULLTEXT_MORELIKE,
|
||||
FILTER_FULLTEXT_QUERY,
|
||||
} from './filter-rule-type'
|
||||
|
||||
export function cloneFilterRules(filterRules: FilterRule[]): FilterRule[] {
|
||||
if (filterRules) {
|
||||
let newRules: FilterRule[] = []
|
||||
for (let rule of filterRules) {
|
||||
newRules.push({ rule_type: rule.rule_type, value: rule.value })
|
||||
}
|
||||
return newRules
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export function isFullTextFilterRule(filterRules: FilterRule[]): boolean {
|
||||
return (
|
||||
filterRules.find(
|
||||
(r) =>
|
||||
r.rule_type == FILTER_FULLTEXT_QUERY ||
|
||||
r.rule_type == FILTER_FULLTEXT_MORELIKE
|
||||
) != null
|
||||
)
|
||||
}
|
||||
|
||||
export function filterRulesDiffer(
|
||||
filterRulesA: FilterRule[],
|
||||
filterRulesB: FilterRule[]
|
||||
): boolean {
|
||||
let differ = false
|
||||
if (filterRulesA.length != filterRulesB.length) {
|
||||
differ = true
|
||||
} else {
|
||||
differ = filterRulesA.some((rule) => {
|
||||
return (
|
||||
filterRulesB.find(
|
||||
(fri) => fri.rule_type == rule.rule_type && fri.value == rule.value
|
||||
) == undefined
|
||||
)
|
||||
})
|
||||
}
|
||||
return differ
|
||||
}
|
||||
|
||||
export interface FilterRule {
|
||||
rule_type: number
|
||||
value: string
|
||||
|
@ -0,0 +1,63 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { IfObjectPermissionsDirective } from './if-object-permissions.directive'
|
||||
import { PermissionsService } from '../services/permissions.service'
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<div>
|
||||
<button
|
||||
*appIfObjectPermissions="{
|
||||
object: { id: 2, owner: user1 },
|
||||
action: 'view'
|
||||
}"
|
||||
>
|
||||
Some Text
|
||||
</button>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
class TestComponent {}
|
||||
|
||||
describe('IfObjectPermissionsDirective', () => {
|
||||
let fixture: ComponentFixture<TestComponent>
|
||||
let permissionsService: PermissionsService
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [IfObjectPermissionsDirective, TestComponent],
|
||||
providers: [PermissionsService],
|
||||
})
|
||||
permissionsService = TestBed.inject(PermissionsService)
|
||||
})
|
||||
|
||||
it('should create element if user has object permissions', () => {
|
||||
jest
|
||||
.spyOn(permissionsService, 'currentUserHasObjectPermissions')
|
||||
.mockImplementation(() => {
|
||||
return true
|
||||
})
|
||||
|
||||
fixture = TestBed.createComponent(TestComponent)
|
||||
|
||||
fixture.detectChanges()
|
||||
|
||||
const rootEl = (fixture.nativeElement as HTMLDivElement).children[0]
|
||||
expect(rootEl.querySelectorAll('button').length).toEqual(1)
|
||||
})
|
||||
|
||||
it('should not create element if user does not have object permissions', () => {
|
||||
jest
|
||||
.spyOn(permissionsService, 'currentUserHasObjectPermissions')
|
||||
.mockImplementation(() => {
|
||||
return false
|
||||
})
|
||||
|
||||
fixture = TestBed.createComponent(TestComponent)
|
||||
|
||||
fixture.detectChanges()
|
||||
|
||||
const rootEl = (fixture.nativeElement as HTMLDivElement).children[0]
|
||||
expect(rootEl.querySelectorAll('button').length).toEqual(0)
|
||||
})
|
||||
})
|
56
src-ui/src/app/directives/if-owner.directive.spec.ts
Normal file
56
src-ui/src/app/directives/if-owner.directive.spec.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { IfOwnerDirective } from './if-owner.directive'
|
||||
import { PermissionsService } from '../services/permissions.service'
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<div>
|
||||
<button *appIfOwner="{ id: 2, owner: user1 }">Some Text</button>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
class TestComponent {}
|
||||
|
||||
describe('IfOwnerDirective', () => {
|
||||
let fixture: ComponentFixture<TestComponent>
|
||||
let permissionsService: PermissionsService
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [IfOwnerDirective, TestComponent],
|
||||
providers: [PermissionsService],
|
||||
})
|
||||
permissionsService = TestBed.inject(PermissionsService)
|
||||
})
|
||||
|
||||
it('should create element if user owns object', () => {
|
||||
jest
|
||||
.spyOn(permissionsService, 'currentUserOwnsObject')
|
||||
.mockImplementation(() => {
|
||||
return true
|
||||
})
|
||||
|
||||
fixture = TestBed.createComponent(TestComponent)
|
||||
|
||||
fixture.detectChanges()
|
||||
|
||||
const rootEl = (fixture.nativeElement as HTMLDivElement).children[0]
|
||||
expect(rootEl.querySelectorAll('button').length).toEqual(1)
|
||||
})
|
||||
|
||||
it('should not create element if user does not own object', () => {
|
||||
jest
|
||||
.spyOn(permissionsService, 'currentUserOwnsObject')
|
||||
.mockImplementation(() => {
|
||||
return false
|
||||
})
|
||||
|
||||
fixture = TestBed.createComponent(TestComponent)
|
||||
|
||||
fixture.detectChanges()
|
||||
|
||||
const rootEl = (fixture.nativeElement as HTMLDivElement).children[0]
|
||||
expect(rootEl.querySelectorAll('button').length).toEqual(0)
|
||||
})
|
||||
})
|
54
src-ui/src/app/directives/if-permissions.directive.spec.ts
Normal file
54
src-ui/src/app/directives/if-permissions.directive.spec.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { IfPermissionsDirective } from './if-permissions.directive'
|
||||
import { PermissionsService } from '../services/permissions.service'
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<div>
|
||||
<button *appIfPermissions="{ action: 'add', type: '%s_user' }">
|
||||
Some Text
|
||||
</button>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
class TestComponent {}
|
||||
|
||||
describe('IfPermissionsDirective', () => {
|
||||
let fixture: ComponentFixture<TestComponent>
|
||||
let permissionsService: PermissionsService
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [IfPermissionsDirective, TestComponent],
|
||||
providers: [PermissionsService],
|
||||
})
|
||||
permissionsService = TestBed.inject(PermissionsService)
|
||||
})
|
||||
|
||||
it('should create element if user has permissions', () => {
|
||||
jest.spyOn(permissionsService, 'currentUserCan').mockImplementation(() => {
|
||||
return true
|
||||
})
|
||||
|
||||
fixture = TestBed.createComponent(TestComponent)
|
||||
|
||||
fixture.detectChanges()
|
||||
|
||||
const rootEl = (fixture.nativeElement as HTMLDivElement).children[0]
|
||||
expect(rootEl.querySelectorAll('button').length).toEqual(1)
|
||||
})
|
||||
|
||||
it('should not create element if user has does not have permissions', () => {
|
||||
jest.spyOn(permissionsService, 'currentUserCan').mockImplementation(() => {
|
||||
return false
|
||||
})
|
||||
|
||||
fixture = TestBed.createComponent(TestComponent)
|
||||
|
||||
fixture.detectChanges()
|
||||
|
||||
const rootEl = (fixture.nativeElement as HTMLDivElement).children[0]
|
||||
expect(rootEl.querySelectorAll('button').length).toEqual(0)
|
||||
})
|
||||
})
|
93
src-ui/src/app/directives/sortable.directive.spec.ts
Normal file
93
src-ui/src/app/directives/sortable.directive.spec.ts
Normal file
@ -0,0 +1,93 @@
|
||||
import { Component, DebugElement } from '@angular/core'
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { SortEvent, SortableDirective } from './sortable.directive'
|
||||
import { By } from '@angular/platform-browser'
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<table class="table">
|
||||
<thead>
|
||||
<th></th>
|
||||
<th class="d-none d-lg-table-cell" appSortable="archive_serial_number">
|
||||
ASN
|
||||
</th>
|
||||
<th class="d-none d-md-table-cell" appSortable="correspondent__name">
|
||||
Correspondent
|
||||
</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
`,
|
||||
})
|
||||
class TestComponent {}
|
||||
|
||||
describe('SortableDirective', () => {
|
||||
let fixture: ComponentFixture<TestComponent>
|
||||
let directive: SortableDirective
|
||||
let des: DebugElement[] // the elements w/ the directive
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.configureTestingModule({
|
||||
declarations: [SortableDirective, TestComponent],
|
||||
}).createComponent(TestComponent)
|
||||
|
||||
fixture.detectChanges() // initial binding
|
||||
|
||||
// all elements with an attached SortableDirective
|
||||
des = fixture.debugElement.queryAll(By.directive(SortableDirective))
|
||||
|
||||
directive = des[1].injector.get(SortableDirective)
|
||||
directive.currentSortField = 'correspondent__name'
|
||||
})
|
||||
|
||||
it('should have three 2 sortable elements', () => {
|
||||
expect(des.length).toBe(2)
|
||||
})
|
||||
|
||||
it('should trigger sort on click', () => {
|
||||
const tableCell = des[1].nativeElement as HTMLTableCellElement
|
||||
|
||||
let sortEvent: SortEvent
|
||||
directive.sort.subscribe((event) => {
|
||||
directive.currentSortReverse = event.reverse
|
||||
sortEvent = event
|
||||
})
|
||||
|
||||
expect(directive.currentSortReverse).toBeFalsy()
|
||||
|
||||
tableCell.dispatchEvent(new MouseEvent('click'))
|
||||
fixture.detectChanges()
|
||||
|
||||
expect(sortEvent).not.toBeNull()
|
||||
expect(sortEvent.column).toEqual('correspondent__name')
|
||||
expect(sortEvent.reverse).toBeTruthy()
|
||||
|
||||
tableCell.dispatchEvent(new MouseEvent('click'))
|
||||
fixture.detectChanges()
|
||||
|
||||
expect(sortEvent.reverse).toBeFalsy()
|
||||
})
|
||||
|
||||
it('should change column to sort when clicked', () => {
|
||||
const tableCell = des[1].nativeElement as HTMLTableCellElement
|
||||
|
||||
let sortEvent: SortEvent
|
||||
directive.sort.subscribe((event) => {
|
||||
directive.currentSortReverse = event.reverse
|
||||
sortEvent = event
|
||||
})
|
||||
|
||||
directive.currentSortField = 'archive_serial_number'
|
||||
|
||||
tableCell.dispatchEvent(new MouseEvent('click'))
|
||||
fixture.detectChanges()
|
||||
|
||||
expect(sortEvent.column).toEqual('correspondent__name')
|
||||
})
|
||||
})
|
48
src-ui/src/app/guards/dirty-doc.guard.spec.ts
Normal file
48
src-ui/src/app/guards/dirty-doc.guard.spec.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { TestBed } from '@angular/core/testing'
|
||||
import { NgbModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { RouterTestingModule } from '@angular/router/testing'
|
||||
import { routes } from '../app-routing.module'
|
||||
import { Component } from '@angular/core'
|
||||
import { ComponentCanDeactivate, DirtyDocGuard } from './dirty-doc.guard'
|
||||
|
||||
@Component({})
|
||||
class GenericDirtyDocComponent implements ComponentCanDeactivate {
|
||||
canDeactivate: () => boolean
|
||||
}
|
||||
|
||||
describe('DirtyDocGuard', () => {
|
||||
let guard: DirtyDocGuard
|
||||
let component: ComponentCanDeactivate
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [DirtyDocGuard, NgbModal, GenericDirtyDocComponent],
|
||||
imports: [RouterTestingModule.withRoutes(routes), NgbModule],
|
||||
declarations: [GenericDirtyDocComponent],
|
||||
}).compileComponents()
|
||||
|
||||
guard = TestBed.inject(DirtyDocGuard)
|
||||
const fixture = TestBed.createComponent(GenericDirtyDocComponent)
|
||||
component = fixture.componentInstance
|
||||
window.confirm = jest.fn().mockImplementation(() => true)
|
||||
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
it('should deactivate if component is not dirty', () => {
|
||||
component.canDeactivate = () => true
|
||||
const confirmSpy = jest.spyOn(window, 'confirm')
|
||||
const canDeactivate = guard.canDeactivate(component)
|
||||
|
||||
expect(canDeactivate).toBeTruthy()
|
||||
expect(confirmSpy).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not deactivate if component is dirty', () => {
|
||||
component.canDeactivate = () => false
|
||||
const confirmSpy = jest.spyOn(window, 'confirm')
|
||||
const canDeactivate = guard.canDeactivate(component)
|
||||
|
||||
expect(confirmSpy).toHaveBeenCalled()
|
||||
})
|
||||
})
|
65
src-ui/src/app/guards/dirty-form.guard.spec.ts
Normal file
65
src-ui/src/app/guards/dirty-form.guard.spec.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import { TestBed } from '@angular/core/testing'
|
||||
import { NgbModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { RouterTestingModule } from '@angular/router/testing'
|
||||
import { routes } from '../app-routing.module'
|
||||
import { ConfirmDialogComponent } from '../components/common/confirm-dialog/confirm-dialog.component'
|
||||
import { DirtyFormGuard } from './dirty-form.guard'
|
||||
import { DirtyComponent } from '@ngneat/dirty-check-forms'
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
import { Component } from '@angular/core'
|
||||
|
||||
@Component({})
|
||||
class GenericDirtyComponent implements DirtyComponent {
|
||||
isDirty$: boolean
|
||||
}
|
||||
|
||||
describe('DirtyFormGuard', () => {
|
||||
let guard: DirtyFormGuard
|
||||
let component: DirtyComponent
|
||||
let route: ActivatedRoute
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
DirtyFormGuard,
|
||||
NgbModal,
|
||||
{
|
||||
provide: ActivatedRoute,
|
||||
useValue: {
|
||||
snapshot: {},
|
||||
},
|
||||
},
|
||||
GenericDirtyComponent,
|
||||
],
|
||||
imports: [RouterTestingModule.withRoutes(routes), NgbModule],
|
||||
declarations: [ConfirmDialogComponent, GenericDirtyComponent],
|
||||
}).compileComponents()
|
||||
|
||||
guard = TestBed.inject(DirtyFormGuard)
|
||||
route = TestBed.inject(ActivatedRoute)
|
||||
const fixture = TestBed.createComponent(GenericDirtyComponent)
|
||||
component = fixture.componentInstance
|
||||
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
it('should deactivate if component is not dirty', () => {
|
||||
component.isDirty$ = false
|
||||
const confirmSpy = jest.spyOn(guard, 'confirmChanges')
|
||||
const canDeactivate = guard.canDeactivate(component, route.snapshot)
|
||||
canDeactivate.subscribe()
|
||||
|
||||
expect(canDeactivate).toBeTruthy()
|
||||
expect(confirmSpy).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should offer confirm before deactivate if component is dirty', () => {
|
||||
component.isDirty$ = true
|
||||
const confirmSpy = jest.spyOn(guard, 'confirmChanges')
|
||||
const canDeactivate = guard.canDeactivate(component, route.snapshot)
|
||||
canDeactivate.subscribe()
|
||||
|
||||
expect(canDeactivate).toHaveProperty('source') // Observable
|
||||
expect(confirmSpy).toHaveBeenCalled()
|
||||
})
|
||||
})
|
131
src-ui/src/app/guards/dirty-saved-view.guard.spec.ts
Normal file
131
src-ui/src/app/guards/dirty-saved-view.guard.spec.ts
Normal file
@ -0,0 +1,131 @@
|
||||
import { TestBed } from '@angular/core/testing'
|
||||
import { DirtySavedViewGuard } from './dirty-saved-view.guard'
|
||||
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { SettingsService } from '../services/settings.service'
|
||||
import { DocumentListComponent } from '../components/document-list/document-list.component'
|
||||
import { RouterTestingModule } from '@angular/router/testing'
|
||||
import { routes } from '../app-routing.module'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { ConfirmDialogComponent } from '../components/common/confirm-dialog/confirm-dialog.component'
|
||||
|
||||
describe('DirtySavedViewGuard', () => {
|
||||
let guard: DirtySavedViewGuard
|
||||
let settingsService: SettingsService
|
||||
let modalService: NgbModal
|
||||
let component: DocumentListComponent
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
DirtySavedViewGuard,
|
||||
SettingsService,
|
||||
NgbModal,
|
||||
DocumentListComponent,
|
||||
],
|
||||
imports: [
|
||||
RouterTestingModule.withRoutes(routes),
|
||||
HttpClientTestingModule,
|
||||
],
|
||||
declarations: [ConfirmDialogComponent],
|
||||
})
|
||||
|
||||
settingsService = TestBed.inject(SettingsService)
|
||||
modalService = TestBed.inject(NgbModal)
|
||||
guard = TestBed.inject(DirtySavedViewGuard)
|
||||
const fixture = TestBed.createComponent(DocumentListComponent)
|
||||
component = fixture.componentInstance
|
||||
})
|
||||
|
||||
it('should deactivate if component is not dirty', () => {
|
||||
jest
|
||||
.spyOn(DocumentListComponent.prototype, 'savedViewIsModified', 'get')
|
||||
.mockImplementation(() => {
|
||||
return false
|
||||
})
|
||||
const canDeactivate = guard.canDeactivate(component)
|
||||
|
||||
expect(canDeactivate).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should not warn on deactivate if component is dirty & setting disabled', () => {
|
||||
jest
|
||||
.spyOn(DocumentListComponent.prototype, 'savedViewIsModified', 'get')
|
||||
.mockImplementation(() => {
|
||||
return true
|
||||
})
|
||||
|
||||
jest.spyOn(settingsService, 'get').mockImplementation(() => {
|
||||
return false
|
||||
})
|
||||
|
||||
const modalSpy = jest.spyOn(modalService, 'open')
|
||||
|
||||
const canDeactivate = guard.canDeactivate(component)
|
||||
|
||||
expect(canDeactivate).toBeTruthy()
|
||||
expect(modalSpy).not.toHaveBeenCalled()
|
||||
|
||||
const saveSpy = jest.spyOn(component, 'saveViewConfig')
|
||||
expect(saveSpy).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should warn on deactivate if component is dirty & setting enabled', () => {
|
||||
jest
|
||||
.spyOn(DocumentListComponent.prototype, 'savedViewIsModified', 'get')
|
||||
.mockImplementation(() => {
|
||||
return true
|
||||
})
|
||||
|
||||
jest.spyOn(settingsService, 'get').mockImplementation(() => {
|
||||
return true
|
||||
})
|
||||
|
||||
const modalSpy = jest.spyOn(modalService, 'open')
|
||||
|
||||
let modal: NgbModalRef
|
||||
|
||||
modalService.activeInstances.subscribe((ngbmodalRef) => {
|
||||
modal = ngbmodalRef[0]
|
||||
})
|
||||
|
||||
const canDeactivate = guard.canDeactivate(component)
|
||||
|
||||
expect(canDeactivate).toHaveProperty('closed') // returns confirm dialog subject
|
||||
expect(modalSpy).toHaveBeenCalled()
|
||||
expect(modal).not.toBeNull()
|
||||
|
||||
const saveSpy = jest.spyOn(component, 'saveViewConfig')
|
||||
modal.componentInstance.alternativeClicked.emit()
|
||||
expect(saveSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not save if user proceeds on warn', () => {
|
||||
jest
|
||||
.spyOn(DocumentListComponent.prototype, 'savedViewIsModified', 'get')
|
||||
.mockImplementation(() => {
|
||||
return true
|
||||
})
|
||||
|
||||
jest.spyOn(settingsService, 'get').mockImplementation(() => {
|
||||
return true
|
||||
})
|
||||
|
||||
const modalSpy = jest.spyOn(modalService, 'open')
|
||||
|
||||
let modal: NgbModalRef
|
||||
|
||||
modalService.activeInstances.subscribe((ngbmodalRef) => {
|
||||
modal = ngbmodalRef[0]
|
||||
})
|
||||
|
||||
const canDeactivate = guard.canDeactivate(component)
|
||||
|
||||
expect(canDeactivate).toHaveProperty('closed') // returns confirm dialog subject
|
||||
expect(modalSpy).toHaveBeenCalled()
|
||||
expect(modal).not.toBeNull()
|
||||
|
||||
const saveSpy = jest.spyOn(component, 'saveViewConfig')
|
||||
modal.componentInstance.confirmClicked.emit()
|
||||
expect(saveSpy).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
100
src-ui/src/app/guards/permissions.guard.spec.ts
Normal file
100
src-ui/src/app/guards/permissions.guard.spec.ts
Normal file
@ -0,0 +1,100 @@
|
||||
import { TestBed } from '@angular/core/testing'
|
||||
import { PermissionsGuard } from './permissions.guard'
|
||||
import {
|
||||
PermissionAction,
|
||||
PermissionType,
|
||||
PermissionsService,
|
||||
} from '../services/permissions.service'
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
import { RouterStateSnapshot } from '@angular/router'
|
||||
import { TourService } from 'ngx-ui-tour-ng-bootstrap'
|
||||
import { ToastService } from '../services/toast.service'
|
||||
import { RouterState } from '@angular/router'
|
||||
|
||||
describe('PermissionsGuard', () => {
|
||||
let guard: PermissionsGuard
|
||||
let permissionsService: PermissionsService
|
||||
let route: ActivatedRoute
|
||||
let routerState: RouterState
|
||||
let tourService: TourService
|
||||
let toastService: ToastService
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
PermissionsGuard,
|
||||
PermissionsService,
|
||||
{
|
||||
provide: ActivatedRoute,
|
||||
useValue: {
|
||||
snapshot: {
|
||||
data: {
|
||||
requiredPermission: {
|
||||
action: PermissionAction.View,
|
||||
type: PermissionType.Document,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: RouterState,
|
||||
useValue: {
|
||||
snapshot: {
|
||||
url: '/documents',
|
||||
},
|
||||
},
|
||||
},
|
||||
TourService,
|
||||
ToastService,
|
||||
],
|
||||
})
|
||||
|
||||
permissionsService = TestBed.inject(PermissionsService)
|
||||
tourService = TestBed.inject(TourService)
|
||||
toastService = TestBed.inject(ToastService)
|
||||
guard = TestBed.inject(PermissionsGuard)
|
||||
route = TestBed.inject(ActivatedRoute)
|
||||
routerState = TestBed.inject(RouterState)
|
||||
})
|
||||
|
||||
it('should activate if user has permissions', () => {
|
||||
jest
|
||||
.spyOn(permissionsService, 'currentUserCan')
|
||||
.mockImplementation((action, type) => {
|
||||
return true
|
||||
})
|
||||
|
||||
const canActivate = guard.canActivate(route.snapshot, routerState.snapshot)
|
||||
|
||||
expect(canActivate).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should not activate if user does not have permissions', () => {
|
||||
jest
|
||||
.spyOn(permissionsService, 'currentUserCan')
|
||||
.mockImplementation((action, type) => {
|
||||
return false
|
||||
})
|
||||
|
||||
const canActivate = guard.canActivate(route.snapshot, routerState.snapshot)
|
||||
|
||||
expect(canActivate).toHaveProperty('root') // returns UrlTree
|
||||
})
|
||||
|
||||
it('should not activate if user does not have permissions and tour is running', () => {
|
||||
jest
|
||||
.spyOn(permissionsService, 'currentUserCan')
|
||||
.mockImplementation((action, type) => {
|
||||
return false
|
||||
})
|
||||
jest.spyOn(tourService, 'getStatus').mockImplementation(() => 2)
|
||||
|
||||
const toastSpy = jest.spyOn(toastService, 'showError')
|
||||
|
||||
const canActivate = guard.canActivate(route.snapshot, routerState.snapshot)
|
||||
|
||||
expect(canActivate).toHaveProperty('root') // returns UrlTree
|
||||
expect(toastSpy).toHaveBeenCalled()
|
||||
})
|
||||
})
|
30
src-ui/src/app/interceptors/api-version.interceptor.spec.ts
Normal file
30
src-ui/src/app/interceptors/api-version.interceptor.spec.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { TestBed } from '@angular/core/testing'
|
||||
import { ApiVersionInterceptor } from './api-version.interceptor'
|
||||
import { HttpEvent, HttpRequest } from '@angular/common/http'
|
||||
import { of } from 'rxjs'
|
||||
import { environment } from 'src/environments/environment'
|
||||
|
||||
describe('ApiVersionInterceptor', () => {
|
||||
let interceptor: ApiVersionInterceptor
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [ApiVersionInterceptor],
|
||||
})
|
||||
|
||||
interceptor = TestBed.inject(ApiVersionInterceptor)
|
||||
})
|
||||
|
||||
it('should add api version to headers', () => {
|
||||
interceptor.intercept(new HttpRequest('GET', 'https://example.com'), {
|
||||
handle: (request) => {
|
||||
const header = request.headers['lazyUpdate'][0]
|
||||
expect(header.name).toEqual('Accept')
|
||||
expect(header.value).toEqual(
|
||||
`application/json; version=${environment.apiVersion}`
|
||||
)
|
||||
return of({} as HttpEvent<any>)
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
@ -1,16 +1,35 @@
|
||||
import { TestBed } from '@angular/core/testing'
|
||||
|
||||
import { CsrfInterceptor } from './csrf.interceptor'
|
||||
import { Meta } from '@angular/platform-browser'
|
||||
import { HttpEvent, HttpRequest } from '@angular/common/http'
|
||||
import { of } from 'rxjs'
|
||||
import { CookieService } from 'ngx-cookie-service'
|
||||
|
||||
describe('CsrfInterceptor', () => {
|
||||
beforeEach(() =>
|
||||
TestBed.configureTestingModule({
|
||||
providers: [CsrfInterceptor],
|
||||
})
|
||||
)
|
||||
let interceptor: CsrfInterceptor
|
||||
let meta: Meta
|
||||
let cookieService: CookieService
|
||||
|
||||
it('should be created', () => {
|
||||
const interceptor: CsrfInterceptor = TestBed.inject(CsrfInterceptor)
|
||||
expect(interceptor).toBeTruthy()
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [CsrfInterceptor, Meta, CookieService],
|
||||
})
|
||||
|
||||
meta = TestBed.inject(Meta)
|
||||
cookieService = TestBed.inject(CookieService)
|
||||
interceptor = TestBed.inject(CsrfInterceptor)
|
||||
})
|
||||
|
||||
it('should get csrf token', () => {
|
||||
meta.addTag({ name: 'cookie_prefix', content: 'ngx-' }, true)
|
||||
const cookieServiceSpy = jest.spyOn(cookieService, 'get')
|
||||
cookieServiceSpy.mockReturnValue('csrftoken')
|
||||
interceptor.intercept(new HttpRequest('GET', 'https://example.com'), {
|
||||
handle: (request) => {
|
||||
expect(request.headers['lazyUpdate'][0]['name']).toEqual('X-CSRFToken')
|
||||
return of({} as HttpEvent<any>)
|
||||
},
|
||||
})
|
||||
expect(cookieServiceSpy).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
@ -21,7 +21,7 @@ export class CsrfInterceptor implements HttpInterceptor {
|
||||
if (this.meta.getTag('name=cookie_prefix')) {
|
||||
prefix = this.meta.getTag('name=cookie_prefix').content
|
||||
}
|
||||
let csrfToken = this.cookieService.get(`${prefix ? prefix : ''}csrftoken`)
|
||||
let csrfToken = this.cookieService.get(`${prefix}csrftoken`)
|
||||
if (csrfToken) {
|
||||
request = request.clone({
|
||||
setHeaders: {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user