Files
paperless-ngx/src-ui/src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.spec.ts
2026-02-12 22:11:37 -08:00

227 lines
7.9 KiB
TypeScript

import { DatePipe } from '@angular/common'
import { SimpleChange } from '@angular/core'
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { Subject, of, throwError } from 'rxjs'
import { DocumentVersionInfo } from 'src/app/data/document'
import { DocumentService } from 'src/app/services/rest/document.service'
import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
import {
UploadState,
WebsocketStatusService,
} from 'src/app/services/websocket-status.service'
import { DocumentVersionDropdownComponent } from './document-version-dropdown.component'
describe('DocumentVersionDropdownComponent', () => {
let component: DocumentVersionDropdownComponent
let fixture: ComponentFixture<DocumentVersionDropdownComponent>
let documentService: jest.Mocked<
Pick<DocumentService, 'deleteVersion' | 'getVersions' | 'uploadVersion'>
>
let toastService: jest.Mocked<Pick<ToastService, 'showError' | 'showInfo'>>
let finished$: Subject<{ taskId: string }>
let failed$: Subject<{ taskId: string; message?: string }>
beforeEach(async () => {
finished$ = new Subject<{ taskId: string }>()
failed$ = new Subject<{ taskId: string; message?: string }>()
documentService = {
deleteVersion: jest.fn(),
getVersions: jest.fn(),
uploadVersion: jest.fn(),
}
toastService = {
showError: jest.fn(),
showInfo: jest.fn(),
}
await TestBed.configureTestingModule({
imports: [
DocumentVersionDropdownComponent,
NgxBootstrapIconsModule.pick(allIcons),
],
providers: [
DatePipe,
{
provide: DocumentService,
useValue: documentService,
},
{
provide: SettingsService,
useValue: {
get: () => null,
},
},
{
provide: ToastService,
useValue: toastService,
},
{
provide: WebsocketStatusService,
useValue: {
onDocumentConsumptionFinished: () => finished$,
onDocumentConsumptionFailed: () => failed$,
},
},
],
}).compileComponents()
fixture = TestBed.createComponent(DocumentVersionDropdownComponent)
component = fixture.componentInstance
component.documentId = 3
component.selectedVersionId = 3
component.versions = [
{
id: 3,
is_root: true,
checksum: 'aaaa',
},
{
id: 10,
is_root: false,
checksum: 'bbbb',
},
]
fixture.detectChanges()
})
it('selectVersion should emit the selected id', () => {
const emitSpy = jest.spyOn(component.versionSelected, 'emit')
component.selectVersion(10)
expect(emitSpy).toHaveBeenCalledWith(10)
})
it('deleteVersion should refresh versions and select fallback when deleting current selection', () => {
const updatedVersions: DocumentVersionInfo[] = [
{ id: 3, is_root: true, checksum: 'aaaa' },
{ id: 20, is_root: false, checksum: 'cccc' },
]
component.selectedVersionId = 10
documentService.deleteVersion.mockReturnValue(
of({ result: 'deleted', current_version_id: 3 })
)
documentService.getVersions.mockReturnValue(
of({ id: 3, versions: updatedVersions } as any)
)
const versionsEmitSpy = jest.spyOn(component.versionsUpdated, 'emit')
const selectedEmitSpy = jest.spyOn(component.versionSelected, 'emit')
component.deleteVersion(10)
expect(documentService.deleteVersion).toHaveBeenCalledWith(3, 10)
expect(documentService.getVersions).toHaveBeenCalledWith(3)
expect(versionsEmitSpy).toHaveBeenCalledWith(updatedVersions)
expect(selectedEmitSpy).toHaveBeenCalledWith(3)
})
it('deleteVersion should show an error toast on failure', () => {
const error = new Error('delete failed')
documentService.deleteVersion.mockReturnValue(throwError(() => error))
component.deleteVersion(10)
expect(toastService.showError).toHaveBeenCalledWith(
'Error deleting version',
error
)
})
it('onVersionFileSelected should upload and update versions after websocket success', () => {
const versions: DocumentVersionInfo[] = [
{ id: 3, is_root: true, checksum: 'aaaa' },
{ id: 20, is_root: false, checksum: 'cccc' },
]
const file = new File(['test'], 'new-version.pdf', {
type: 'application/pdf',
})
const input = document.createElement('input')
Object.defineProperty(input, 'files', { value: [file] })
component.newVersionLabel = ' Updated scan '
documentService.uploadVersion.mockReturnValue(
of({ task_id: 'task-1' } as any)
)
documentService.getVersions.mockReturnValue(of({ id: 3, versions } as any))
const versionsEmitSpy = jest.spyOn(component.versionsUpdated, 'emit')
const selectedEmitSpy = jest.spyOn(component.versionSelected, 'emit')
component.onVersionFileSelected({ target: input } as Event)
finished$.next({ taskId: 'task-1' })
expect(documentService.uploadVersion).toHaveBeenCalledWith(
3,
file,
'Updated scan'
)
expect(toastService.showInfo).toHaveBeenCalled()
expect(documentService.getVersions).toHaveBeenCalledWith(3)
expect(versionsEmitSpy).toHaveBeenCalledWith(versions)
expect(selectedEmitSpy).toHaveBeenCalledWith(20)
expect(component.newVersionLabel).toEqual('')
expect(component.versionUploadState).toEqual(UploadState.Idle)
expect(component.versionUploadError).toBeNull()
})
it('onVersionFileSelected should set failed state after websocket failure', () => {
const file = new File(['test'], 'new-version.pdf', {
type: 'application/pdf',
})
const input = document.createElement('input')
Object.defineProperty(input, 'files', { value: [file] })
documentService.uploadVersion.mockReturnValue(of('task-1'))
component.onVersionFileSelected({ target: input } as Event)
failed$.next({ taskId: 'task-1', message: 'processing failed' })
expect(component.versionUploadState).toEqual(UploadState.Failed)
expect(component.versionUploadError).toEqual('processing failed')
expect(documentService.getVersions).not.toHaveBeenCalled()
})
it('onVersionFileSelected should fail when backend response has no task id', () => {
const file = new File(['test'], 'new-version.pdf', {
type: 'application/pdf',
})
const input = document.createElement('input')
Object.defineProperty(input, 'files', { value: [file] })
documentService.uploadVersion.mockReturnValue(of({} as any))
component.onVersionFileSelected({ target: input } as Event)
expect(component.versionUploadState).toEqual(UploadState.Failed)
expect(component.versionUploadError).toEqual('Missing task ID.')
expect(documentService.getVersions).not.toHaveBeenCalled()
})
it('onVersionFileSelected should show error when upload request fails', () => {
const file = new File(['test'], 'new-version.pdf', {
type: 'application/pdf',
})
const input = document.createElement('input')
Object.defineProperty(input, 'files', { value: [file] })
const error = new Error('upload failed')
documentService.uploadVersion.mockReturnValue(throwError(() => error))
component.onVersionFileSelected({ target: input } as Event)
expect(component.versionUploadState).toEqual(UploadState.Failed)
expect(component.versionUploadError).toEqual('upload failed')
expect(toastService.showError).toHaveBeenCalledWith(
'Error uploading new version',
error
)
})
it('ngOnChanges should clear upload status on document switch', () => {
component.versionUploadState = UploadState.Failed
component.versionUploadError = 'something failed'
component.ngOnChanges({
documentId: new SimpleChange(3, 4, false),
})
expect(component.versionUploadState).toEqual(UploadState.Idle)
expect(component.versionUploadError).toBeNull()
})
})