diff --git a/src-ui/src/app/components/manage/ocr-templates/ocr-templates.component.spec.ts b/src-ui/src/app/components/manage/ocr-templates/ocr-templates.component.spec.ts index 6fb766fab..31be7a773 100644 --- a/src-ui/src/app/components/manage/ocr-templates/ocr-templates.component.spec.ts +++ b/src-ui/src/app/components/manage/ocr-templates/ocr-templates.component.spec.ts @@ -1,13 +1,14 @@ -import { EventEmitter } from '@angular/core' +import { EventEmitter, signal, WritableSignal } from '@angular/core' import { ComponentFixture, TestBed } from '@angular/core/testing' import { NgbModal, NgbModalOptions } from '@ng-bootstrap/ng-bootstrap' -import { of } from 'rxjs' +import { of, Subject } from 'rxjs' import { OcrTemplate } from 'src/app/data/ocr-template' import { DirtyFormGuard } from 'src/app/guards/dirty-form.guard' import { PermissionsService } from 'src/app/services/permissions.service' import { DocumentTypeService } from 'src/app/services/rest/document-type.service' import { OcrTemplateService } from 'src/app/services/rest/ocr-template.service' import { ToastService } from 'src/app/services/toast.service' +import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component' import { OcrTemplateEditorComponent } from './ocr-template-editor/ocr-template-editor.component' import { OcrTemplatesComponent } from './ocr-templates.component' @@ -23,6 +24,8 @@ describe('OcrTemplatesComponent', () => { } const modal = { open: jest.fn() } const dirtyFormGuard = { confirmChanges: jest.fn() } + const templates = { delete: jest.fn() } + const toast = { showError: jest.fn() } beforeEach(async () => { editor.object = undefined @@ -34,15 +37,17 @@ describe('OcrTemplatesComponent', () => { return { componentInstance: editor } }) dirtyFormGuard.confirmChanges.mockReset().mockReturnValue(of(true)) + templates.delete.mockReset() + toast.showError.mockReset() await TestBed.configureTestingModule({ imports: [OcrTemplatesComponent], providers: [ { provide: NgbModal, useValue: modal }, { provide: DirtyFormGuard, useValue: dirtyFormGuard }, { provide: PermissionsService, useValue: {} }, - { provide: OcrTemplateService, useValue: {} }, + { provide: OcrTemplateService, useValue: templates }, { provide: DocumentTypeService, useValue: {} }, - { provide: ToastService, useValue: {} }, + { provide: ToastService, useValue: toast }, ], }) .overrideComponent(OcrTemplatesComponent, { @@ -88,4 +93,71 @@ describe('OcrTemplatesComponent', () => { expect(options.beforeDismiss()).toBe(false) expect(dirtyFormGuard.confirmChanges).toHaveBeenCalledTimes(2) }) + + describe('deletion', () => { + const template = { id: 3, name: 'Invoices' } as OcrTemplate + let dialog: { + confirmClicked: EventEmitter + buttonsEnabled: WritableSignal + messageBold?: string + } + let close: jest.Mock + let reload: jest.SpyInstance + + beforeEach(() => { + dialog = { + confirmClicked: new EventEmitter(), + buttonsEnabled: signal(true), + } + close = jest.fn() + modal.open.mockReturnValue({ componentInstance: dialog, close }) + reload = jest.spyOn(component, 'reload').mockImplementation(() => {}) + }) + + it('keeps the confirmation open and disables buttons until deletion succeeds', () => { + const pending = new Subject() + templates.delete.mockReturnValue(pending) + component.deleteTemplate(template) + + expect(modal.open).toHaveBeenCalledWith(ConfirmDialogComponent, { + backdrop: 'static', + }) + expect(dialog.messageBold).toBe(template.name) + expect(templates.delete).not.toHaveBeenCalled() + dialog.confirmClicked.emit() + + expect(templates.delete).toHaveBeenCalledWith(template) + expect(dialog.buttonsEnabled()).toBe(false) + expect(close).not.toHaveBeenCalled() + expect(reload).not.toHaveBeenCalled() + + pending.next() + pending.complete() + + expect(close).toHaveBeenCalledTimes(1) + expect(reload).toHaveBeenCalledTimes(1) + }) + + it('shows errors and allows retrying without closing the confirmation', () => { + const pending = new Subject() + templates.delete + .mockReturnValueOnce(pending) + .mockReturnValue(of(undefined)) + component.deleteTemplate(template) + dialog.confirmClicked.emit() + const error = new Error('Delete failed') + pending.error(error) + + expect(toast.showError).toHaveBeenCalledWith(expect.any(String), error) + expect(dialog.buttonsEnabled()).toBe(true) + expect(close).not.toHaveBeenCalled() + expect(reload).not.toHaveBeenCalled() + + dialog.confirmClicked.emit() + + expect(templates.delete).toHaveBeenCalledTimes(2) + expect(close).toHaveBeenCalledTimes(1) + expect(reload).toHaveBeenCalledTimes(1) + }) + }) }) diff --git a/src-ui/src/app/components/manage/ocr-templates/ocr-templates.component.ts b/src-ui/src/app/components/manage/ocr-templates/ocr-templates.component.ts index bc9934e0b..d1589866b 100644 --- a/src-ui/src/app/components/manage/ocr-templates/ocr-templates.component.ts +++ b/src-ui/src/app/components/manage/ocr-templates/ocr-templates.component.ts @@ -117,15 +117,34 @@ export class OcrTemplatesComponent } deleteTemplate(t: OcrTemplate) { - const modal = this.modalService.open(ConfirmDialogComponent) + const modal = this.modalService.open(ConfirmDialogComponent, { + backdrop: 'static', + }) modal.componentInstance.title = $localize`Delete OCR Template` - modal.componentInstance.messageBoldPart = t.name + modal.componentInstance.messageBold = t.name modal.componentInstance.message = $localize`Do you really want to delete this OCR template?` modal.componentInstance.btnClass = 'btn-danger' modal.componentInstance.btnCaption = $localize`Delete` - modal.componentInstance.confirmClicked.subscribe(() => { - modal.close() - this.service.delete(t).subscribe(() => this.reload()) - }) + modal.componentInstance.confirmClicked + .pipe(takeUntil(this.unsubscribeNotifier)) + .subscribe(() => { + modal.componentInstance.buttonsEnabled.set(false) + this.service + .delete(t) + .pipe(takeUntil(this.unsubscribeNotifier)) + .subscribe({ + next: () => { + modal.close() + this.reload() + }, + error: (error) => { + modal.componentInstance.buttonsEnabled.set(true) + this.toastService.showError( + $localize`Error deleting OCR template.`, + error + ) + }, + }) + }) } }