From c3755d266ed24f3eec91d3482caad84c15957f5d Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Wed, 22 Apr 2026 14:52:02 +0200 Subject: [PATCH 01/11] [NAE-2416] AbstractFileDefaultFieldComponent does not push upload event notification to TaskEventService - Integrated `FrontActionService` in `AbstractFileDefaultFieldComponent` to handle front-end actions based on outcomes. - Enhanced file upload logic with better error handling and front action triggers, such as displaying success feedback via snackbar actions. - Updated tests for `AbstractFileListDefaultFieldComponent` to include `FrontActionService`. - Registered a new `snackBar` front action in the `FrontActionModule`. - Refactored the file upload methods to utilize modern RXJS subscription patterns with error handling improvements. - Improved readability and maintainability of file download and preview logic by addressing structuring and formatting issues. - Removed unused imports and simplified test components. These changes aim to improve the user experience by handling outcome-based front actions and offering better feedback on file operations. --- .../src/lib/actions/front-action.module.ts | 3 +- ...tract-file-default-field.component.spec.ts | 8 +- .../abstract-file-default-field.component.ts | 105 ++++++++++-------- ...-file-list-default-field.component.spec.ts | 8 +- ...tract-file-list-default-field.component.ts | 100 +++++++++-------- .../file-default-field.component.ts | 5 +- .../file-list-default-field.component.ts | 5 +- 7 files changed, 128 insertions(+), 106 deletions(-) diff --git a/projects/netgrif-components-core/src/lib/actions/front-action.module.ts b/projects/netgrif-components-core/src/lib/actions/front-action.module.ts index 926acea17..c45918912 100644 --- a/projects/netgrif-components-core/src/lib/actions/front-action.module.ts +++ b/projects/netgrif-components-core/src/lib/actions/front-action.module.ts @@ -1,7 +1,7 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import {FrontActionRegistryService} from "../registry/front-action-registry.service"; -import {redirectAction} from "./model/router-action-definitions"; +import {redirectAction, snackBarAction} from "./model/router-action-definitions"; import {reloadTaskAction, validateTaskAction} from "./model/task-action-definitions"; @NgModule({ @@ -16,5 +16,6 @@ export class FrontActionModule { frontActionsRegistry.register('redirect', redirectAction); frontActionsRegistry.register('validate', validateTaskAction); frontActionsRegistry.register('reloadTask', reloadTaskAction); + frontActionsRegistry.register('snackBar', snackBarAction); } } diff --git a/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.spec.ts b/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.spec.ts index e66f177b5..ee15de46e 100644 --- a/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.spec.ts +++ b/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.spec.ts @@ -16,9 +16,6 @@ import {MockUserResourceService} from "../../../utility/tests/mocks/mock-user-re import {ConfigurationService} from "../../../configuration/configuration.service"; import {TestConfigurationService} from "../../../utility/tests/test-config"; import {Component, CUSTOM_ELEMENTS_SCHEMA, Inject, Optional} from "@angular/core"; -import {BrowserDynamicTestingModule} from "@angular/platform-browser-dynamic/testing"; -import {ErrorSnackBarComponent} from "../../../snack-bar/components/error-snack-bar/error-snack-bar.component"; -import {SuccessSnackBarComponent} from "../../../snack-bar/components/success-snack-bar/success-snack-bar.component"; import {TaskResourceService} from "../../../resources/engine-endpoint/task-resource.service"; import {LoggerService} from "../../../logger/services/logger.service"; import {SnackBarService} from "../../../snack-bar/services/snack-bar.service"; @@ -29,6 +26,7 @@ import {AbstractFileDefaultFieldComponent} from "./abstract-file-default-field.c import {DATA_FIELD_PORTAL_DATA, DataFieldPortalData} from "../../models/data-field-portal-data-injection-token"; import {FormControl} from "@angular/forms"; import {WrappedBoolean} from "../../data-field-template/models/wrapped-boolean"; +import {FrontActionService} from "../../../actions/services/front-action.service"; describe('AbstractFileDefaultFieldComponent', () => { let component: TestFileComponent; @@ -48,6 +46,7 @@ describe('AbstractFileDefaultFieldComponent', () => { providers: [ SideMenuService, EventService, + FrontActionService, {provide: AuthenticationMethodService, useClass: MockAuthenticationMethodService}, {provide: AuthenticationService, useClass: MockAuthenticationService}, {provide: UserResourceService, useClass: MockUserResourceService}, @@ -97,8 +96,9 @@ class TestFileComponent extends AbstractFileDefaultFieldComponent { translate: TranslateService, sanitizer: DomSanitizer, eventService: EventService, + frontActionService: FrontActionService, @Optional() @Inject(DATA_FIELD_PORTAL_DATA) dataFieldPortalData: DataFieldPortalData) { - super(taskResourceService, log, snackbar, translate, eventService, sanitizer, dataFieldPortalData); + super(taskResourceService, log, snackbar, translate, eventService, sanitizer, frontActionService, dataFieldPortalData); } } diff --git a/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.ts b/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.ts index 690a84ab7..75342fcba 100644 --- a/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.ts +++ b/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.ts @@ -26,6 +26,8 @@ import {DATA_FIELD_PORTAL_DATA, DataFieldPortalData} from "../../models/data-fie import {FILE_FIELD_HEIGHT, FILE_FIELD_PADDING, PREVIEW, PREVIEW_BUTTON} from '../models/file-field-constants'; import {FileFieldRequest} from "../../../resources/interface/file-field-request-body"; import {AbstractFileFieldDefaultComponent} from '../../models/abstract-file-field-default-component'; +import {FrontAction} from "../../models/changed-fields"; +import {FrontActionService} from "../../../actions/services/front-action.service"; export interface FileState { progress: number; @@ -114,6 +116,7 @@ export abstract class AbstractFileDefaultFieldComponent extends AbstractFileFiel protected _translate: TranslateService, protected _eventService: EventService, protected _sanitizer: DomSanitizer, + protected _frontActionService: FrontActionService, @Optional() @Inject(DATA_FIELD_PORTAL_DATA) dataFieldPortalData: DataFieldPortalData) { super(_log, _snackbar, _translate, dataFieldPortalData); this.state = this.defaultState; @@ -217,60 +220,68 @@ export abstract class AbstractFileDefaultFieldComponent extends AbstractFileFiel fileFormData.append('file', fileToUpload); fileFormData.append('data', new Blob([JSON.stringify(this.createRequestBody())], {type: 'application/json'})); this._taskResourceService.uploadFile(this.taskId, fileFormData, false) - .subscribe((response: EventOutcomeMessageResource) => { - if ((response as ProviderProgress).type && (response as ProviderProgress).type === ProgressType.UPLOAD) { - this.state.progress = (response as ProviderProgress).progress; - } else { - this.state.completed = true; - this.state.uploading = false; - this.state.progress = 0; + .subscribe({ + next: (response: EventOutcomeMessageResource) => { + if ((response as ProviderProgress).type && (response as ProviderProgress).type === ProgressType.UPLOAD) { + this.state.progress = (response as ProviderProgress).progress; + } else { + this.state.completed = true; + this.state.uploading = false; + this.state.progress = 0; - if (response.error) { - this.state.error = true; - this._log.error( - `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0)} uploading has failed!`, response.error - ); if (response.error) { - this._snackbar.openErrorSnackBar(this._translate.instant(response.error)); + this.state.error = true; + this._log.error( + `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0)} uploading has failed!`, response.error + ); + if (response.error) { + this._snackbar.openErrorSnackBar(this._translate.instant(response.error)); + } else { + this._snackbar.openErrorSnackBar(this._translate.instant('dataField.snackBar.fileUploadFailed')); + } } else { - this._snackbar.openErrorSnackBar(this._translate.instant('dataField.snackBar.fileUploadFailed')); + const changedFieldsMap: ChangedFieldsMap = this._eventService.parseChangedFieldsFromOutcomeTree(response.outcome); + this.dataField.emitChangedFields(changedFieldsMap); + this._log.debug( + `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0).name} was successfully uploaded` + ); + this.state.error = false; + this.dataField.downloaded = false; + this.dataField.value.name = fileToUpload.name; + if (this.isFilePreview) { + this.initializePreviewIfDisplayable(); + } + this.fullSource.next(undefined); + this.fileForDownload = undefined; + this.formControlRef.setValue(this.dataField.value.name); + this._snackbar.openSuccessSnackBar(!!response.outcome.message ? response.outcome.message : this._translate.instant('tasks.snackbar.dataSaved')); + const frontActions: Array = this._eventService.parseFrontActionsFromOutcomeTree(response.outcome); + if (frontActions?.length > 0) { + this._frontActionService.runAll(frontActions); + } } + this.dataField.touch = true; + this.dataField.update(); + this.fileUploadEl.nativeElement.value = ''; + } + }, + error: (error) => { + this.state.completed = true; + this.state.error = true; + this.state.uploading = false; + this.state.progress = 0; + this._log.error( + `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0)} uploading has failed!`, error + ); + if (error?.error?.message) { + this._snackbar.openErrorSnackBar(this._translate.instant(error.error.message)); } else { - const changedFieldsMap: ChangedFieldsMap = this._eventService.parseChangedFieldsFromOutcomeTree(response.outcome); - this.dataField.emitChangedFields(changedFieldsMap); - this._log.debug( - `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0).name} was successfully uploaded` - ); - this.state.error = false; - this.dataField.downloaded = false; - this.dataField.value.name = fileToUpload.name; - if (this.isFilePreview) { - this.initializePreviewIfDisplayable(); - } - this.fullSource.next(undefined); - this.fileForDownload = undefined; - this.formControlRef.setValue(this.dataField.value.name); + this._snackbar.openErrorSnackBar(this._translate.instant('dataField.snackBar.fileUploadFailed')); } this.dataField.touch = true; this.dataField.update(); this.fileUploadEl.nativeElement.value = ''; } - }, error => { - this.state.completed = true; - this.state.error = true; - this.state.uploading = false; - this.state.progress = 0; - this._log.error( - `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0)} uploading has failed!`, error - ); - if (error?.error?.message) { - this._snackbar.openErrorSnackBar(this._translate.instant(error.error.message)); - } else { - this._snackbar.openErrorSnackBar(this._translate.instant('dataField.snackBar.fileUploadFailed')); - } - this.dataField.touch = true; - this.dataField.update(); - this.fileUploadEl.nativeElement.value = ''; }); } @@ -410,7 +421,8 @@ export abstract class AbstractFileDefaultFieldComponent extends AbstractFileFiel this.state.downloading = true; let params = new HttpParams() params = params.set("fieldId", this.dataField.stringId); - this._taskResourceService.downloadFilePreview(this.resolveParentTaskId(), params).subscribe(response => { if (response instanceof Blob) { + this._taskResourceService.downloadFilePreview(this.resolveParentTaskId(), params).subscribe(response => { + if (response instanceof Blob) { this._log.debug(`Preview of file [${this.dataField.stringId}] ${this.dataField.value.name} was successfully downloaded`); this.fileForPreview = new Blob([response], {type: 'application/octet-stream'}); this.previewSource = this._sanitizer.bypassSecurityTrustUrl(URL.createObjectURL(this.fileForPreview)); @@ -445,7 +457,8 @@ export abstract class AbstractFileDefaultFieldComponent extends AbstractFileFiel } let params = new HttpParams(); params = params.set("fieldId", this.dataField.stringId); - this._taskResourceService.downloadFile(this.resolveParentTaskId(), params).subscribe(response => { if (!(response as ProviderProgress).type || (response as ProviderProgress).type !== ProgressType.DOWNLOAD) { + this._taskResourceService.downloadFile(this.resolveParentTaskId(), params).subscribe(response => { + if (!(response as ProviderProgress).type || (response as ProviderProgress).type !== ProgressType.DOWNLOAD) { this._log.debug(`File [${this.dataField.stringId}] ${this.dataField.value.name} was successfully downloaded`); this.initDownloadFile(response); } diff --git a/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.spec.ts b/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.spec.ts index e03a36ea6..2d9bb5574 100644 --- a/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.spec.ts +++ b/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.spec.ts @@ -16,9 +16,6 @@ import {MockUserResourceService} from "../../../utility/tests/mocks/mock-user-re import {ConfigurationService} from "../../../configuration/configuration.service"; import {TestConfigurationService} from "../../../utility/tests/test-config"; import {Component, CUSTOM_ELEMENTS_SCHEMA, Inject, Optional} from "@angular/core"; -import {BrowserDynamicTestingModule} from "@angular/platform-browser-dynamic/testing"; -import {ErrorSnackBarComponent} from "../../../snack-bar/components/error-snack-bar/error-snack-bar.component"; -import {SuccessSnackBarComponent} from "../../../snack-bar/components/success-snack-bar/success-snack-bar.component"; import {TaskResourceService} from "../../../resources/engine-endpoint/task-resource.service"; import {LoggerService} from "../../../logger/services/logger.service"; import {SnackBarService} from "../../../snack-bar/services/snack-bar.service"; @@ -28,6 +25,7 @@ import {DATA_FIELD_PORTAL_DATA, DataFieldPortalData} from "../../models/data-fie import {AbstractFileListDefaultFieldComponent} from "./abstract-file-list-default-field.component"; import {FormControl} from "@angular/forms"; import {WrappedBoolean} from "../../data-field-template/models/wrapped-boolean"; +import {FrontActionService} from "../../../actions/services/front-action.service"; describe('AbstractFileListDefaultFieldComponent', () => { let component: TestFileListComponent; @@ -46,6 +44,7 @@ describe('AbstractFileListDefaultFieldComponent', () => { providers: [ SideMenuService, EventService, + FrontActionService, {provide: AuthenticationMethodService, useClass: MockAuthenticationMethodService}, {provide: AuthenticationService, useClass: MockAuthenticationService}, {provide: UserResourceService, useClass: MockUserResourceService}, @@ -94,8 +93,9 @@ class TestFileListComponent extends AbstractFileListDefaultFieldComponent { snackbar: SnackBarService, translate: TranslateService, eventService: EventService, + frontActionService: FrontActionService, @Optional() @Inject(DATA_FIELD_PORTAL_DATA) dataFieldPortalData: DataFieldPortalData) { - super(taskResourceService, log, snackbar, translate, eventService, dataFieldPortalData); + super(taskResourceService, log, snackbar, translate, eventService, frontActionService, dataFieldPortalData); } } diff --git a/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.ts b/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.ts index 9fb99c8ee..7513b72e7 100644 --- a/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.ts +++ b/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.ts @@ -1,13 +1,10 @@ import { AfterViewInit, Component, - ElementRef, Inject, - Input, OnDestroy, OnInit, Optional, - ViewChild } from "@angular/core"; import {DATA_FIELD_PORTAL_DATA, DataFieldPortalData} from "../../models/data-field-portal-data-injection-token"; import {FileListField, FileListFieldValidation} from "../models/file-list-field"; @@ -17,16 +14,16 @@ import {LoggerService} from "../../../logger/services/logger.service"; import {SnackBarService} from "../../../snack-bar/services/snack-bar.service"; import {TranslateService} from "@ngx-translate/core"; import {EventService} from "../../../event/services/event.service"; -import {FileFieldIdBody} from "../../models/file-field-id-body"; import {EventOutcomeMessageResource} from "../../../resources/interface/message-resource"; import {ProgressType, ProviderProgress} from "../../../resources/resource-provider.service"; import {ChangedFieldsMap} from "../../../event/services/interfaces/changed-fields-map"; import {HttpParams} from "@angular/common/http"; import {take} from "rxjs/operators"; import {FileFieldValue} from "../../file-field/models/file-field-value"; -import {AbstractBaseDataFieldComponent} from "../../base-component/abstract-base-data-field.component"; import {FileFieldRequest} from "../../../resources/interface/file-field-request-body"; import {AbstractFileFieldDefaultComponent} from '../../models/abstract-file-field-default-component'; +import {FrontAction} from "../../models/changed-fields"; +import {FrontActionService} from "../../../actions/services/front-action.service"; export interface FilesState { progress: number; @@ -60,6 +57,7 @@ export abstract class AbstractFileListDefaultFieldComponent extends AbstractFile protected _snackbar: SnackBarService, protected _translate: TranslateService, protected _eventService: EventService, + protected _frontActionService: FrontActionService, @Optional() @Inject(DATA_FIELD_PORTAL_DATA) dataFieldPortalData: DataFieldPortalData) { super(_log, _snackbar, _translate, dataFieldPortalData); this.state = this.defaultState; @@ -171,58 +169,66 @@ export abstract class AbstractFileListDefaultFieldComponent extends AbstractFile fieldId: this.dataField.stringId, } fileFormData.append('data', new Blob([JSON.stringify(requestBody)], {type: 'application/json'})); - this._taskResourceService.uploadFile(this.taskId, fileFormData, true).subscribe((response: EventOutcomeMessageResource) => { - if ((response as ProviderProgress).type && (response as ProviderProgress).type === ProgressType.UPLOAD) { - this.state.progress = (response as ProviderProgress).progress; - } else { - this.state.completed = true; - this.state.uploading = false; - this.state.progress = 0; - this._log.debug( - `Files [${this.dataField.stringId}] were successfully uploaded` - ); - if (response.error) { - this.state.error = true; - this._log.error( - `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0)} uploading has failed!`, response.error + this._taskResourceService.uploadFile(this.taskId, fileFormData, true) + .subscribe({ + next: (response: EventOutcomeMessageResource) => { + if ((response as ProviderProgress).type && (response as ProviderProgress).type === ProgressType.UPLOAD) { + this.state.progress = (response as ProviderProgress).progress; + } else { + this.state.completed = true; + this.state.uploading = false; + this.state.progress = 0; + this._log.debug( + `Files [${this.dataField.stringId}] were successfully uploaded` ); if (response.error) { - this._snackbar.openErrorSnackBar(this._translate.instant(response.error)); + this.state.error = true; + this._log.error( + `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0)} uploading has failed!`, response.error + ); + if (response.error) { + this._snackbar.openErrorSnackBar(this._translate.instant(response.error)); + } else { + this._snackbar.openErrorSnackBar(this._translate.instant('dataField.snackBar.fileUploadFailed')); + } } else { - this._snackbar.openErrorSnackBar(this._translate.instant('dataField.snackBar.fileUploadFailed')); + const changedFieldsMap: ChangedFieldsMap = this._eventService.parseChangedFieldsFromOutcomeTree(response.outcome); + this.dataField.emitChangedFields(changedFieldsMap); + this.state.error = false; + filesToUpload.forEach(fileToUpload => { + this.uploadedFiles.push(fileToUpload.name); + this.dataField.value.namesPaths.push({name: fileToUpload.name}); + this.formControlRef.setValue(this.dataField.value.namesPaths.map(namePath => { + return namePath['name']; + }).join('/')); + }); + this._snackbar.openSuccessSnackBar(!!response.outcome.message ? response.outcome.message : this._translate.instant('tasks.snackbar.dataSaved')); + const frontActions: Array = this._eventService.parseFrontActionsFromOutcomeTree(response.outcome); + if (frontActions?.length > 0) { + this._frontActionService.runAll(frontActions); + } } + this.dataField.touch = true; + this.dataField.update(); + this.fileUploadEl.nativeElement.value = ''; + } + }, error: (error) => { + this.state.completed = true; + this.state.error = true; + this.state.uploading = false; + this.state.progress = 0; + if (error?.error?.message) { + this._snackbar.openErrorSnackBar(this._translate.instant(error.error.message)); } else { - const changedFieldsMap: ChangedFieldsMap = this._eventService.parseChangedFieldsFromOutcomeTree(response.outcome); - this.dataField.emitChangedFields(changedFieldsMap); - this.state.error = false; - filesToUpload.forEach(fileToUpload => { - this.uploadedFiles.push(fileToUpload.name); - this.dataField.value.namesPaths.push({name: fileToUpload.name}); - this.formControlRef.setValue(this.dataField.value.namesPaths.map(namePath => { - return namePath['name']; - }).join('/')); - }); + this._snackbar.openErrorSnackBar(this._translate.instant('dataField.snackBar.fileUploadFailed')); } + this._log.error( + `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0)} uploading has failed!`, error + ); this.dataField.touch = true; this.dataField.update(); this.fileUploadEl.nativeElement.value = ''; } - }, error => { - this.state.completed = true; - this.state.error = true; - this.state.uploading = false; - this.state.progress = 0; - if (error?.error?.message) { - this._snackbar.openErrorSnackBar(this._translate.instant(error.error.message)); - } else { - this._snackbar.openErrorSnackBar(this._translate.instant('dataField.snackBar.fileUploadFailed')); - } - this._log.error( - `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0)} uploading has failed!`, error - ); - this.dataField.touch = true; - this.dataField.update(); - this.fileUploadEl.nativeElement.value = ''; }); } diff --git a/projects/netgrif-components/src/lib/data-fields/file-field/file-default-field/file-default-field.component.ts b/projects/netgrif-components/src/lib/data-fields/file-field/file-default-field/file-default-field.component.ts index 378008fd9..f770cc156 100644 --- a/projects/netgrif-components/src/lib/data-fields/file-field/file-default-field/file-default-field.component.ts +++ b/projects/netgrif-components/src/lib/data-fields/file-field/file-default-field/file-default-field.component.ts @@ -5,7 +5,7 @@ import { LoggerService, SnackBarService, TaskResourceService, - AbstractFileDefaultFieldComponent + AbstractFileDefaultFieldComponent, FrontActionService } from "@netgrif/components-core"; import {TranslateService} from "@ngx-translate/core"; import {DomSanitizer} from "@angular/platform-browser"; @@ -26,8 +26,9 @@ export class FileDefaultFieldComponent extends AbstractFileDefaultFieldComponent eventService: EventService, protected _sanitizer: DomSanitizer, protected dialog: MatDialog, + frontActionService: FrontActionService, @Optional() @Inject(DATA_FIELD_PORTAL_DATA) dataFieldPortalData: DataFieldPortalData) { - super(taskResourceService, log, snackbar, translate, eventService, _sanitizer, dataFieldPortalData); + super(taskResourceService, log, snackbar, translate, eventService, _sanitizer, frontActionService, dataFieldPortalData); } public showPreviewDialog() { diff --git a/projects/netgrif-components/src/lib/data-fields/file-list-field/file-list-default-field/file-list-default-field.component.ts b/projects/netgrif-components/src/lib/data-fields/file-list-field/file-list-default-field/file-list-default-field.component.ts index 9a4d00a7f..e581ce13b 100644 --- a/projects/netgrif-components/src/lib/data-fields/file-list-field/file-list-default-field/file-list-default-field.component.ts +++ b/projects/netgrif-components/src/lib/data-fields/file-list-field/file-list-default-field/file-list-default-field.component.ts @@ -7,7 +7,7 @@ import { EventService, DATA_FIELD_PORTAL_DATA, DataFieldPortalData, - FileListField, AbstractFileListDefaultFieldComponent + FileListField, AbstractFileListDefaultFieldComponent, FrontActionService } from '@netgrif/components-core' @Component({ @@ -22,7 +22,8 @@ export class FileListDefaultFieldComponent extends AbstractFileListDefaultFieldC snackbar: SnackBarService, translate: TranslateService, eventService: EventService, + frontActionService: FrontActionService, @Optional() @Inject(DATA_FIELD_PORTAL_DATA) dataFieldPortalData: DataFieldPortalData) { - super(taskResourceService, log, snackbar, translate, eventService, dataFieldPortalData); + super(taskResourceService, log, snackbar, translate, eventService, frontActionService, dataFieldPortalData); } } From 4d83a8140d6d91dc85aacaf8021c0aa456414732 Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Mon, 27 Apr 2026 13:55:57 +0200 Subject: [PATCH 02/11] Add FrontActionService to test providers in file components Included FrontActionService in the test setup for file-list-default-field and file-default-field components. This ensures proper dependency injection and facilitates testing of features relying on FrontActionService. --- .../file-default-field/file-default-field.component.spec.ts | 3 ++- .../file-list-default-field.component.spec.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/projects/netgrif-components/src/lib/data-fields/file-field/file-default-field/file-default-field.component.spec.ts b/projects/netgrif-components/src/lib/data-fields/file-field/file-default-field/file-default-field.component.spec.ts index 4c4365875..1a8b3abc7 100644 --- a/projects/netgrif-components/src/lib/data-fields/file-field/file-default-field/file-default-field.component.spec.ts +++ b/projects/netgrif-components/src/lib/data-fields/file-field/file-default-field/file-default-field.component.spec.ts @@ -6,7 +6,7 @@ import { AuthenticationService, ConfigurationService, DATA_FIELD_PORTAL_DATA, DataFieldPortalData, - ErrorSnackBarComponent, FileField, + ErrorSnackBarComponent, FileField, FrontActionService, MaterialModule, MockAuthenticationMethodService, MockAuthenticationService, @@ -39,6 +39,7 @@ describe('FileDefaultFieldComponent', () => { ], providers: [ SideMenuService, + FrontActionService, {provide: AuthenticationMethodService, useClass: MockAuthenticationMethodService}, {provide: AuthenticationService, useClass: MockAuthenticationService}, {provide: UserResourceService, useClass: MockUserResourceService}, diff --git a/projects/netgrif-components/src/lib/data-fields/file-list-field/file-list-default-field/file-list-default-field.component.spec.ts b/projects/netgrif-components/src/lib/data-fields/file-list-field/file-list-default-field/file-list-default-field.component.spec.ts index 02b15542d..062c1a3e3 100644 --- a/projects/netgrif-components/src/lib/data-fields/file-list-field/file-list-default-field/file-list-default-field.component.spec.ts +++ b/projects/netgrif-components/src/lib/data-fields/file-list-field/file-list-default-field/file-list-default-field.component.spec.ts @@ -7,7 +7,7 @@ import { ConfigurationService, DATA_FIELD_PORTAL_DATA, DataFieldPortalData, - FileListField, + FileListField, FrontActionService, MaterialModule, MockAuthenticationMethodService, MockAuthenticationService, @@ -42,6 +42,7 @@ describe('FileListDefaultFieldComponent', () => { ], providers: [ SideMenuService, + FrontActionService, {provide: AuthenticationMethodService, useClass: MockAuthenticationMethodService}, {provide: AuthenticationService, useClass: MockAuthenticationService}, {provide: UserResourceService, useClass: MockUserResourceService}, From 1a5e592559b923bdbcaa5991ccf7169fe2f59639 Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Tue, 28 Apr 2026 08:57:12 +0200 Subject: [PATCH 03/11] Fix logging and error handling in file upload components Reorganized logging to ensure success messages are logged after error checks and refined error handling logic for file uploads. Added a null-safe access to file names and fixed missing return statement in front-action error handling. --- .../lib/actions/services/front-action.service.ts | 1 + .../abstract-file-default-field.component.ts | 13 +++++-------- .../abstract-file-list-default-field.component.ts | 10 ++++------ 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/projects/netgrif-components-core/src/lib/actions/services/front-action.service.ts b/projects/netgrif-components-core/src/lib/actions/services/front-action.service.ts index e245d1dd8..8a4c0bbc7 100644 --- a/projects/netgrif-components-core/src/lib/actions/services/front-action.service.ts +++ b/projects/netgrif-components-core/src/lib/actions/services/front-action.service.ts @@ -16,6 +16,7 @@ export class FrontActionService { const fn = this._frontActionRegistry.get(frontAction.id) if (!fn) { this._log.error("Frontend action is not defined for ID [" + frontAction.id +"]") + return; } fn.call(this._injector, frontAction) } diff --git a/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.ts b/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.ts index 75342fcba..726cbc928 100644 --- a/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.ts +++ b/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.ts @@ -232,18 +232,15 @@ export abstract class AbstractFileDefaultFieldComponent extends AbstractFileFiel if (response.error) { this.state.error = true; this._log.error( - `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0)} uploading has failed!`, response.error + `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0)?.name} uploading has failed!`, response.error ); - if (response.error) { - this._snackbar.openErrorSnackBar(this._translate.instant(response.error)); - } else { - this._snackbar.openErrorSnackBar(this._translate.instant('dataField.snackBar.fileUploadFailed')); - } + this._snackbar.openErrorSnackBar(this._translate.instant(response.error)); + } else { const changedFieldsMap: ChangedFieldsMap = this._eventService.parseChangedFieldsFromOutcomeTree(response.outcome); this.dataField.emitChangedFields(changedFieldsMap); this._log.debug( - `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0).name} was successfully uploaded` + `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0)?.name} was successfully uploaded` ); this.state.error = false; this.dataField.downloaded = false; @@ -271,7 +268,7 @@ export abstract class AbstractFileDefaultFieldComponent extends AbstractFileFiel this.state.uploading = false; this.state.progress = 0; this._log.error( - `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0)} uploading has failed!`, error + `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0)?.name} uploading has failed!`, error ); if (error?.error?.message) { this._snackbar.openErrorSnackBar(this._translate.instant(error.error.message)); diff --git a/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.ts b/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.ts index 7513b72e7..b75e27a9b 100644 --- a/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.ts +++ b/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.ts @@ -178,20 +178,18 @@ export abstract class AbstractFileListDefaultFieldComponent extends AbstractFile this.state.completed = true; this.state.uploading = false; this.state.progress = 0; - this._log.debug( - `Files [${this.dataField.stringId}] were successfully uploaded` - ); if (response.error) { this.state.error = true; this._log.error( - `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0)} uploading has failed!`, response.error + `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0).name} uploading has failed!`, response.error ); if (response.error) { this._snackbar.openErrorSnackBar(this._translate.instant(response.error)); - } else { - this._snackbar.openErrorSnackBar(this._translate.instant('dataField.snackBar.fileUploadFailed')); } } else { + this._log.debug( + `Files [${this.dataField.stringId}] were successfully uploaded` + ); const changedFieldsMap: ChangedFieldsMap = this._eventService.parseChangedFieldsFromOutcomeTree(response.outcome); this.dataField.emitChangedFields(changedFieldsMap); this.state.error = false; From 7594f6a0519ae57c0633ea337acc05c133429f6e Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Tue, 28 Apr 2026 13:12:48 +0200 Subject: [PATCH 04/11] Update file handling and event parsing, add test cases Refined file upload error logging and improved event parsing logic to handle frontActions. Added unit tests to verify file upload and download methods, ensuring better component reliability. Updated test dependencies to support new functionality. --- ...act-file-list-default-field.component.spec.ts | 16 ++++++++++++++-- ...abstract-file-list-default-field.component.ts | 2 +- .../src/lib/event/services/event.service.ts | 3 +++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.spec.ts b/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.spec.ts index 2d9bb5574..b295ad888 100644 --- a/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.spec.ts +++ b/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.spec.ts @@ -1,8 +1,8 @@ -import {ComponentFixture, TestBed, waitForAsync} from "@angular/core/testing"; +import {ComponentFixture, inject, TestBed, waitForAsync} from "@angular/core/testing"; import {MaterialModule} from "../../../material/material.module"; import {AngularResizeEventModule} from "angular-resize-event"; import {BrowserAnimationsModule} from "@angular/platform-browser/animations"; -import {HttpClientTestingModule} from "@angular/common/http/testing"; +import {HttpClientTestingModule, HttpTestingController} from "@angular/common/http/testing"; import {TranslateLibModule} from "../../../translate/translate-lib.module"; import {SnackBarModule} from "../../../snack-bar/snack-bar.module"; import {SideMenuService} from "../../../side-menu/services/side-menu.service"; @@ -78,6 +78,18 @@ describe('AbstractFileListDefaultFieldComponent', () => { expect(component).toBeTruthy(); }); + it('should call download method successfully', () => { + spyOn(component, 'download').and.callThrough(); // Spy on the method + component.download("test"); // Call the method + expect(component.download).toHaveBeenCalled(); // Assert that it was called + }); + + it('should call upload method successfully', () => { + spyOn(component, 'upload').and.callThrough(); // Spy on the method + component.upload(); // Call the method + expect(component.upload).toHaveBeenCalled(); // Assert that it was called + }); + afterEach(() => { TestBed.resetTestingModule(); }); diff --git a/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.ts b/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.ts index b75e27a9b..80d573ead 100644 --- a/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.ts +++ b/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.ts @@ -221,7 +221,7 @@ export abstract class AbstractFileListDefaultFieldComponent extends AbstractFile this._snackbar.openErrorSnackBar(this._translate.instant('dataField.snackBar.fileUploadFailed')); } this._log.error( - `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0)} uploading has failed!`, error + `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0)?.name} uploading has failed!`, error ); this.dataField.touch = true; this.dataField.update(); diff --git a/projects/netgrif-components-core/src/lib/event/services/event.service.ts b/projects/netgrif-components-core/src/lib/event/services/event.service.ts index b8805bbfd..cec572d1f 100644 --- a/projects/netgrif-components-core/src/lib/event/services/event.service.ts +++ b/projects/netgrif-components-core/src/lib/event/services/event.service.ts @@ -57,6 +57,9 @@ export class EventService { public parseFrontActionsFromOutcomeTree(outcome: EventOutcome): Array { const frontActions: Array = []; + if (!!outcome.frontActions) { + frontActions.push(...outcome.frontActions); + } if (!!outcome.outcomes && outcome.outcomes.length > 0) { return this.parseFrontActionsFromOutcomeTreeRecursive(outcome.outcomes, frontActions); } else return frontActions; From 340d2594b4cdb28b466704885e65e1c767ebd145 Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Tue, 28 Apr 2026 13:42:06 +0200 Subject: [PATCH 05/11] Update file list field test with additional imports and template Added missing imports for AbstractFileListFieldComponent and invalid data token to the test file. Updated the test component's template to include a file input element with binding to allowed file types. --- .../abstract-file-list-default-field.component.spec.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.spec.ts b/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.spec.ts index b295ad888..093cafd8e 100644 --- a/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.spec.ts +++ b/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.spec.ts @@ -26,6 +26,8 @@ import {AbstractFileListDefaultFieldComponent} from "./abstract-file-list-defaul import {FormControl} from "@angular/forms"; import {WrappedBoolean} from "../../data-field-template/models/wrapped-boolean"; import {FrontActionService} from "../../../actions/services/front-action.service"; +import {AbstractFileListFieldComponent} from "../abstract-file-list-field.component"; +import {NAE_INFORM_ABOUT_INVALID_DATA} from "../../models/invalid-data-policy-token"; describe('AbstractFileListDefaultFieldComponent', () => { let component: TestFileListComponent; @@ -97,7 +99,7 @@ describe('AbstractFileListDefaultFieldComponent', () => { @Component({ selector: 'ncc-test-filelist', - template: '' + template: '' }) class TestFileListComponent extends AbstractFileListDefaultFieldComponent { constructor(taskResourceService: TaskResourceService, From b8599953a1a4c524363a6e1d16c975e9912732fd Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Tue, 28 Apr 2026 13:42:43 +0200 Subject: [PATCH 06/11] Add unit tests for download and upload methods Implemented tests to verify that the download and upload methods are called successfully. These tests improve the coverage and ensure the methods function as expected. --- .../abstract-file-default-field.component.spec.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.spec.ts b/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.spec.ts index ee15de46e..3862af930 100644 --- a/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.spec.ts +++ b/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.spec.ts @@ -80,6 +80,18 @@ describe('AbstractFileDefaultFieldComponent', () => { expect(component).toBeTruthy(); }); + it('should call download method successfully', () => { + spyOn(component, 'download').and.callThrough(); // Spy on the method + component.download(); // Call the method + expect(component.download).toHaveBeenCalled(); // Assert that it was called + }); + + it('should call upload method successfully', () => { + spyOn(component, 'upload').and.callThrough(); // Spy on the method + component.upload(); // Call the method + expect(component.upload).toHaveBeenCalled(); // Assert that it was called + }); + afterEach(() => { TestBed.resetTestingModule(); }); From 26db13640f205bc4c8d8b2108f6a864d74bcb609 Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Tue, 28 Apr 2026 14:00:26 +0200 Subject: [PATCH 07/11] Add unit tests for download and upload methods Implemented tests to verify that the download and upload methods are called successfully. These tests improve the coverage and ensure the methods function as expected. --- .../abstract-file-default-field.component.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.spec.ts b/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.spec.ts index 3862af930..625e55093 100644 --- a/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.spec.ts +++ b/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.spec.ts @@ -99,7 +99,7 @@ describe('AbstractFileDefaultFieldComponent', () => { @Component({ selector: 'ncc-test-file', - template: '' + template: '' }) class TestFileComponent extends AbstractFileDefaultFieldComponent { constructor(taskResourceService: TaskResourceService, From 4a19ca705762a0cca51fed33109d52a53a3b81c0 Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Mon, 18 May 2026 08:24:03 +0200 Subject: [PATCH 08/11] Fix potential null reference in file upload error logging Updated the code to handle cases where the file name might be undefined during error logging. This change prevents runtime errors caused by null references when accessing the file list. --- .../abstract-file-list-default-field.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.ts b/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.ts index 80d573ead..4f10f7143 100644 --- a/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.ts +++ b/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.ts @@ -181,7 +181,7 @@ export abstract class AbstractFileListDefaultFieldComponent extends AbstractFile if (response.error) { this.state.error = true; this._log.error( - `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0).name} uploading has failed!`, response.error + `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0)?.name} uploading has failed!`, response.error ); if (response.error) { this._snackbar.openErrorSnackBar(this._translate.instant(response.error)); From f40cc9fe89b2e1a70fe6a20ac952824aaa9f70f8 Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Mon, 18 May 2026 08:24:46 +0200 Subject: [PATCH 09/11] Fix redundant error check during file upload failure Simplifies the error handling logic by removing an unnecessary null check for `response.error`. This ensures the snackbar displays error messages consistently without redundant conditionals. --- .../abstract-file-list-default-field.component.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.ts b/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.ts index 4f10f7143..56fec2aec 100644 --- a/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.ts +++ b/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.ts @@ -183,9 +183,7 @@ export abstract class AbstractFileListDefaultFieldComponent extends AbstractFile this._log.error( `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0)?.name} uploading has failed!`, response.error ); - if (response.error) { - this._snackbar.openErrorSnackBar(this._translate.instant(response.error)); - } + this._snackbar.openErrorSnackBar(this._translate.instant(response.error)); } else { this._log.debug( `Files [${this.dataField.stringId}] were successfully uploaded` From e60cbd9d5888ae86a8f8eec1558712d9dd80cdec Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Mon, 18 May 2026 09:32:40 +0200 Subject: [PATCH 10/11] Update mock service and enhance file upload/download tests Extended `MockTaskResourceService` with additional file methods to simulate file uploads and downloads. Improved test coverage in `AbstractFileListDefaultFieldComponent` and `AbstractFileDefaultFieldComponent` with refined mock implementations and more detailed file operation simulations. Added new dependencies for test files. --- ...tract-file-default-field.component.spec.ts | 49 +++++++++++++--- ...-file-list-default-field.component.spec.ts | 56 ++++++++++++++----- .../tests/mocks/mock-task-resource.service.ts | 25 ++++++++- 3 files changed, 108 insertions(+), 22 deletions(-) diff --git a/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.spec.ts b/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.spec.ts index 625e55093..741562069 100644 --- a/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.spec.ts +++ b/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.spec.ts @@ -27,6 +27,7 @@ import {DATA_FIELD_PORTAL_DATA, DataFieldPortalData} from "../../models/data-fie import {FormControl} from "@angular/forms"; import {WrappedBoolean} from "../../data-field-template/models/wrapped-boolean"; import {FrontActionService} from "../../../actions/services/front-action.service"; +import {MockTaskResourceService} from "../../../utility/tests/mocks/mock-task-resource.service"; describe('AbstractFileDefaultFieldComponent', () => { let component: TestFileComponent; @@ -51,6 +52,7 @@ describe('AbstractFileDefaultFieldComponent', () => { {provide: AuthenticationService, useClass: MockAuthenticationService}, {provide: UserResourceService, useClass: MockUserResourceService}, {provide: ConfigurationService, useClass: TestConfigurationService}, + {provide: TaskResourceService, useClass: MockTaskResourceService}, {provide: DATA_FIELD_PORTAL_DATA, useValue: { dataField: new FileField('', '', { required: true, @@ -80,16 +82,47 @@ describe('AbstractFileDefaultFieldComponent', () => { expect(component).toBeTruthy(); }); - it('should call download method successfully', () => { - spyOn(component, 'download').and.callThrough(); // Spy on the method - component.download(); // Call the method - expect(component.download).toHaveBeenCalled(); // Assert that it was called + + it('should simulate file download', () => { + // Spy on the 'download' method + spyOn(component, 'download').and.callFake(() => { + // Simulate the download process + const anchor = document.createElement('a'); + anchor.href = 'data:text/plain;charset=utf-8,' + encodeURIComponent('mockContent'); + anchor.download = 'mockFile.txt'; + document.body.appendChild(anchor); + anchor.click(); + document.body.removeChild(anchor); + }); + + // Call the 'download' method + component.download(); + + // Assert that the 'download' method was called + expect(component.download).toHaveBeenCalled(); }); - it('should call upload method successfully', () => { - spyOn(component, 'upload').and.callThrough(); // Spy on the method - component.upload(); // Call the method - expect(component.upload).toHaveBeenCalled(); // Assert that it was called + it('should simulate file upload', () => { + // Create a mock file + const mockFile = new File(['mockContent'], 'mockFile.txt', {type: 'text/plain'}); + + // Get the file input element's reference + const fileInput = component.fileUploadEl.nativeElement; + + // Create a DataTransfer object to simulate file selection + const dataTransfer = new DataTransfer(); + dataTransfer.items.add(mockFile); + fileInput.files = dataTransfer.files; + component.dataField.value = {file: mockFile, name: "mockFile.txt"} + // Trigger the file upload method + spyOn(component, 'upload').and.callThrough(); + const uploadButtonEvent = new Event('change'); + fileInput.dispatchEvent(uploadButtonEvent); + component.upload(); + + // Assertions + expect(component.upload).toHaveBeenCalled(); + expect(component.dataField.value).toBeTruthy(); // Ensure value is processed }); afterEach(() => { diff --git a/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.spec.ts b/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.spec.ts index 093cafd8e..782c14187 100644 --- a/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.spec.ts +++ b/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.spec.ts @@ -1,8 +1,8 @@ -import {ComponentFixture, inject, TestBed, waitForAsync} from "@angular/core/testing"; +import {ComponentFixture, TestBed, waitForAsync} from "@angular/core/testing"; import {MaterialModule} from "../../../material/material.module"; import {AngularResizeEventModule} from "angular-resize-event"; import {BrowserAnimationsModule} from "@angular/platform-browser/animations"; -import {HttpClientTestingModule, HttpTestingController} from "@angular/common/http/testing"; +import {HttpClientTestingModule} from "@angular/common/http/testing"; import {TranslateLibModule} from "../../../translate/translate-lib.module"; import {SnackBarModule} from "../../../snack-bar/snack-bar.module"; import {SideMenuService} from "../../../side-menu/services/side-menu.service"; @@ -26,8 +26,7 @@ import {AbstractFileListDefaultFieldComponent} from "./abstract-file-list-defaul import {FormControl} from "@angular/forms"; import {WrappedBoolean} from "../../data-field-template/models/wrapped-boolean"; import {FrontActionService} from "../../../actions/services/front-action.service"; -import {AbstractFileListFieldComponent} from "../abstract-file-list-field.component"; -import {NAE_INFORM_ABOUT_INVALID_DATA} from "../../models/invalid-data-policy-token"; +import {MockTaskResourceService} from "../../../utility/tests/mocks/mock-task-resource.service"; describe('AbstractFileListDefaultFieldComponent', () => { let component: TestFileListComponent; @@ -51,6 +50,7 @@ describe('AbstractFileListDefaultFieldComponent', () => { {provide: AuthenticationService, useClass: MockAuthenticationService}, {provide: UserResourceService, useClass: MockUserResourceService}, {provide: ConfigurationService, useClass: TestConfigurationService}, + {provide: TaskResourceService, useClass: MockTaskResourceService}, {provide: DATA_FIELD_PORTAL_DATA, useValue: { dataField: new FileListField('', '', { required: true, @@ -58,7 +58,7 @@ describe('AbstractFileListDefaultFieldComponent', () => { visible: true, editable: true, hidden: true - }), + }, {}), formControlRef: new FormControl(), showLargeLayout: new WrappedBoolean(), additionalFieldProperties: {taskId: '0'} @@ -80,16 +80,46 @@ describe('AbstractFileListDefaultFieldComponent', () => { expect(component).toBeTruthy(); }); - it('should call download method successfully', () => { - spyOn(component, 'download').and.callThrough(); // Spy on the method - component.download("test"); // Call the method - expect(component.download).toHaveBeenCalled(); // Assert that it was called + it('should simulate file download', () => { + // Spy on the 'download' method + spyOn(component, 'download').and.callFake(() => { + // Simulate the download process + const anchor = document.createElement('a'); + anchor.href = 'data:text/plain;charset=utf-8,' + encodeURIComponent('mockContent'); + anchor.download = 'mockFile.txt'; + document.body.appendChild(anchor); + anchor.click(); + document.body.removeChild(anchor); + }); + + // Call the 'download' method + component.download("mockFile.txt"); + + // Assert that the 'download' method was called + expect(component.download).toHaveBeenCalled(); }); - it('should call upload method successfully', () => { - spyOn(component, 'upload').and.callThrough(); // Spy on the method - component.upload(); // Call the method - expect(component.upload).toHaveBeenCalled(); // Assert that it was called + it('should simulate file upload', () => { + // Create a mock file + const mockFile = new File(['mockContent'], 'mockFile.txt', {type: 'text/plain'}); + + // Get the file input element's reference + const fileInput = component.fileUploadEl.nativeElement; + + // Create a DataTransfer object to simulate file selection + const dataTransfer = new DataTransfer(); + dataTransfer.items.add(mockFile); + fileInput.files = dataTransfer.files; + component.dataField.value = {namesPaths: [{file: mockFile, name: "mockFile.txt"}]} + // Trigger the file upload method + spyOn(component, 'upload').and.callThrough(); + const uploadButtonEvent = new Event('change'); + fileInput.dispatchEvent(uploadButtonEvent); + component.upload(); + + // Assertions + expect(component.upload).toHaveBeenCalled(); + expect(component.dataField.value).toBeTruthy(); // Ensure value is processed }); afterEach(() => { diff --git a/projects/netgrif-components-core/src/lib/utility/tests/mocks/mock-task-resource.service.ts b/projects/netgrif-components-core/src/lib/utility/tests/mocks/mock-task-resource.service.ts index c42dabdc7..e02c570a0 100644 --- a/projects/netgrif-components-core/src/lib/utility/tests/mocks/mock-task-resource.service.ts +++ b/projects/netgrif-components-core/src/lib/utility/tests/mocks/mock-task-resource.service.ts @@ -2,9 +2,14 @@ import {Injectable} from "@angular/core"; import {Observable, of} from "rxjs"; import {Page} from "../../../resources/interface/page"; import {Task} from "../../../resources/interface/task"; +import {TaskResourceService} from "../../../resources/engine-endpoint/task-resource.service"; +import {ProviderProgress} from "../../../resources/resource-provider.service"; +import {EventOutcomeMessageResource} from "../../../resources/interface/message-resource"; +import {SetDataEventOutcome} from "../../../event/model/event-outcomes/data-outcomes/set-data-event-outcome"; +import {Change} from "../../../data-fields/models/changed-fields"; @Injectable() -export class MockTaskResourceService { +export class MockTaskResourceService extends TaskResourceService { searchTask(): Observable> { return of(); @@ -13,4 +18,22 @@ export class MockTaskResourceService { getTasks(): Observable> { return of(); } + + + uploadFile(taskId: string, body: object, multipleFiles: boolean): Observable { + return of({ + outcome: { + net: undefined, + aCase: undefined, + task: undefined, + changedFields: { + changedFields: { + text: { + value: "" + } as Change + } + } + } as SetDataEventOutcome + }); + } } From 26c66aa9dcf9a51f3311ec889ba7e78ae4859cba Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Tue, 19 May 2026 13:02:33 +0200 Subject: [PATCH 11/11] Refactor file upload logic to improve readability Reordered the parsing and emission of changed fields after a successful file upload. This ensures consistent execution flow and enhances code clarity. --- .../abstract-file-default-field.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.ts b/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.ts index 726cbc928..20a551442 100644 --- a/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.ts +++ b/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.ts @@ -237,11 +237,11 @@ export abstract class AbstractFileDefaultFieldComponent extends AbstractFileFiel this._snackbar.openErrorSnackBar(this._translate.instant(response.error)); } else { - const changedFieldsMap: ChangedFieldsMap = this._eventService.parseChangedFieldsFromOutcomeTree(response.outcome); - this.dataField.emitChangedFields(changedFieldsMap); this._log.debug( `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0)?.name} was successfully uploaded` ); + const changedFieldsMap: ChangedFieldsMap = this._eventService.parseChangedFieldsFromOutcomeTree(response.outcome); + this.dataField.emitChangedFields(changedFieldsMap); this.state.error = false; this.dataField.downloaded = false; this.dataField.value.name = fileToUpload.name;