From 909d9cb349014e6e555e6534958ffd249de59bfa Mon Sep 17 00:00:00 2001 From: Angelika Kinas Date: Mon, 9 Sep 2024 12:06:18 +0200 Subject: [PATCH 1/3] chore(keywords): filter out place keyword in keyword section of metadata editor, add e2e test for place keywords and other keywords --- apps/metadata-editor-e2e/src/e2e/edit.cy.ts | 58 ++++++++++++++ .../form-field-keywords.component.html | 2 +- .../form-field-keywords.component.spec.ts | 79 ++++++++++++++++++- .../form-field-keywords.component.ts | 18 ++++- 4 files changed, 152 insertions(+), 5 deletions(-) diff --git a/apps/metadata-editor-e2e/src/e2e/edit.cy.ts b/apps/metadata-editor-e2e/src/e2e/edit.cy.ts index 0a5ec22ce2..2d913db377 100644 --- a/apps/metadata-editor-e2e/src/e2e/edit.cy.ts +++ b/apps/metadata-editor-e2e/src/e2e/edit.cy.ts @@ -258,6 +258,64 @@ describe('editor form', () => { .eq(0) .should('not.have.class', 'mat-button-toggle-checked') }) + + it('should display place keywords', () => { + cy.get('gn-ui-autocomplete').should('have.length', 1) + cy.get('gn-ui-autocomplete').type('a') + cy.get('mat-option').should('have.length', 10) + cy.get('mat-option').eq(0).click() + cy.get('gn-ui-badge').should('have.length', 4) + cy.get('gn-ui-badge') + .eq(0) + .find('span') + .should('have.text', 'Adriatic Sea ') + }) + }) + }) + + describe('Keywords', () => { + beforeEach(() => { + cy.get('@accessAndContactPageSelectorButton').click() + }) + + it('should display keywords without place keywords', () => { + cy.get('gn-ui-form-field-keywords') + .find('gn-ui-badge') + .should('have.length', 19) + cy.get('gn-ui-form-field-keywords') + .find('gn-ui-badge') + .find('span') + .each(($span) => { + cy.wrap($span).should('not.have.text', 'Adriatic Sea ') + }) + }) + + it('should add a keyword', () => { + cy.get('gn-ui-form-field-keywords') + .find('gn-ui-autocomplete') + .should('have.length', 1) + cy.get('gn-ui-form-field-keywords').find('gn-ui-autocomplete').click() + cy.get('mat-option').should('have.length', 10) + cy.get('mat-option').eq(0).click() + cy.get('gn-ui-badge').should('have.length', 20) + cy.get('gn-ui-badge') + .eq(19) + .find('span') + .should('have.text', 'Addresses ') + }) + + it('should remove a keyword', () => { + cy.get('gn-ui-form-field-keywords') + .find('gn-ui-badge') + .should('have.length', 19) + cy.get('gn-ui-form-field-keywords') + .find('gn-ui-badge') + .eq(0) + .find('button') + .click() + cy.get('gn-ui-form-field-keywords') + .find('gn-ui-badge') + .should('have.length', 18) }) }) diff --git a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-keywords/form-field-keywords.component.html b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-keywords/form-field-keywords.component.html index 4e94b74a75..7db0e989f0 100644 --- a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-keywords/form-field-keywords.component.html +++ b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-keywords/form-field-keywords.component.html @@ -1,6 +1,6 @@
of([{ label: 'Address', thesaurus: { id: '1' } }]) ) } + +class EditorFacadeMock { + record$ = of({ keywords: [...placeKeywords, ...otherKeywords] }) +} + describe('FormFieldKeywordsComponent', () => { let component: FormFieldKeywordsComponent let fixture: ComponentFixture @@ -36,15 +82,44 @@ describe('FormFieldKeywordsComponent', () => { provide: PlatformServiceInterface, useClass: PlatformServiceInterfaceMock, }, + { + provide: EditorFacade, + useClass: EditorFacadeMock, + }, ], }) fixture = TestBed.createComponent(FormFieldKeywordsComponent) component = fixture.componentInstance - component.control = new FormControl() fixture.detectChanges() }) + beforeEach(() => { + return MockBuilder(FormFieldKeywordsComponent) + }) + it('should create', () => { expect(component).toBeTruthy() }) + + it('should filter out place keywords', () => { + component.value = [...placeKeywords, ...otherKeywords] + + expect(component.filteredKeywords).toEqual(otherKeywords) + }) + + it('should emit all keywords (place and other) on change', async () => { + const newKeyword: Keyword = { + label: 'New keyword', + thesaurus: { id: '6' }, + type: 'theme', + } + otherKeywords.push(newKeyword) + const valueChangeSpy = jest.spyOn(component.valueChange, 'emit') + await component.handleKeywordsChange([...otherKeywords]) + + expect(valueChangeSpy).toHaveBeenCalledWith([ + ...otherKeywords, + ...placeKeywords, + ]) + }) }) diff --git a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-keywords/form-field-keywords.component.ts b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-keywords/form-field-keywords.component.ts index c0f574eddc..e2a6bca1fb 100644 --- a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-keywords/form-field-keywords.component.ts +++ b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-keywords/form-field-keywords.component.ts @@ -16,6 +16,8 @@ import { Keyword } from '@geonetwork-ui/common/domain/model/record' import { GenericKeywordsComponent } from '../../../generic-keywords/generic-keywords.component' import { TranslateModule } from '@ngx-translate/core' import { KeywordType } from '@geonetwork-ui/common/domain/model/thesaurus' +import { EditorFacade } from '../../../../+state/editor.facade' +import { firstValueFrom, map } from 'rxjs' @Component({ selector: 'gn-ui-form-field-keywords', @@ -40,7 +42,19 @@ export class FormFieldKeywordsComponent { keywordTypes = ['temporal', 'theme', 'other'] as KeywordType[] placeholder = 'editor.form.keywords.placeholder' - handleKeywordsChange(keywords: Keyword[]) { - this.valueChange.emit(keywords) + get filteredKeywords(): Keyword[] { + return this.value?.filter((keyword) => keyword.type !== 'place') || [] + } + + constructor(private editorFacade: EditorFacade) {} + + async handleKeywordsChange(keywords: Keyword[]) { + const placeKeywords = await firstValueFrom( + this.editorFacade.record$.pipe( + map((record) => record.keywords.filter((k) => k.type == 'place')) + ) + ) + + this.valueChange.emit([...keywords, ...placeKeywords]) } } From 679dab0cf25bd8daa04a3d29a46c29a876923858 Mon Sep 17 00:00:00 2001 From: Angelika Kinas Date: Mon, 9 Sep 2024 13:36:38 +0200 Subject: [PATCH 2/3] fix(e2e): Change keyword search input --- apps/metadata-editor-e2e/src/e2e/edit.cy.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/apps/metadata-editor-e2e/src/e2e/edit.cy.ts b/apps/metadata-editor-e2e/src/e2e/edit.cy.ts index 2d913db377..74c1ef588a 100644 --- a/apps/metadata-editor-e2e/src/e2e/edit.cy.ts +++ b/apps/metadata-editor-e2e/src/e2e/edit.cy.ts @@ -261,14 +261,11 @@ describe('editor form', () => { it('should display place keywords', () => { cy.get('gn-ui-autocomplete').should('have.length', 1) - cy.get('gn-ui-autocomplete').type('a') + cy.get('gn-ui-autocomplete').type('afr') cy.get('mat-option').should('have.length', 10) cy.get('mat-option').eq(0).click() cy.get('gn-ui-badge').should('have.length', 4) - cy.get('gn-ui-badge') - .eq(0) - .find('span') - .should('have.text', 'Adriatic Sea ') + cy.get('gn-ui-badge').eq(0).find('span').should('have.text', 'Africa ') }) }) }) @@ -286,7 +283,7 @@ describe('editor form', () => { .find('gn-ui-badge') .find('span') .each(($span) => { - cy.wrap($span).should('not.have.text', 'Adriatic Sea ') + cy.wrap($span).should('not.have.text', 'Africa ') }) }) From 95d514b3aba5194af1bce6f8b519efd2a4ca9185 Mon Sep 17 00:00:00 2001 From: Angelika Kinas Date: Tue, 10 Sep 2024 18:20:49 +0200 Subject: [PATCH 3/3] Filter out spatial scope keywords from the keywords list in the form (visual), but keep all keywords in the record. --- .../form-field-keywords.component.spec.ts | 32 ++++++++++--- .../form-field-keywords.component.ts | 27 +++++++++-- ...orm-field-spatial-extent.component.spec.ts | 45 +++++++++++++++++++ .../form-field-spatial-extent.component.ts | 21 ++++++--- 4 files changed, 110 insertions(+), 15 deletions(-) diff --git a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-keywords/form-field-keywords.component.spec.ts b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-keywords/form-field-keywords.component.spec.ts index 6a583f08d4..d433e14292 100644 --- a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-keywords/form-field-keywords.component.spec.ts +++ b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-keywords/form-field-keywords.component.spec.ts @@ -37,12 +37,12 @@ const placeKeywords: Keyword[] = [ const otherKeywords: Keyword[] = [ { - label: 'Regional', + label: 'Administatrative', thesaurus: { id: '3' }, type: 'theme', }, { - label: 'National', + label: 'Agriculture', thesaurus: { id: '4' }, type: 'theme', }, @@ -53,6 +53,19 @@ const otherKeywords: Keyword[] = [ }, ] +const spatialScopeKeywords: Keyword[] = [ + { + label: 'National', + description: '', + type: 'theme', + }, + { + label: 'Regional', + description: '', + type: 'theme', + }, +] + class PlatformServiceInterfaceMock { searchKeywords = jest.fn(() => of([{ label: 'Address', thesaurus: { id: '1' } }]) @@ -60,7 +73,9 @@ class PlatformServiceInterfaceMock { } class EditorFacadeMock { - record$ = of({ keywords: [...placeKeywords, ...otherKeywords] }) + record$ = of({ + keywords: [...placeKeywords, ...otherKeywords, spatialScopeKeywords[1]], + }) } describe('FormFieldKeywordsComponent', () => { @@ -101,8 +116,12 @@ describe('FormFieldKeywordsComponent', () => { expect(component).toBeTruthy() }) - it('should filter out place keywords', () => { - component.value = [...placeKeywords, ...otherKeywords] + it('should filter out place keywords and spatial scope keywords', () => { + component.value = [ + ...placeKeywords, + ...otherKeywords, + spatialScopeKeywords[0], + ] expect(component.filteredKeywords).toEqual(otherKeywords) }) @@ -118,8 +137,9 @@ describe('FormFieldKeywordsComponent', () => { await component.handleKeywordsChange([...otherKeywords]) expect(valueChangeSpy).toHaveBeenCalledWith([ - ...otherKeywords, ...placeKeywords, + spatialScopeKeywords[1], + ...otherKeywords, ]) }) }) diff --git a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-keywords/form-field-keywords.component.ts b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-keywords/form-field-keywords.component.ts index e2a6bca1fb..381146ebb1 100644 --- a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-keywords/form-field-keywords.component.ts +++ b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-keywords/form-field-keywords.component.ts @@ -18,6 +18,8 @@ import { TranslateModule } from '@ngx-translate/core' import { KeywordType } from '@geonetwork-ui/common/domain/model/thesaurus' import { EditorFacade } from '../../../../+state/editor.facade' import { firstValueFrom, map } from 'rxjs' +import { SPATIAL_SCOPES } from '../../../../fields.config' +import { all } from 'ol/loadingstrategy' @Component({ selector: 'gn-ui-form-field-keywords', @@ -43,18 +45,35 @@ export class FormFieldKeywordsComponent { placeholder = 'editor.form.keywords.placeholder' get filteredKeywords(): Keyword[] { - return this.value?.filter((keyword) => keyword.type !== 'place') || [] + return ( + this.value?.filter( + (keyword) => + keyword.type !== 'place' && // filter out place keywords + !SPATIAL_SCOPES.some( + (spatialScope) => spatialScope.label === keyword.label + ) // filter out keywords matching spatialScope keys + ) || [] + ) } constructor(private editorFacade: EditorFacade) {} async handleKeywordsChange(keywords: Keyword[]) { - const placeKeywords = await firstValueFrom( + const filteredKeywords = await firstValueFrom( this.editorFacade.record$.pipe( - map((record) => record.keywords.filter((k) => k.type == 'place')) + map((record) => + record.keywords.filter( + (k) => + k.type === 'place' || // get back place keyword + SPATIAL_SCOPES.some( + (spatialScope) => spatialScope.label === k.label // get back spatialScope keywords + ) + ) + ) ) ) - this.valueChange.emit([...keywords, ...placeKeywords]) + const allKeywords = [...filteredKeywords, ...keywords] + this.valueChange.emit(allKeywords) } } diff --git a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-spatial-extent/form-field-spatial-extent.component.spec.ts b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-spatial-extent/form-field-spatial-extent.component.spec.ts index 5e6f3ec2b1..359d036bf1 100644 --- a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-spatial-extent/form-field-spatial-extent.component.spec.ts +++ b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-spatial-extent/form-field-spatial-extent.component.spec.ts @@ -416,5 +416,50 @@ describe('FormFieldSpatialExtentComponent', () => { ) }) }) + + describe('#emitChanges', () => { + const allKeywords = [ + ...datasetRecordsFixture()[0].keywords, + ...SAMPLE_PLACE_KEYWORDS, + NATIONAL_KEYWORD, + ] as Keyword[] + + beforeEach(() => { + editorFacade.record$ = from([ + { ...SAMPLE_RECORD, keywords: allKeywords } as CatalogRecord, + ]) + }) + + it('should filter out keywords that are not of type place and spatial scope keywords', async () => { + const placeKeywords = [...SAMPLE_PLACE_KEYWORDS].map((k) => ({ + label: k.label, + type: k.type, + thesaurus: k.thesaurus, + })) // remove bbox + const placeKeywordsWithExtent = placeKeywords.map((k) => ({ + ...k, + _doNotSave: false, + _linkedExtent: SAMPLE_SPATIAL_EXTENTS[0], + })) + const spatialExtents = SAMPLE_SPATIAL_EXTENTS + + await component.emitChanges(placeKeywordsWithExtent, spatialExtents) + + expect(editorFacade.updateRecordField).toHaveBeenNthCalledWith( + 1, + 'keywords', + [ + ...datasetRecordsFixture()[0].keywords, + NATIONAL_KEYWORD, + ...placeKeywords, + ] + ) + expect(editorFacade.updateRecordField).toHaveBeenNthCalledWith( + 2, + 'spatialExtents', + spatialExtents + ) + }) + }) }) }) diff --git a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-spatial-extent/form-field-spatial-extent.component.ts b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-spatial-extent/form-field-spatial-extent.component.ts index 97f3f1b9f1..3436410cb4 100644 --- a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-spatial-extent/form-field-spatial-extent.component.ts +++ b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-spatial-extent/form-field-spatial-extent.component.ts @@ -175,16 +175,27 @@ export class FormFieldSpatialExtentComponent { ...(thesaurus && { thesaurus }), } as Keyword) ) - const notPlaceKeywords = await firstValueFrom( + + const notPlaceKwAndSpatialScopeKw = await firstValueFrom( this.editorFacade.record$.pipe( - map((record) => record.keywords.filter((k) => k.type !== 'place')) + map((record) => + record.keywords.filter( + (k) => + k.type !== 'place' || + SPATIAL_SCOPES.some( + (spatialScope) => spatialScope.label === k.label // get back spatialScope keywords + ) + ) + ) ) ) - this.editorFacade.updateRecordField('keywords', [ - ...notPlaceKeywords, + const allKeywords = [ + ...notPlaceKwAndSpatialScopeKw, ...filteredPlaceKeywords, - ]) + ] + + this.editorFacade.updateRecordField('keywords', allKeywords) this.editorFacade.updateRecordField('spatialExtents', spatialExtents) }