From 251975d2cece0080aed44fd81a5695b0023ef40c Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Fri, 11 Oct 2024 14:40:49 +0200 Subject: [PATCH 1/2] [DSC-1864] revert treeview changes after alignment with 7.6.2 --- .../vocabulary-treeview.component.spec.ts | 86 ++++++++------ .../vocabulary-treeview.component.ts | 106 +++++++++--------- 2 files changed, 105 insertions(+), 87 deletions(-) diff --git a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.spec.ts b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.spec.ts index 0d17cb0317a..2cc9b500f41 100644 --- a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.spec.ts +++ b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.spec.ts @@ -1,12 +1,11 @@ -import { ChangeDetectorRef, Component, NO_ERRORS_SCHEMA } from '@angular/core'; +import { Component, NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, inject, TestBed, waitForAsync } from '@angular/core/testing'; import { CdkTreeModule } from '@angular/cdk/tree'; +import { By } from '@angular/platform-browser'; import { of as observableOf } from 'rxjs'; -import { StoreModule } from '@ngrx/store'; import { TranslateModule } from '@ngx-translate/core'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; -import { provideMockStore } from '@ngrx/store/testing'; import { createTestComponent } from '../../testing/utils.test'; import { VocabularyTreeviewComponent } from './vocabulary-treeview.component'; @@ -16,29 +15,31 @@ import { TreeviewFlatNode, TreeviewNode } from './vocabulary-treeview-node.model import { FormFieldMetadataValueObject } from '../builder/models/form-field-metadata-value.model'; import { VocabularyOptions } from '../../../core/submission/vocabularies/models/vocabulary-options.model'; import { PageInfo } from '../../../core/shared/page-info.model'; -import { VocabularyEntry } from '../../../core/submission/vocabularies/models/vocabulary-entry.model'; -import { AuthTokenInfo } from '../../../core/auth/models/auth-token-info.model'; -import { authReducer } from '../../../core/auth/auth.reducer'; -import { storeModuleConfig } from '../../../app.reducer'; -import { By } from '@angular/platform-browser'; import { VocabularyService } from '../../../core/submission/vocabularies/vocabulary.service'; +import { VocabularyEntry } from '../../../core/submission/vocabularies/models/vocabulary-entry.model'; describe('VocabularyTreeviewComponent test suite', () => { let comp: VocabularyTreeviewComponent; let compAsAny: any; let fixture: ComponentFixture; - let initialState; let de; const item = new VocabularyEntryDetail(); item.id = 'node1'; const item2 = new VocabularyEntryDetail(); item2.id = 'node2'; + const entryWithAuthority = new VocabularyEntryDetail(); + entryWithAuthority.authority = 'entryWithAuthority'; + entryWithAuthority.id = 'entryWithAuthority'; + entryWithAuthority.value = 'test'; + const entryWithoutAuthority = new VocabularyEntryDetail(); + entryWithoutAuthority.id = 'entryWithoutAuthority'; + entryWithoutAuthority.value = 'test2'; const emptyNodeMap = new Map(); const storedNodeMap = new Map().set('test', new TreeviewFlatNode(item2)); const nodeMap = new Map().set('test', new TreeviewFlatNode(item)); - const vocabularyOptions = new VocabularyOptions('vocabularyTest', 'false'); + const vocabularyOptions = new VocabularyOptions('vocabularyTest', null, null, false); const modalStub = jasmine.createSpyObj('modalStub', ['close']); const vocabularyTreeviewServiceStub = jasmine.createSpyObj('VocabularyTreeviewService', { initialize: jasmine.createSpy('initialize'), @@ -59,25 +60,10 @@ describe('VocabularyTreeviewComponent test suite', () => { clearSearchTopRequests: jasmine.createSpy('clearSearchTopRequests') }); - initialState = { - core: { - auth: { - authenticated: true, - loaded: true, - blocking: false, - loading: false, - authToken: new AuthTokenInfo('test_token'), - userId: 'testid', - authMethods: [] - } - } - }; - beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [ CdkTreeModule, - StoreModule.forRoot({ auth: authReducer }, storeModuleConfig), TranslateModule.forRoot() ], declarations: [ @@ -88,8 +74,6 @@ describe('VocabularyTreeviewComponent test suite', () => { { provide: VocabularyTreeviewService, useValue: vocabularyTreeviewServiceStub }, { provide: VocabularyService, useValue: vocabularyServiceStub }, { provide: NgbActiveModal, useValue: modalStub }, - provideMockStore({ initialState }), - ChangeDetectorRef, VocabularyTreeviewComponent ], schemas: [NO_ERRORS_SCHEMA] @@ -144,10 +128,10 @@ describe('VocabularyTreeviewComponent test suite', () => { currentValue.otherInformation = { id: 'entryID' }; - comp.selectedItems = [currentValue.value]; + comp.selectedItems = [currentValue]; fixture.detectChanges(); expect(comp.dataSource.data).toEqual([]); - expect(vocabularyTreeviewServiceStub.initialize).toHaveBeenCalledWith(comp.vocabularyOptions, new PageInfo(), ['testValue'], null); + expect(vocabularyTreeviewServiceStub.initialize).toHaveBeenCalledWith(comp.vocabularyOptions, new PageInfo(), ['entryID'], 'entryID'); }); it('should should init component properly with init value as VocabularyEntry', () => { @@ -156,10 +140,20 @@ describe('VocabularyTreeviewComponent test suite', () => { currentValue.otherInformation = { id: 'entryID' }; - comp.selectedItems = [currentValue.value]; + comp.selectedItems = [currentValue]; + fixture.detectChanges(); + expect(comp.dataSource.data).toEqual([]); + expect(vocabularyTreeviewServiceStub.initialize).toHaveBeenCalledWith(comp.vocabularyOptions, new PageInfo(), ['entryID'], 'entryID'); + }); + + it('should should init component properly with init value as VocabularyEntryDetail', () => { + const currentValue = new VocabularyEntryDetail(); + currentValue.value = 'testValue'; + currentValue.id = 'entryID'; + comp.selectedItems = [currentValue]; fixture.detectChanges(); expect(comp.dataSource.data).toEqual([]); - expect(vocabularyTreeviewServiceStub.initialize).toHaveBeenCalledWith(comp.vocabularyOptions, new PageInfo(), ['testValue'], null); + expect(vocabularyTreeviewServiceStub.initialize).toHaveBeenCalledWith(comp.vocabularyOptions, new PageInfo(), ['entryID'], 'entryID'); }); it('should call loadMore function', () => { @@ -182,11 +176,31 @@ describe('VocabularyTreeviewComponent test suite', () => { expect(vocabularyTreeviewServiceStub.loadMore).toHaveBeenCalledWith(node.item, [], true); }); - it('should emit select event', () => { - spyOn(comp, 'onSelect'); - comp.onSelect(item); + it('should emit proper FormFieldMetadataValueObject when VocabularyEntryDetail has authority', () => { + spyOn(compAsAny, 'getSelectedEntryIds').and.returnValue([]); + spyOn(comp.select, 'emit'); + comp.onSelect(entryWithAuthority); + + const expected = new FormFieldMetadataValueObject(entryWithAuthority.value, null, null, entryWithAuthority.authority); + expect(comp.select.emit).toHaveBeenCalledWith(expected); + }); + + it('should emit proper FormFieldMetadataValueObject when VocabularyEntryDetail has no authority', () => { + spyOn(compAsAny, 'getSelectedEntryIds').and.returnValue([]); + spyOn(comp.select, 'emit'); + comp.onSelect(entryWithoutAuthority); + + const expected = new FormFieldMetadataValueObject(entryWithoutAuthority.value); + expect(comp.select.emit).toHaveBeenCalledWith(expected); + }); + + it('should emit deselect when entry is already present', () => { + spyOn(compAsAny, 'getSelectedEntryIds').and.returnValue([entryWithAuthority.id]); + spyOn(comp.select, 'emit'); + spyOn(comp.deselect, 'emit'); + comp.onSelect(entryWithAuthority); - expect(comp.onSelect).toHaveBeenCalledWith(item); + expect(comp.deselect.emit).toHaveBeenCalled(); }); it('should call searchByQuery function and set storedNodeMap properly', () => { @@ -288,7 +302,7 @@ describe('VocabularyTreeviewComponent test suite', () => { }) class TestComponent { - vocabularyOptions: VocabularyOptions = new VocabularyOptions('vocabularyTest', 'false'); + vocabularyOptions: VocabularyOptions = new VocabularyOptions('vocabularyTest', null, null, false); preloadLevel = 2; } diff --git a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.ts b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.ts index 9b0958472a1..2914464341d 100644 --- a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.ts +++ b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.ts @@ -1,8 +1,7 @@ import { FlatTreeControl } from '@angular/cdk/tree'; -import { Component, EventEmitter, Input, OnDestroy, OnInit, Output, OnChanges, SimpleChanges } from '@angular/core'; +import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges } from '@angular/core'; import { Observable, Subscription } from 'rxjs'; -import { Store } from '@ngrx/store'; import { TranslateService } from '@ngx-translate/core'; import { VocabularyEntryDetail } from '../../../core/submission/vocabularies/models/vocabulary-entry-detail.model'; @@ -14,10 +13,10 @@ import { PageInfo } from '../../../core/shared/page-info.model'; import { VocabularyEntry } from '../../../core/submission/vocabularies/models/vocabulary-entry.model'; import { VocabularyTreeFlattener } from './vocabulary-tree-flattener'; import { VocabularyTreeFlatDataSource } from './vocabulary-tree-flat-data-source'; -import { CoreState } from '../../../core/core-state.model'; import { VocabularyService } from '../../../core/submission/vocabularies/vocabulary.service'; -import { getFirstSucceededRemoteDataPayload } from '../../../core/shared/operators'; -import { AlertType } from '../../alert/alert-type'; +import { FormFieldMetadataValueObject } from '../builder/models/form-field-metadata-value.model'; + +export type VocabularyTreeItemType = FormFieldMetadataValueObject | VocabularyEntry | VocabularyEntryDetail; /** * Component that shows a hierarchical vocabulary in a tree view @@ -39,11 +38,6 @@ export class VocabularyTreeviewComponent implements OnDestroy, OnInit, OnChanges */ @Input() preloadLevel = 2; - /** - * The vocabulary entries already selected, if any - */ - @Input() selectedItems: string[] = []; - /** * Contain a descriptive message for the tree */ @@ -59,6 +53,11 @@ export class VocabularyTreeviewComponent implements OnDestroy, OnInit, OnChanges */ @Input() showAdd = true; + /** + * The vocabulary entries already selected, if any + */ + @Input() selectedItems: VocabularyTreeItemType[] = []; + /** * A map containing the current node showed by the tree */ @@ -96,40 +95,31 @@ export class VocabularyTreeviewComponent implements OnDestroy, OnInit, OnChanges /** * An event fired when a vocabulary entry is selected. - * Event's payload equals to {@link VocabularyEntryDetail} selected. + * Event's payload equals to {@link VocabularyTreeItemType} selected. */ - @Output() select: EventEmitter = new EventEmitter(null); + @Output() select: EventEmitter = new EventEmitter(null); /** * An event fired when a vocabulary entry is deselected. - * Event's payload equals to {@link VocabularyEntryDetail} deselected. + * Event's payload equals to {@link VocabularyTreeItemType} deselected. */ - @Output() deselect: EventEmitter = new EventEmitter(null); - - /** - * A boolean representing if user is authenticated - */ - private isAuthenticated: Observable; + @Output() deselect: EventEmitter = new EventEmitter(null); /** * Array to track all subscriptions and unsubscribe them onDestroy */ private subs: Subscription[] = []; - readonly AlertType = AlertType; - /** * Initialize instance variables * * @param {VocabularyTreeviewService} vocabularyTreeviewService * @param {vocabularyService} vocabularyService - * @param {Store} store * @param {TranslateService} translate */ constructor( private vocabularyTreeviewService: VocabularyTreeviewService, private vocabularyService: VocabularyService, - private store: Store, private translate: TranslateService ) { this.treeFlattener = new VocabularyTreeFlattener(this.transformer, this.getLevel, @@ -152,24 +142,24 @@ export class VocabularyTreeviewComponent implements OnDestroy, OnInit, OnChanges * @param level The node level information */ transformer = (node: TreeviewNode, level: number) => { - const existingNode = this.nodeMap.get(node.item.id); + const entryId = this.getEntryId(node.item); + const existingNode = this.nodeMap.get(entryId); if (existingNode && existingNode.item.id !== LOAD_MORE && existingNode.item.id !== LOAD_MORE_ROOT) { return existingNode; } - const newNode: TreeviewFlatNode = new TreeviewFlatNode( node.item, level, node.hasChildren, - (node.hasChildren && isNotEmpty(node.children)), + ((!node.isSearchNode && node.hasChildren) || (node.isSearchNode && node.hasChildren && isNotEmpty(node.children))), node.pageInfo, node.loadMoreParentItem, node.isSearchNode, node.isInInitValueHierarchy, node.isSelected ); - this.nodeMap.set(node.item.id, newNode); + this.nodeMap.set(entryId, newNode); if ((((level + 1) < this.preloadLevel) && newNode.childrenLoaded) || (newNode.isSearchNode && newNode.childrenLoaded) @@ -224,7 +214,8 @@ export class VocabularyTreeviewComponent implements OnDestroy, OnInit, OnChanges this.loading = this.vocabularyTreeviewService.isLoading(); - this.vocabularyTreeviewService.initialize(this.vocabularyOptions, new PageInfo(), this.selectedItems, null); + const entryId: string = (this.selectedItems?.length > 0) ? this.getEntryId(this.selectedItems[0]) : null; + this.vocabularyTreeviewService.initialize(this.vocabularyOptions, new PageInfo(), this.getSelectedEntryIds(), entryId); } /** @@ -232,7 +223,7 @@ export class VocabularyTreeviewComponent implements OnDestroy, OnInit, OnChanges * @param item The VocabularyEntryDetail for which to load more nodes */ loadMore(item: VocabularyEntryDetail) { - this.vocabularyTreeviewService.loadMore(item, this.selectedItems); + this.vocabularyTreeviewService.loadMore(item, this.getSelectedEntryIds()); } /** @@ -240,7 +231,7 @@ export class VocabularyTreeviewComponent implements OnDestroy, OnInit, OnChanges * @param node The TreeviewFlatNode for which to load more nodes */ loadMoreRoot(node: TreeviewFlatNode) { - this.vocabularyTreeviewService.loadMoreRoot(node, this.selectedItems); + this.vocabularyTreeviewService.loadMoreRoot(node, this.getSelectedEntryIds()); } /** @@ -248,18 +239,18 @@ export class VocabularyTreeviewComponent implements OnDestroy, OnInit, OnChanges * @param node The TreeviewFlatNode for which to load children nodes */ loadChildren(node: TreeviewFlatNode) { - this.vocabularyTreeviewService.loadMore(node.item, this.selectedItems, true); + this.vocabularyTreeviewService.loadMore(node.item, this.getSelectedEntryIds(), true); } /** * Method called on entry select/deselect */ onSelect(item: VocabularyEntryDetail) { - if (!this.selectedItems.includes(item.id)) { - this.selectedItems.push(item.id); - this.select.emit(item); + if (!this.getSelectedEntryIds().includes(this.getEntryId(item))) { + this.selectedItems.push(item); + this.select.emit(new FormFieldMetadataValueObject(item.value, null, item.securityLevel, item.authority, item.display)); } else { - this.selectedItems = this.selectedItems.filter((detail: string) => { return detail !== item.id; }); + this.selectedItems = this.selectedItems.filter((detail: VocabularyTreeItemType) => this.getEntryId(detail) !== this.getEntryId(item)); this.deselect.emit(item); } } @@ -273,7 +264,7 @@ export class VocabularyTreeviewComponent implements OnDestroy, OnInit, OnChanges this.storedNodeMap = this.nodeMap; } this.nodeMap = new Map(); - this.vocabularyTreeviewService.searchByQuery(this.searchText, this.selectedItems); + this.vocabularyTreeviewService.searchByQuery(this.searchText, this.getSelectedEntryIds()); } } @@ -290,12 +281,8 @@ export class VocabularyTreeviewComponent implements OnDestroy, OnInit, OnChanges reset() { this.searchText = ''; for (const item of this.selectedItems) { - this.subs.push(this.vocabularyService.findEntryDetailById(item, this.vocabularyOptions.name, true, true, false).pipe( - getFirstSucceededRemoteDataPayload(), - ).subscribe((detail: VocabularyEntryDetail) => { - this.deselect.emit(detail); - })); - this.nodeMap.get(item).isSelected = false; + this.deselect.emit(item); + this.nodeMap.get(this.getEntryId(item)).isSelected = false; } this.selectedItems = []; @@ -307,10 +294,13 @@ export class VocabularyTreeviewComponent implements OnDestroy, OnInit, OnChanges } add() { - const userVocabularyEntry = { - value: this.searchText, - display: this.searchText, - } as VocabularyEntryDetail; + const userVocabularyEntry = new FormFieldMetadataValueObject( + this.searchText, + null, + null, + null, + this.searchText + ); this.select.emit(userVocabularyEntry); } @@ -326,14 +316,28 @@ export class VocabularyTreeviewComponent implements OnDestroy, OnInit, OnChanges } /** - * Return an id for a given {@link VocabularyEntry} + * Return an id for a given {@link VocabularyTreeItemType} */ - private getEntryId(entry: VocabularyEntry): string { - return entry.authority || entry.otherInformation.id || undefined; + private getEntryId(entry: VocabularyTreeItemType): string { + const entryId: string = entry?.authority || entry?.otherInformation?.id || (entry as any)?.id || undefined; + return entryId?.startsWith(this.vocabularyOptions.name) ? entryId.replace(`${this.vocabularyOptions.name}:`, '') : entryId; + } + + /** + * Return an ids for all selected entries + */ + private getSelectedEntryIds(): string[] { + return this.selectedItems + .map((entry: VocabularyTreeItemType) => this.getEntryId(entry)) + .filter((value) => isNotEmpty(value)); } ngOnChanges(changes: SimpleChanges): void { - this.reset(); - this.vocabularyTreeviewService.initialize(this.vocabularyOptions, new PageInfo(), this.selectedItems, null); + if (!changes.vocabularyOptions.isFirstChange() && changes.vocabularyOptions.currentValue !== changes.vocabularyOptions.previousValue) { + this.selectedItems = []; + this.searchText = ''; + this.vocabularyTreeviewService.cleanTree(); + this.vocabularyTreeviewService.initialize(this.vocabularyOptions, new PageInfo(), this.getSelectedEntryIds(), null); + } } } From 8373868874eed8efc9594b722629e8c3203c9363 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Fri, 11 Oct 2024 16:29:49 +0200 Subject: [PATCH 2/2] [DSC-1864] porting of Fixed browse by vocabulary treeview accessibility issues --- .../vocabulary-treeview.component.html | 27 +++++++++---------- .../vocabulary-treeview.component.ts | 3 +++ 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.html b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.html index 27d5f902087..b7c4a622075 100644 --- a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.html +++ b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.html @@ -1,21 +1,20 @@ - -
-
+ +
+
- +
- - -
+ +
@@ -59,7 +58,7 @@

- +