From f704a00bea3f7fbf0ec50705dd96d3f4178f5300 Mon Sep 17 00:00:00 2001 From: Angelika Kinas Date: Tue, 31 Oct 2023 15:06:06 +0100 Subject: [PATCH 1/6] feat(libs): Add selection service and first test --- .../gn4/selection/selection.service.spec.ts | 67 +++++++++++++++++++ .../lib/gn4/selection/selection.service.ts | 47 +++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 libs/api/repository/src/lib/gn4/selection/selection.service.spec.ts create mode 100644 libs/api/repository/src/lib/gn4/selection/selection.service.ts diff --git a/libs/api/repository/src/lib/gn4/selection/selection.service.spec.ts b/libs/api/repository/src/lib/gn4/selection/selection.service.spec.ts new file mode 100644 index 0000000000..a33f213575 --- /dev/null +++ b/libs/api/repository/src/lib/gn4/selection/selection.service.spec.ts @@ -0,0 +1,67 @@ +import { SelectionsApiService } from '@geonetwork-ui/data-access/gn4' +import { SelectionService } from './selection.service' +import { firstValueFrom, of } from 'rxjs' +import { CatalogRecord } from '@geonetwork-ui/common/domain/record' + +function record(uuid: string): CatalogRecord { + return { + uniqueIdentifier: uuid, + } as CatalogRecord +} + +class SelectionsServiceMock { + private selected = ['001', '002', '003'] + add = jest.fn((bucket, ids) => { + this.selected.push(...ids) + return of(undefined) + }) + clear = jest.fn((bucket, ids) => { + this.selected = this.selected.filter( + (id) => !!ids && ids.indexOf(id) === -1 + ) + return of(undefined) + }) + get = jest.fn(() => of(this.selected)) +} + +describe('SelectionService', () => { + let service: SelectionService + let selectionsService: SelectionsApiService + + beforeEach(async () => { + selectionsService = new SelectionsServiceMock() as any + service = new SelectionService(selectionsService) + }) + + it('should be created', () => { + expect(service).toBeTruthy() + }) + + describe('#selectRecords', () => { + let selectedRecords + beforeEach(async () => { + service.selectedRecordsIdentifiers$.subscribe((value) => { + selectedRecords = value + }) + await firstValueFrom( + service.selectRecords([record('abcd'), record('efgh'), record('001')]) + ) + }) + it('calls the corresponding API', () => { + expect(selectionsService.add).toHaveBeenCalledWith('gnui', [ + 'abcd', + 'efgh', + '001', + ]) + }) + it('emits new records in selectedRecordsIdentifiers$', () => { + expect(selectedRecords).toEqual(['001', '002', '003', 'abcd', 'efgh']) + }) + }) + + // describe('#deselectRecord', () => {}) + + // describe('#clearSelection', () => {}) + + // describe('selectedRecordsIdentifiers$', () => {}) +}) diff --git a/libs/api/repository/src/lib/gn4/selection/selection.service.ts b/libs/api/repository/src/lib/gn4/selection/selection.service.ts new file mode 100644 index 0000000000..88db88fd49 --- /dev/null +++ b/libs/api/repository/src/lib/gn4/selection/selection.service.ts @@ -0,0 +1,47 @@ +import { Injectable } from '@angular/core' +import { CatalogRecord } from '@geonetwork-ui/common/domain/record' +import { SelectionsApiService } from '@geonetwork-ui/data-access/gn4' +import { BehaviorSubject, Observable, map, tap } from 'rxjs' + +const BUCKET_ID = 'gnui' + +@Injectable({ + providedIn: 'root', +}) +export class SelectionService { + selectedRecordsIdentifiers$: BehaviorSubject = new BehaviorSubject( + [] + ) + + constructor(private selectionsApi: SelectionsApiService) { + this.selectionsApi.get(BUCKET_ID).subscribe((selectedIds) => { + this.addIdsToSelected(Array.from(selectedIds)) + }) + } + + private addIdsToSelected(ids: string[]) { + const currentIds = this.selectedRecordsIdentifiers$.value + const uniqueSet = new Set([...currentIds, ...ids]) + this.selectedRecordsIdentifiers$.next([...uniqueSet]) + } + + private removeIdsFromSelected(ids: string[]) {} + + selectRecords(records: CatalogRecord[]): Observable { + const newIds = [] + records.map((record) => { + newIds.push(record.uniqueIdentifier) + }) + const apiResponse = this.selectionsApi.add(BUCKET_ID, newIds) + return apiResponse.pipe( + tap(() => { + this.addIdsToSelected(newIds) + }), + map(() => undefined) + ) + } + + deselectRecord(records: CatalogRecord[]) {} + + clearSelection() {} +} From 0c138c3d1107dce136bc371706ea77e2569f3841 Mon Sep 17 00:00:00 2001 From: Angelika Kinas Date: Wed, 1 Nov 2023 08:45:10 +0100 Subject: [PATCH 2/6] feat(libs): Complete selection service with clear and deselect --- .../gn4/selection/selection.service.spec.ts | 44 ++++++++++++++++++- .../lib/gn4/selection/selection.service.ts | 41 +++++++++++++++-- 2 files changed, 79 insertions(+), 6 deletions(-) diff --git a/libs/api/repository/src/lib/gn4/selection/selection.service.spec.ts b/libs/api/repository/src/lib/gn4/selection/selection.service.spec.ts index a33f213575..36300825a6 100644 --- a/libs/api/repository/src/lib/gn4/selection/selection.service.spec.ts +++ b/libs/api/repository/src/lib/gn4/selection/selection.service.spec.ts @@ -59,9 +59,49 @@ describe('SelectionService', () => { }) }) - // describe('#deselectRecord', () => {}) + describe('#deselectRecord', () => { + let selectedRecords + beforeEach(async () => { + service.selectedRecordsIdentifiers$.subscribe((value) => { + selectedRecords = value + }) + await firstValueFrom( + service.deselectRecord([record('abcd'), record('efgh'), record('001')]) + ) + }) + it('calls the corresponding API', () => { + expect(selectionsService.clear).toHaveBeenCalledWith('gnui', [ + 'abcd', + 'efgh', + '001', + ]) + }) + it('emits new records in selectedRecordsIdentifiers$', () => { + expect(selectedRecords).toEqual(['002', '003']) + }) + }) - // describe('#clearSelection', () => {}) + describe('#clearSelection', () => { + let selectedRecords + beforeEach(async () => { + service.selectedRecordsIdentifiers$.subscribe((value) => { + selectedRecords = value + }) + await firstValueFrom(service.clearSelection()) + }) + it('calls the corresponding API', () => { + expect(selectionsService.get).toHaveBeenCalledWith('gnui') + + expect(selectionsService.clear).toHaveBeenCalledWith('gnui', [ + '001', + '002', + '003', + ]) + }) + it('emits new records in selectedRecordsIdentifiers$', () => { + expect(selectedRecords).toEqual([]) + }) + }) // describe('selectedRecordsIdentifiers$', () => {}) }) diff --git a/libs/api/repository/src/lib/gn4/selection/selection.service.ts b/libs/api/repository/src/lib/gn4/selection/selection.service.ts index 88db88fd49..206df858a7 100644 --- a/libs/api/repository/src/lib/gn4/selection/selection.service.ts +++ b/libs/api/repository/src/lib/gn4/selection/selection.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core' import { CatalogRecord } from '@geonetwork-ui/common/domain/record' import { SelectionsApiService } from '@geonetwork-ui/data-access/gn4' -import { BehaviorSubject, Observable, map, tap } from 'rxjs' +import { BehaviorSubject, Observable, Subscription, map, tap } from 'rxjs' const BUCKET_ID = 'gnui' @@ -12,6 +12,7 @@ export class SelectionService { selectedRecordsIdentifiers$: BehaviorSubject = new BehaviorSubject( [] ) + subscription: Subscription constructor(private selectionsApi: SelectionsApiService) { this.selectionsApi.get(BUCKET_ID).subscribe((selectedIds) => { @@ -25,7 +26,12 @@ export class SelectionService { this.selectedRecordsIdentifiers$.next([...uniqueSet]) } - private removeIdsFromSelected(ids: string[]) {} + private removeIdsFromSelected(ids: string[]) { + const filtered = this.selectedRecordsIdentifiers$.value.filter( + (value) => !ids.includes(value) + ) + this.selectedRecordsIdentifiers$.next(filtered) + } selectRecords(records: CatalogRecord[]): Observable { const newIds = [] @@ -41,7 +47,34 @@ export class SelectionService { ) } - deselectRecord(records: CatalogRecord[]) {} + deselectRecord(records: CatalogRecord[]): Observable { + const idsToBeRemoved = [] + records.map((record) => { + idsToBeRemoved.push(record.uniqueIdentifier) + }) + const apiResponse = this.selectionsApi.clear(BUCKET_ID, idsToBeRemoved) + return apiResponse.pipe( + tap(() => { + this.removeIdsFromSelected(idsToBeRemoved) + }), + map(() => undefined) + ) + } - clearSelection() {} + clearSelection(): Observable { + const currentSelectedResponse = this.selectionsApi.get(BUCKET_ID) + let apiResponse + let currentSelection + this.subscription = currentSelectedResponse.subscribe((value) => { + currentSelection = [...value] + this.selectionsApi.clear(BUCKET_ID, currentSelection) + apiResponse = this.selectionsApi.clear(BUCKET_ID, currentSelection) + }) + return apiResponse.pipe( + tap(() => { + this.removeIdsFromSelected(currentSelection) + }), + map(() => undefined) + ) + } } From bb0ea8f95b176efacfe3758b490d57da14e4a742 Mon Sep 17 00:00:00 2001 From: Angelika Kinas Date: Thu, 2 Nov 2023 09:03:02 +0100 Subject: [PATCH 3/6] feat(ME): Use selection service in record table --- .../app/records/records-list.component.html | 1 + .../src/app/records/records-list.component.ts | 8 +++- libs/api/repository/src/lib/gn4/index.ts | 1 + .../gn4/selection/selection.service.spec.ts | 2 +- .../lib/gn4/selection/selection.service.ts | 8 ++-- .../record-table.component.spec.ts | 40 +++++++++++++++++-- .../record-table/record-table.component.ts | 38 ++++++++++++++++-- 7 files changed, 85 insertions(+), 13 deletions(-) diff --git a/apps/metadata-editor/src/app/records/records-list.component.html b/apps/metadata-editor/src/app/records/records-list.component.html index ef2f49181f..8b0c891f0d 100644 --- a/apps/metadata-editor/src/app/records/records-list.component.html +++ b/apps/metadata-editor/src/app/records/records-list.component.html @@ -66,6 +66,7 @@

(recordSelect)="editRecord($event)" (sortByChange)="setSortBy($event)" [sortBy]="searchFacade.sortBy$ | async" + [selectedRecords]="getSelectedRecords() | async" >
{ selectedRecords = value }) await firstValueFrom( - service.deselectRecord([record('abcd'), record('efgh'), record('001')]) + service.deselectRecords([record('abcd'), record('efgh'), record('001')]) ) }) it('calls the corresponding API', () => { diff --git a/libs/api/repository/src/lib/gn4/selection/selection.service.ts b/libs/api/repository/src/lib/gn4/selection/selection.service.ts index 206df858a7..20113654a7 100644 --- a/libs/api/repository/src/lib/gn4/selection/selection.service.ts +++ b/libs/api/repository/src/lib/gn4/selection/selection.service.ts @@ -47,7 +47,7 @@ export class SelectionService { ) } - deselectRecord(records: CatalogRecord[]): Observable { + deselectRecords(records: CatalogRecord[]): Observable { const idsToBeRemoved = [] records.map((record) => { idsToBeRemoved.push(record.uniqueIdentifier) @@ -63,13 +63,13 @@ export class SelectionService { clearSelection(): Observable { const currentSelectedResponse = this.selectionsApi.get(BUCKET_ID) - let apiResponse let currentSelection this.subscription = currentSelectedResponse.subscribe((value) => { currentSelection = [...value] - this.selectionsApi.clear(BUCKET_ID, currentSelection) - apiResponse = this.selectionsApi.clear(BUCKET_ID, currentSelection) }) + this.selectionsApi.clear(BUCKET_ID, currentSelection) + const apiResponse = this.selectionsApi.clear(BUCKET_ID, currentSelection) + return apiResponse.pipe( tap(() => { this.removeIdsFromSelected(currentSelection) diff --git a/libs/ui/search/src/lib/record-table/record-table.component.spec.ts b/libs/ui/search/src/lib/record-table/record-table.component.spec.ts index a043f39804..f7319a8cee 100644 --- a/libs/ui/search/src/lib/record-table/record-table.component.spec.ts +++ b/libs/ui/search/src/lib/record-table/record-table.component.spec.ts @@ -4,6 +4,23 @@ import { DATASET_RECORDS } from '@geonetwork-ui/common/fixtures' import { RecordTableComponent } from './record-table.component' import { SortByField } from '@geonetwork-ui/common/domain/search' import { CatalogRecord } from '@geonetwork-ui/common/domain/record' +import { of } from 'rxjs' +import { SelectionService } from '@geonetwork-ui/api/repository/gn4' + +class SelectionsApiMock { + private selected = ['001', '002', '003'] + add = jest.fn((bucket, ids) => { + this.selected.push(...ids) + return of(undefined) + }) + clear = jest.fn((bucket, ids) => { + this.selected = this.selected.filter( + (id) => !!ids && ids.indexOf(id) === -1 + ) + return of(undefined) + }) + get = jest.fn(() => of(this.selected)) +} describe('RecordTableComponent', () => { let component: RecordTableComponent @@ -12,6 +29,12 @@ describe('RecordTableComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [RecordTableComponent], + providers: [ + { + provide: SelectionService, + useValue: SelectionService, + }, + ], }).compileComponents() fixture = TestBed.createComponent(RecordTableComponent) @@ -83,16 +106,22 @@ describe('RecordTableComponent', () => { }) }) - describe('isChecked', () => { + describe('#isChecked', () => { it('should return true when the record is in the selectedRecords array', () => { - const component = new RecordTableComponent() + const selectionServiceApiMock = new SelectionsApiMock() as any + const component = new RecordTableComponent( + new SelectionService(selectionServiceApiMock) + ) component.selectedRecords = ['1', '2', '3'] const record = { uniqueIdentifier: '2' } as any as CatalogRecord expect(component.isChecked(record)).toBe(true) }) it('should return false when the record is not in the selectedRecords array', () => { - const component = new RecordTableComponent() + const selectionServiceApiMock = new SelectionsApiMock() as any + const component = new RecordTableComponent( + new SelectionService(selectionServiceApiMock) + ) component.selectedRecords = ['1', '2', '3'] const record = { uniqueIdentifier: '4' } as any as CatalogRecord expect(component.isChecked(record)).toBe(false) @@ -101,7 +130,10 @@ describe('RecordTableComponent', () => { describe('#handleRecordSelectedChange', () => { it('should add unique identifier to selectedRecords when checkbox is clicked for a record that is not already selected', () => { - const component = new RecordTableComponent() + const selectionServiceApiMock = new SelectionsApiMock() as any + const component = new RecordTableComponent( + new SelectionService(selectionServiceApiMock) + ) const record = { uniqueIdentifier: '1' } component.selectedRecords = [] diff --git a/libs/ui/search/src/lib/record-table/record-table.component.ts b/libs/ui/search/src/lib/record-table/record-table.component.ts index 6cdf956f42..9c7fd621f0 100644 --- a/libs/ui/search/src/lib/record-table/record-table.component.ts +++ b/libs/ui/search/src/lib/record-table/record-table.component.ts @@ -1,4 +1,10 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core' +import { + Component, + EventEmitter, + Input, + OnDestroy, + Output, +} from '@angular/core' import { CatalogRecord } from '@geonetwork-ui/common/domain/record' import { FileFormat, @@ -7,21 +13,26 @@ import { getFormatPriority, } from '@geonetwork-ui/util/shared' import { SortByField } from '@geonetwork-ui/common/domain/search' +import { SelectionService } from '@geonetwork-ui/api/repository/gn4' +import { Subject, takeUntil } from 'rxjs' @Component({ selector: 'gn-ui-record-table', templateUrl: './record-table.component.html', styleUrls: ['./record-table.component.css'], }) -export class RecordTableComponent { - selectedRecords: string[] = [] +export class RecordTableComponent implements OnDestroy { + private onDestroy$: Subject = new Subject() + @Input() selectedRecords: string[] = [] @Input() records: any[] = [] @Input() totalHits?: number @Input() sortBy?: SortByField @Output() recordSelect = new EventEmitter() @Output() sortByChange = new EventEmitter() + constructor(private selectionService: SelectionService) {} + dateToString(date: Date): string { return date?.toLocaleDateString(undefined, { year: 'numeric', @@ -105,18 +116,34 @@ export class RecordTableComponent { this.selectedRecords = this.selectedRecords.filter( (val) => val !== record.uniqueIdentifier ) + this.selectionService + .deselectRecords([record]) + .pipe(takeUntil(this.onDestroy$)) + .subscribe() } else { this.selectedRecords.push(record.uniqueIdentifier) + this.selectionService + .selectRecords([record]) + .pipe(takeUntil(this.onDestroy$)) + .subscribe() } } selectAll() { if (this.isAllSelected()) { this.selectedRecords = [] + this.selectionService + .clearSelection() + .pipe(takeUntil(this.onDestroy$)) + .subscribe() } else { this.selectedRecords = this.records.map( (record) => record.uniqueIdentifier ) + this.selectionService + .selectRecords(this.records) + .pipe(takeUntil(this.onDestroy$)) + .subscribe() } } @@ -141,4 +168,9 @@ export class RecordTableComponent { } return false } + + ngOnDestroy(): void { + this.onDestroy$.next() + this.onDestroy$.complete() + } } From 5972e3b1c751d5dff32df2b788d0753a98b46d11 Mon Sep 17 00:00:00 2001 From: Angelika Kinas Date: Mon, 6 Nov 2023 16:15:28 +0100 Subject: [PATCH 4/6] [ME]: Move logic from ui component to ME-records-list --- .../app/records/records-list.component.html | 3 +- .../records/records-list.component.spec.ts | 19 +++++++ .../src/app/records/records-list.component.ts | 26 +++++++++- .../gn4/selection/selection.service.spec.ts | 2 +- .../record-table.component.spec.ts | 38 ++------------ .../record-table/record-table.component.ts | 49 +++++-------------- 6 files changed, 60 insertions(+), 77 deletions(-) diff --git a/apps/metadata-editor/src/app/records/records-list.component.html b/apps/metadata-editor/src/app/records/records-list.component.html index 8b0c891f0d..a3eac3e2ef 100644 --- a/apps/metadata-editor/src/app/records/records-list.component.html +++ b/apps/metadata-editor/src/app/records/records-list.component.html @@ -14,7 +14,6 @@

{{ title }}

- np

dashboard.records.users

@@ -64,6 +63,8 @@

[records]="results" [totalHits]="users ? users.length : (searchFacade.resultsHits$ | async)" (recordSelect)="editRecord($event)" + (recordsDeselection)="handleRecordsDeselection($event)" + (recordsSelection)="handleRecordsSelection($event)" (sortByChange)="setSortBy($event)" [sortBy]="searchFacade.sortBy$ | async" [selectedRecords]="getSelectedRecords() | async" diff --git a/apps/metadata-editor/src/app/records/records-list.component.spec.ts b/apps/metadata-editor/src/app/records/records-list.component.spec.ts index 62c728488c..e3a11d2b60 100644 --- a/apps/metadata-editor/src/app/records/records-list.component.spec.ts +++ b/apps/metadata-editor/src/app/records/records-list.component.spec.ts @@ -8,6 +8,7 @@ import { Router } from '@angular/router' import { BehaviorSubject } from 'rxjs' import { CommonModule } from '@angular/common' import { MatIconModule } from '@angular/material/icon' +import { SelectionService } from '@geonetwork-ui/api/repository/gn4' const results = [{ md: true }] const currentPage = 5 @@ -23,6 +24,7 @@ export class RecordTableComponent { @Input() records: CatalogRecord[] @Input() totalHits: number @Output() recordSelect = new EventEmitter() + @Output() recordsSelection = new EventEmitter() } @Component({ @@ -55,12 +57,19 @@ class RouterMock { navigate = jest.fn() } +class SelectionServiceMock { + selectRecords = jest.fn() + deselectRecords = jest.fn() + clearSelection = jest.fn() +} + describe('RecordsListComponent', () => { let component: RecordsListComponent let fixture: ComponentFixture let router: Router let searchService: SearchService let searchFacade: SearchFacade + let selectionService: SelectionService beforeEach(() => { TestBed.configureTestingModule({ @@ -77,6 +86,10 @@ describe('RecordsListComponent', () => { provide: SearchService, useClass: SearchServiceMock, }, + { + provide: SelectionService, + useClass: SelectionServiceMock, + }, ], }).overrideComponent(RecordsListComponent, { set: { @@ -90,6 +103,7 @@ describe('RecordsListComponent', () => { }) router = TestBed.inject(Router) searchService = TestBed.inject(SearchService) + selectionService = TestBed.inject(SelectionService) searchFacade = TestBed.inject(SearchFacade) fixture = TestBed.createComponent(RecordsListComponent) component = fixture.componentInstance @@ -121,10 +135,15 @@ describe('RecordsListComponent', () => { describe('when click on a record', () => { beforeEach(() => { table.recordSelect.emit({ uniqueIdentifier: 123 }) + table.recordsSelection.emit([{ uniqueIdentifier: 123 }]) }) it('routes to record edition', () => { expect(router.navigate).toHaveBeenCalledWith(['/edit', 123]) }) + + it('persists selection', () => { + expect(selectionService.selectRecords).toHaveBeenCalled() + }) }) describe('when click on pagination', () => { beforeEach(() => { diff --git a/apps/metadata-editor/src/app/records/records-list.component.ts b/apps/metadata-editor/src/app/records/records-list.component.ts index a2c893a0af..316bb56096 100644 --- a/apps/metadata-editor/src/app/records/records-list.component.ts +++ b/apps/metadata-editor/src/app/records/records-list.component.ts @@ -1,5 +1,5 @@ import { CommonModule } from '@angular/common' -import { Component, Input } from '@angular/core' +import { Component, Input, OnDestroy } from '@angular/core' import { MatIconModule } from '@angular/material/icon' import { Router } from '@angular/router' import { CatalogRecord } from '@geonetwork-ui/common/domain/record' @@ -9,6 +9,7 @@ import { UiElementsModule } from '@geonetwork-ui/ui/elements' import { SortByField } from '@geonetwork-ui/common/domain/search' import { TranslateModule } from '@ngx-translate/core' import { SelectionService } from '@geonetwork-ui/api/repository/gn4' +import { Subject, takeUntil } from 'rxjs' const includes = [ 'uuid', @@ -35,7 +36,7 @@ const includes = [ TranslateModule, ], }) -export class RecordsListComponent { +export class RecordsListComponent implements OnDestroy { @Input() title: string @Input() logo: string @Input() recordCount: number @@ -43,6 +44,8 @@ export class RecordsListComponent { @Input() users @Input() linkToDatahub?: string + private onDestroy$: Subject = new Subject() + constructor( private router: Router, public searchFacade: SearchFacade, @@ -78,4 +81,23 @@ export class RecordsListComponent { getSelectedRecords() { return this.selectionService.selectedRecordsIdentifiers$ } + + handleRecordsSelection(records: CatalogRecord[]) { + this.selectionService + .selectRecords(records) + .pipe(takeUntil(this.onDestroy$)) + .subscribe() + } + + handleRecordsDeselection(records: CatalogRecord[]) { + this.selectionService + .deselectRecords(records) + .pipe(takeUntil(this.onDestroy$)) + .subscribe() + } + + ngOnDestroy(): void { + this.onDestroy$.next() + this.onDestroy$.complete() + } } diff --git a/libs/api/repository/src/lib/gn4/selection/selection.service.spec.ts b/libs/api/repository/src/lib/gn4/selection/selection.service.spec.ts index 58f2ac4d7a..76dfe03241 100644 --- a/libs/api/repository/src/lib/gn4/selection/selection.service.spec.ts +++ b/libs/api/repository/src/lib/gn4/selection/selection.service.spec.ts @@ -59,7 +59,7 @@ describe('SelectionService', () => { }) }) - describe('#deselectRecord', () => { + describe('#deselectRecords', () => { let selectedRecords beforeEach(async () => { service.selectedRecordsIdentifiers$.subscribe((value) => { diff --git a/libs/ui/search/src/lib/record-table/record-table.component.spec.ts b/libs/ui/search/src/lib/record-table/record-table.component.spec.ts index f7319a8cee..3b8b8ce869 100644 --- a/libs/ui/search/src/lib/record-table/record-table.component.spec.ts +++ b/libs/ui/search/src/lib/record-table/record-table.component.spec.ts @@ -4,23 +4,6 @@ import { DATASET_RECORDS } from '@geonetwork-ui/common/fixtures' import { RecordTableComponent } from './record-table.component' import { SortByField } from '@geonetwork-ui/common/domain/search' import { CatalogRecord } from '@geonetwork-ui/common/domain/record' -import { of } from 'rxjs' -import { SelectionService } from '@geonetwork-ui/api/repository/gn4' - -class SelectionsApiMock { - private selected = ['001', '002', '003'] - add = jest.fn((bucket, ids) => { - this.selected.push(...ids) - return of(undefined) - }) - clear = jest.fn((bucket, ids) => { - this.selected = this.selected.filter( - (id) => !!ids && ids.indexOf(id) === -1 - ) - return of(undefined) - }) - get = jest.fn(() => of(this.selected)) -} describe('RecordTableComponent', () => { let component: RecordTableComponent @@ -29,12 +12,6 @@ describe('RecordTableComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [RecordTableComponent], - providers: [ - { - provide: SelectionService, - useValue: SelectionService, - }, - ], }).compileComponents() fixture = TestBed.createComponent(RecordTableComponent) @@ -108,20 +85,14 @@ describe('RecordTableComponent', () => { describe('#isChecked', () => { it('should return true when the record is in the selectedRecords array', () => { - const selectionServiceApiMock = new SelectionsApiMock() as any - const component = new RecordTableComponent( - new SelectionService(selectionServiceApiMock) - ) + const component = new RecordTableComponent() component.selectedRecords = ['1', '2', '3'] const record = { uniqueIdentifier: '2' } as any as CatalogRecord expect(component.isChecked(record)).toBe(true) }) it('should return false when the record is not in the selectedRecords array', () => { - const selectionServiceApiMock = new SelectionsApiMock() as any - const component = new RecordTableComponent( - new SelectionService(selectionServiceApiMock) - ) + const component = new RecordTableComponent() component.selectedRecords = ['1', '2', '3'] const record = { uniqueIdentifier: '4' } as any as CatalogRecord expect(component.isChecked(record)).toBe(false) @@ -130,10 +101,7 @@ describe('RecordTableComponent', () => { describe('#handleRecordSelectedChange', () => { it('should add unique identifier to selectedRecords when checkbox is clicked for a record that is not already selected', () => { - const selectionServiceApiMock = new SelectionsApiMock() as any - const component = new RecordTableComponent( - new SelectionService(selectionServiceApiMock) - ) + const component = new RecordTableComponent() const record = { uniqueIdentifier: '1' } component.selectedRecords = [] diff --git a/libs/ui/search/src/lib/record-table/record-table.component.ts b/libs/ui/search/src/lib/record-table/record-table.component.ts index 9c7fd621f0..b2d28b50d7 100644 --- a/libs/ui/search/src/lib/record-table/record-table.component.ts +++ b/libs/ui/search/src/lib/record-table/record-table.component.ts @@ -1,10 +1,4 @@ -import { - Component, - EventEmitter, - Input, - OnDestroy, - Output, -} from '@angular/core' +import { Component, EventEmitter, Input, Output } from '@angular/core' import { CatalogRecord } from '@geonetwork-ui/common/domain/record' import { FileFormat, @@ -13,26 +7,22 @@ import { getFormatPriority, } from '@geonetwork-ui/util/shared' import { SortByField } from '@geonetwork-ui/common/domain/search' -import { SelectionService } from '@geonetwork-ui/api/repository/gn4' -import { Subject, takeUntil } from 'rxjs' @Component({ selector: 'gn-ui-record-table', templateUrl: './record-table.component.html', styleUrls: ['./record-table.component.css'], }) -export class RecordTableComponent implements OnDestroy { - private onDestroy$: Subject = new Subject() - +export class RecordTableComponent { @Input() selectedRecords: string[] = [] @Input() records: any[] = [] @Input() totalHits?: number @Input() sortBy?: SortByField @Output() recordSelect = new EventEmitter() + @Output() recordsSelection = new EventEmitter() + @Output() recordsDeselection = new EventEmitter() @Output() sortByChange = new EventEmitter() - constructor(private selectionService: SelectionService) {} - dateToString(date: Date): string { return date?.toLocaleDateString(undefined, { year: 'numeric', @@ -113,37 +103,25 @@ export class RecordTableComponent implements OnDestroy { handleRecordSelectedChange(selected: boolean, record: CatalogRecord) { if (!selected) { + this.recordsDeselection.emit([record]) this.selectedRecords = this.selectedRecords.filter( (val) => val !== record.uniqueIdentifier ) - this.selectionService - .deselectRecords([record]) - .pipe(takeUntil(this.onDestroy$)) - .subscribe() } else { + this.recordsSelection.emit([record]) this.selectedRecords.push(record.uniqueIdentifier) - this.selectionService - .selectRecords([record]) - .pipe(takeUntil(this.onDestroy$)) - .subscribe() } } selectAll() { if (this.isAllSelected()) { + this.recordsDeselection.emit(this.records) this.selectedRecords = [] - this.selectionService - .clearSelection() - .pipe(takeUntil(this.onDestroy$)) - .subscribe() } else { - this.selectedRecords = this.records.map( - (record) => record.uniqueIdentifier - ) - this.selectionService - .selectRecords(this.records) - .pipe(takeUntil(this.onDestroy$)) - .subscribe() + this.recordsSelection.emit(this.records) + this.selectedRecords = this.records.map((record) => { + return record.uniqueIdentifier + }) } } @@ -168,9 +146,4 @@ export class RecordTableComponent implements OnDestroy { } return false } - - ngOnDestroy(): void { - this.onDestroy$.next() - this.onDestroy$.complete() - } } From 4430fb955ba5d021ce1dcd3791a659bf753ac218 Mon Sep 17 00:00:00 2001 From: Angelika Kinas Date: Mon, 13 Nov 2023 11:38:55 +0100 Subject: [PATCH 5/6] feat(libs, ME): Change naming, organise test --- .../app/records/records-list.component.html | 6 ++-- .../records/records-list.component.spec.ts | 30 ++++++++++++++----- .../src/app/records/records-list.component.ts | 23 ++++---------- .../record-table/record-table.component.ts | 14 ++++----- 4 files changed, 37 insertions(+), 36 deletions(-) diff --git a/apps/metadata-editor/src/app/records/records-list.component.html b/apps/metadata-editor/src/app/records/records-list.component.html index a3eac3e2ef..d30ab13252 100644 --- a/apps/metadata-editor/src/app/records/records-list.component.html +++ b/apps/metadata-editor/src/app/records/records-list.component.html @@ -62,9 +62,9 @@

() - @Output() recordsSelection = new EventEmitter() + @Output() recordClick = new EventEmitter() + @Output() recordsSelect = new EventEmitter() } @Component({ @@ -68,7 +69,6 @@ describe('RecordsListComponent', () => { let fixture: ComponentFixture let router: Router let searchService: SearchService - let searchFacade: SearchFacade let selectionService: SelectionService beforeEach(() => { @@ -104,7 +104,6 @@ describe('RecordsListComponent', () => { router = TestBed.inject(Router) searchService = TestBed.inject(SearchService) selectionService = TestBed.inject(SelectionService) - searchFacade = TestBed.inject(SearchFacade) fixture = TestBed.createComponent(RecordsListComponent) component = fixture.componentInstance fixture.detectChanges() @@ -133,16 +132,31 @@ describe('RecordsListComponent', () => { expect(pagination.totalPages).toEqual(totalPages) }) describe('when click on a record', () => { + const uniqueIdentifier = 123 + const singleRecord = { + ...DATASET_RECORDS[0], + uniqueIdentifier, + } beforeEach(() => { - table.recordSelect.emit({ uniqueIdentifier: 123 }) - table.recordsSelection.emit([{ uniqueIdentifier: 123 }]) + table.recordClick.emit(singleRecord) }) it('routes to record edition', () => { expect(router.navigate).toHaveBeenCalledWith(['/edit', 123]) }) - + }) + describe('when selecting a record', () => { + const uniqueIdentifier = 123 + const singleRecord = { + ...DATASET_RECORDS[0], + uniqueIdentifier, + } + beforeEach(() => { + table.recordsSelect.emit([singleRecord]) + }) it('persists selection', () => { - expect(selectionService.selectRecords).toHaveBeenCalled() + expect(selectionService.selectRecords).toHaveBeenCalledWith([ + singleRecord, + ]) }) }) describe('when click on pagination', () => { diff --git a/apps/metadata-editor/src/app/records/records-list.component.ts b/apps/metadata-editor/src/app/records/records-list.component.ts index 316bb56096..4e5c033613 100644 --- a/apps/metadata-editor/src/app/records/records-list.component.ts +++ b/apps/metadata-editor/src/app/records/records-list.component.ts @@ -1,5 +1,5 @@ import { CommonModule } from '@angular/common' -import { Component, Input, OnDestroy } from '@angular/core' +import { Component, Input } from '@angular/core' import { MatIconModule } from '@angular/material/icon' import { Router } from '@angular/router' import { CatalogRecord } from '@geonetwork-ui/common/domain/record' @@ -9,7 +9,7 @@ import { UiElementsModule } from '@geonetwork-ui/ui/elements' import { SortByField } from '@geonetwork-ui/common/domain/search' import { TranslateModule } from '@ngx-translate/core' import { SelectionService } from '@geonetwork-ui/api/repository/gn4' -import { Subject, takeUntil } from 'rxjs' +import { Subject } from 'rxjs' const includes = [ 'uuid', @@ -36,7 +36,7 @@ const includes = [ TranslateModule, ], }) -export class RecordsListComponent implements OnDestroy { +export class RecordsListComponent { @Input() title: string @Input() logo: string @Input() recordCount: number @@ -44,8 +44,6 @@ export class RecordsListComponent implements OnDestroy { @Input() users @Input() linkToDatahub?: string - private onDestroy$: Subject = new Subject() - constructor( private router: Router, public searchFacade: SearchFacade, @@ -83,21 +81,10 @@ export class RecordsListComponent implements OnDestroy { } handleRecordsSelection(records: CatalogRecord[]) { - this.selectionService - .selectRecords(records) - .pipe(takeUntil(this.onDestroy$)) - .subscribe() + this.selectionService.selectRecords(records).subscribe() } handleRecordsDeselection(records: CatalogRecord[]) { - this.selectionService - .deselectRecords(records) - .pipe(takeUntil(this.onDestroy$)) - .subscribe() - } - - ngOnDestroy(): void { - this.onDestroy$.next() - this.onDestroy$.complete() + this.selectionService.deselectRecords(records).subscribe() } } diff --git a/libs/ui/search/src/lib/record-table/record-table.component.ts b/libs/ui/search/src/lib/record-table/record-table.component.ts index b2d28b50d7..ddae7d03ce 100644 --- a/libs/ui/search/src/lib/record-table/record-table.component.ts +++ b/libs/ui/search/src/lib/record-table/record-table.component.ts @@ -18,9 +18,9 @@ export class RecordTableComponent { @Input() records: any[] = [] @Input() totalHits?: number @Input() sortBy?: SortByField - @Output() recordSelect = new EventEmitter() - @Output() recordsSelection = new EventEmitter() - @Output() recordsDeselection = new EventEmitter() + @Output() recordClick = new EventEmitter() + @Output() recordsSelect = new EventEmitter() + @Output() recordsDeselect = new EventEmitter() @Output() sortByChange = new EventEmitter() dateToString(date: Date): string { @@ -103,22 +103,22 @@ export class RecordTableComponent { handleRecordSelectedChange(selected: boolean, record: CatalogRecord) { if (!selected) { - this.recordsDeselection.emit([record]) + this.recordsDeselect.emit([record]) this.selectedRecords = this.selectedRecords.filter( (val) => val !== record.uniqueIdentifier ) } else { - this.recordsSelection.emit([record]) + this.recordsSelect.emit([record]) this.selectedRecords.push(record.uniqueIdentifier) } } selectAll() { if (this.isAllSelected()) { - this.recordsDeselection.emit(this.records) + this.recordsDeselect.emit(this.records) this.selectedRecords = [] } else { - this.recordsSelection.emit(this.records) + this.recordsSelect.emit(this.records) this.selectedRecords = this.records.map((record) => { return record.uniqueIdentifier }) From d7a3c820d7f7d3ae92cc0d6c4574531907ca256d Mon Sep 17 00:00:00 2001 From: Angelika Kinas Date: Mon, 13 Nov 2023 11:42:15 +0100 Subject: [PATCH 6/6] feat(libs): Remove unnessecary test --- .../repository/src/lib/gn4/selection/selection.service.spec.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/libs/api/repository/src/lib/gn4/selection/selection.service.spec.ts b/libs/api/repository/src/lib/gn4/selection/selection.service.spec.ts index 76dfe03241..8df5a3f655 100644 --- a/libs/api/repository/src/lib/gn4/selection/selection.service.spec.ts +++ b/libs/api/repository/src/lib/gn4/selection/selection.service.spec.ts @@ -102,6 +102,4 @@ describe('SelectionService', () => { expect(selectedRecords).toEqual([]) }) }) - - // describe('selectedRecordsIdentifiers$', () => {}) })