- +
diff --git a/src/themes/custom/app/breadcrumbs/breadcrumbs.component.html b/src/themes/custom/app/breadcrumbs/breadcrumbs.component.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/custom/app/breadcrumbs/breadcrumbs.component.scss b/src/themes/custom/app/breadcrumbs/breadcrumbs.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/custom/app/breadcrumbs/breadcrumbs.component.ts b/src/themes/custom/app/breadcrumbs/breadcrumbs.component.ts new file mode 100644 index 0000000000..32d00fc8b6 --- /dev/null +++ b/src/themes/custom/app/breadcrumbs/breadcrumbs.component.ts @@ -0,0 +1,15 @@ +import { Component } from '@angular/core'; +import { BreadcrumbsComponent as BaseComponent } from '../../../../app/breadcrumbs/breadcrumbs.component'; + +/** + * Component representing the breadcrumbs of a page + */ +@Component({ + selector: 'ds-breadcrumbs', + // templateUrl: './breadcrumbs.component.html', + templateUrl: '../../../../app/breadcrumbs/breadcrumbs.component.html', + // styleUrls: ['./breadcrumbs.component.scss'] + styleUrls: ['../../../../app/breadcrumbs/breadcrumbs.component.scss'] +}) +export class BreadcrumbsComponent extends BaseComponent { +} diff --git a/src/themes/custom/theme.module.ts b/src/themes/custom/theme.module.ts index 0e1096f957..b9bba225a7 100644 --- a/src/themes/custom/theme.module.ts +++ b/src/themes/custom/theme.module.ts @@ -77,6 +77,7 @@ import { MyDSpacePageModule } from '../../app/+my-dspace-page/my-dspace-page.mod import { NavbarComponent } from './app/navbar/navbar.component'; import { HeaderComponent } from './app/header/header.component'; import { FooterComponent } from './app/footer/footer.component'; +import { BreadcrumbsComponent } from './app/breadcrumbs/breadcrumbs.component'; const DECLARATIONS = [ HomePageComponent, @@ -115,8 +116,8 @@ const DECLARATIONS = [ WorkflowItemSendBackComponent, FooterComponent, HeaderComponent, - NavbarComponent - + NavbarComponent, + BreadcrumbsComponent ]; @NgModule({ From c19ee0d49ebf752a5ea58e7960c659a85bdf7878 Mon Sep 17 00:00:00 2001 From: lotte Date: Wed, 17 Mar 2021 12:09:50 +0100 Subject: [PATCH 42/56] Updated TypeDoc for themed components --- .../themed-browse-by-switcher.component.ts | 6 +++--- .../+collection-page/themed-collection-page.component.ts | 7 +++---- src/app/+community-page/themed-community-page.component.ts | 7 +++---- .../objectnotfound/themed-objectnotfound.component.ts | 7 +++---- .../themed-configuration-search-page.component.ts | 7 +++---- src/app/+search-page/themed-search-page.component.ts | 7 +++---- .../themed-community-list-page.component.ts | 3 +-- src/app/forbidden/themed-forbidden.component.ts | 7 +++---- .../themed-end-user-agreement.component.ts | 7 +++---- src/app/info/privacy/themed-privacy.component.ts | 7 +++---- src/app/pagenotfound/themed-pagenotfound.component.ts | 7 +++---- .../themed-collection-statistics-page.component.ts | 7 +++---- .../themed-community-statistics-page.component.ts | 7 +++---- .../themed-item-statistics-page.component.ts | 7 +++---- .../themed-site-statistics-page.component.ts | 7 +++---- 15 files changed, 43 insertions(+), 57 deletions(-) diff --git a/src/app/+browse-by/+browse-by-switcher/themed-browse-by-switcher.component.ts b/src/app/+browse-by/+browse-by-switcher/themed-browse-by-switcher.component.ts index 9fbf1979b5..e92fe30ba9 100644 --- a/src/app/+browse-by/+browse-by-switcher/themed-browse-by-switcher.component.ts +++ b/src/app/+browse-by/+browse-by-switcher/themed-browse-by-switcher.component.ts @@ -3,14 +3,14 @@ import { Component } from '@angular/core'; import { ThemedComponent } from '../../shared/theme-support/themed.component'; import { BrowseBySwitcherComponent } from './browse-by-switcher.component'; +/** + * Themed wrapper for BrowseBySwitcherComponent + */ @Component({ selector: 'ds-themed-browse-by-switcher', styleUrls: [], templateUrl: '../../shared/theme-support/themed.component.html' }) -/** - * Component for determining what Browse-By component to use depending on the metadata (browse ID) provided - */ export class ThemedBrowseBySwitcherComponent extends ThemedComponent { protected getComponentName(): string { return 'BrowseBySwitcherComponent'; diff --git a/src/app/+collection-page/themed-collection-page.component.ts b/src/app/+collection-page/themed-collection-page.component.ts index 7bbc720e30..4ad9ed87e3 100644 --- a/src/app/+collection-page/themed-collection-page.component.ts +++ b/src/app/+collection-page/themed-collection-page.component.ts @@ -2,15 +2,14 @@ import { Component } from '@angular/core'; import { ThemedComponent } from '../shared/theme-support/themed.component'; import { CollectionPageComponent } from './collection-page.component'; +/** + * Themed wrapper for CollectionPageComponent + */ @Component({ selector: 'ds-themed-community-page', styleUrls: [], templateUrl: '../shared/theme-support/themed.component.html', }) - -/** - * Component to render the news section on the home page - */ export class ThemedCollectionPageComponent extends ThemedComponent { protected getComponentName(): string { return 'CollectionPageComponent'; diff --git a/src/app/+community-page/themed-community-page.component.ts b/src/app/+community-page/themed-community-page.component.ts index b420789c5f..97dd59821c 100644 --- a/src/app/+community-page/themed-community-page.component.ts +++ b/src/app/+community-page/themed-community-page.component.ts @@ -2,15 +2,14 @@ import { Component } from '@angular/core'; import { ThemedComponent } from '../shared/theme-support/themed.component'; import { CommunityPageComponent } from './community-page.component'; +/** + * Themed wrapper for CommunityPageComponent + */ @Component({ selector: 'ds-themed-community-page', styleUrls: [], templateUrl: '../shared/theme-support/themed.component.html', }) - -/** - * Component to render the news section on the home page - */ export class ThemedCommunityPageComponent extends ThemedComponent { protected getComponentName(): string { return 'CommunityPageComponent'; diff --git a/src/app/+lookup-by-id/objectnotfound/themed-objectnotfound.component.ts b/src/app/+lookup-by-id/objectnotfound/themed-objectnotfound.component.ts index 947a9f4eaa..e1bec33dfd 100644 --- a/src/app/+lookup-by-id/objectnotfound/themed-objectnotfound.component.ts +++ b/src/app/+lookup-by-id/objectnotfound/themed-objectnotfound.component.ts @@ -2,15 +2,14 @@ import { Component } from '@angular/core'; import { ThemedComponent } from '../../shared/theme-support/themed.component'; import { ObjectNotFoundComponent } from './objectnotfound.component'; +/** + * Themed wrapper for ObjectNotFoundComponent + */ @Component({ selector: 'ds-themed-objnotfound', styleUrls: [], templateUrl: '../../shared/theme-support/themed.component.html', }) - -/** - * Component to render the news section on the home page - */ export class ThemedObjectNotFoundComponent extends ThemedComponent { protected getComponentName(): string { return 'ObjectNotFoundComponent'; diff --git a/src/app/+search-page/themed-configuration-search-page.component.ts b/src/app/+search-page/themed-configuration-search-page.component.ts index 3528d3a326..9f7277be0e 100644 --- a/src/app/+search-page/themed-configuration-search-page.component.ts +++ b/src/app/+search-page/themed-configuration-search-page.component.ts @@ -4,15 +4,14 @@ import { ConfigurationSearchPageComponent } from './configuration-search-page.co import { Observable } from 'rxjs'; import { Context } from '../core/shared/context.model'; +/** + * Themed wrapper for ConfigurationSearchPageComponent + */ @Component({ selector: 'ds-themed-configuration-search-page', styleUrls: [], templateUrl: '../shared/theme-support/themed.component.html', }) - -/** - * Component to render the news section on the home page - */ export class ThemedConfigurationSearchPageComponent extends ThemedComponent { /** * The configuration to use for the search options diff --git a/src/app/+search-page/themed-search-page.component.ts b/src/app/+search-page/themed-search-page.component.ts index b23fff6289..0b2f673373 100644 --- a/src/app/+search-page/themed-search-page.component.ts +++ b/src/app/+search-page/themed-search-page.component.ts @@ -2,15 +2,14 @@ import { Component } from '@angular/core'; import { ThemedComponent } from '../shared/theme-support/themed.component'; import { SearchPageComponent } from './search-page.component'; +/** + * Themed wrapper for SearchPageComponent + */ @Component({ selector: 'ds-themed-search-page', styleUrls: [], templateUrl: '../shared/theme-support/themed.component.html', }) -/** - * This component represents the whole search page - * It renders search results depending on the current search options - */ export class ThemedSearchPageComponent extends ThemedComponent { protected getComponentName(): string { diff --git a/src/app/community-list-page/themed-community-list-page.component.ts b/src/app/community-list-page/themed-community-list-page.component.ts index 6b374fca90..20fa97bedd 100644 --- a/src/app/community-list-page/themed-community-list-page.component.ts +++ b/src/app/community-list-page/themed-community-list-page.component.ts @@ -3,8 +3,7 @@ import { ThemedComponent } from '../shared/theme-support/themed.component'; import { CommunityListPageComponent } from './community-list-page.component'; /** - * Page with title and the community list tree, as described in community-list.component; - * navigated to with community-list.page.routing.module + * Themed wrapper for CommunityListPageComponent */ @Component({ selector: 'ds-themed-community-list-page', diff --git a/src/app/forbidden/themed-forbidden.component.ts b/src/app/forbidden/themed-forbidden.component.ts index bb3694cae2..830529c8fa 100644 --- a/src/app/forbidden/themed-forbidden.component.ts +++ b/src/app/forbidden/themed-forbidden.component.ts @@ -2,15 +2,14 @@ import { Component } from '@angular/core'; import { ThemedComponent } from '../shared/theme-support/themed.component'; import { ForbiddenComponent } from './forbidden.component'; +/** + * Themed wrapper for ForbiddenComponent + */ @Component({ selector: 'ds-themed-forbidden', styleUrls: [], templateUrl: '../shared/theme-support/themed.component.html', }) - -/** - * Component to render the news section on the home page - */ export class ThemedForbiddenComponent extends ThemedComponent { protected getComponentName(): string { return 'ForbiddenComponent'; diff --git a/src/app/info/end-user-agreement/themed-end-user-agreement.component.ts b/src/app/info/end-user-agreement/themed-end-user-agreement.component.ts index 6c76314e06..74eb545b8a 100644 --- a/src/app/info/end-user-agreement/themed-end-user-agreement.component.ts +++ b/src/app/info/end-user-agreement/themed-end-user-agreement.component.ts @@ -2,15 +2,14 @@ import { Component } from '@angular/core'; import { ThemedComponent } from '../../shared/theme-support/themed.component'; import { EndUserAgreementComponent } from './end-user-agreement.component'; +/** + * Themed wrapper for EndUserAgreementComponent + */ @Component({ selector: 'ds-themed-end-user-agreement', styleUrls: [], templateUrl: '../../shared/theme-support/themed.component.html', }) - -/** - * Component to render the news section on the home page - */ export class ThemedEndUserAgreementComponent extends ThemedComponent { protected getComponentName(): string { diff --git a/src/app/info/privacy/themed-privacy.component.ts b/src/app/info/privacy/themed-privacy.component.ts index 5aed8d38af..7f2ee80ffc 100644 --- a/src/app/info/privacy/themed-privacy.component.ts +++ b/src/app/info/privacy/themed-privacy.component.ts @@ -2,15 +2,14 @@ import { Component } from '@angular/core'; import { ThemedComponent } from '../../shared/theme-support/themed.component'; import { PrivacyComponent } from './privacy.component'; +/** + * Themed wrapper for PrivacyComponent + */ @Component({ selector: 'ds-themed-privacy', styleUrls: [], templateUrl: '../../shared/theme-support/themed.component.html', }) - -/** - * Component to render the news section on the home page - */ export class ThemedPrivacyComponent extends ThemedComponent { protected getComponentName(): string { return 'PrivacyComponent'; diff --git a/src/app/pagenotfound/themed-pagenotfound.component.ts b/src/app/pagenotfound/themed-pagenotfound.component.ts index 68b06e2ac0..e6ef9eb700 100644 --- a/src/app/pagenotfound/themed-pagenotfound.component.ts +++ b/src/app/pagenotfound/themed-pagenotfound.component.ts @@ -2,15 +2,14 @@ import { Component } from '@angular/core'; import { ThemedComponent } from '../shared/theme-support/themed.component'; import { PageNotFoundComponent } from './pagenotfound.component'; +/** + * Themed wrapper for PageNotFoundComponent + */ @Component({ selector: 'ds-themed-search-page', styleUrls: [], templateUrl: '../shared/theme-support/themed.component.html', }) -/** - * This component represents the whole search page - * It renders search results depending on the current search options - */ export class ThemedPageNotFoundComponent extends ThemedComponent { protected getComponentName(): string { diff --git a/src/app/statistics-page/collection-statistics-page/themed-collection-statistics-page.component.ts b/src/app/statistics-page/collection-statistics-page/themed-collection-statistics-page.component.ts index 75cf60437f..dab96aef04 100644 --- a/src/app/statistics-page/collection-statistics-page/themed-collection-statistics-page.component.ts +++ b/src/app/statistics-page/collection-statistics-page/themed-collection-statistics-page.component.ts @@ -2,15 +2,14 @@ import { Component } from '@angular/core'; import { ThemedComponent } from '../../shared/theme-support/themed.component'; import { CollectionStatisticsPageComponent } from './collection-statistics-page.component'; +/** + * Themed wrapper for CollectionStatisticsPageComponent + */ @Component({ selector: 'ds-themed-collection-statistics-page', styleUrls: [], templateUrl: '../../shared/theme-support/themed.component.html', }) - -/** - * Component to render the news section on the home page - */ export class ThemedCollectionStatisticsPageComponent extends ThemedComponent { protected getComponentName(): string { return 'CollectionStatisticsPageComponent'; diff --git a/src/app/statistics-page/community-statistics-page/themed-community-statistics-page.component.ts b/src/app/statistics-page/community-statistics-page/themed-community-statistics-page.component.ts index 34dc17e1b3..17f185f786 100644 --- a/src/app/statistics-page/community-statistics-page/themed-community-statistics-page.component.ts +++ b/src/app/statistics-page/community-statistics-page/themed-community-statistics-page.component.ts @@ -2,15 +2,14 @@ import { Component } from '@angular/core'; import { ThemedComponent } from '../../shared/theme-support/themed.component'; import { CommunityStatisticsPageComponent } from './community-statistics-page.component'; +/** + * Themed wrapper for CommunityStatisticsPageComponent + */ @Component({ selector: 'ds-themed-collection-statistics-page', styleUrls: [], templateUrl: '../../shared/theme-support/themed.component.html', }) - -/** - * Component to render the news section on the home page - */ export class ThemedCommunityStatisticsPageComponent extends ThemedComponent { protected getComponentName(): string { return 'CommunityStatisticsPageComponent'; diff --git a/src/app/statistics-page/item-statistics-page/themed-item-statistics-page.component.ts b/src/app/statistics-page/item-statistics-page/themed-item-statistics-page.component.ts index 4651269c54..50e26329a9 100644 --- a/src/app/statistics-page/item-statistics-page/themed-item-statistics-page.component.ts +++ b/src/app/statistics-page/item-statistics-page/themed-item-statistics-page.component.ts @@ -2,15 +2,14 @@ import { Component } from '@angular/core'; import { ThemedComponent } from '../../shared/theme-support/themed.component'; import { ItemStatisticsPageComponent } from './item-statistics-page.component'; +/** + * Themed wrapper for ItemStatisticsPageComponent + */ @Component({ selector: 'ds-themed-item-statistics-page', styleUrls: [], templateUrl: '../../shared/theme-support/themed.component.html', }) - -/** - * Component to render the news section on the home page - */ export class ThemedItemStatisticsPageComponent extends ThemedComponent { protected getComponentName(): string { return 'ItemStatisticsPageComponent'; diff --git a/src/app/statistics-page/site-statistics-page/themed-site-statistics-page.component.ts b/src/app/statistics-page/site-statistics-page/themed-site-statistics-page.component.ts index 4238cc788a..3f841163ed 100644 --- a/src/app/statistics-page/site-statistics-page/themed-site-statistics-page.component.ts +++ b/src/app/statistics-page/site-statistics-page/themed-site-statistics-page.component.ts @@ -2,15 +2,14 @@ import { Component } from '@angular/core'; import { ThemedComponent } from '../../shared/theme-support/themed.component'; import { SiteStatisticsPageComponent } from './site-statistics-page.component'; +/** + * Themed wrapper for SiteStatisticsPageComponent + */ @Component({ selector: 'ds-themed-site-statistics-page', styleUrls: [], templateUrl: '../../shared/theme-support/themed.component.html', }) - -/** - * Component to render the news section on the home page - */ export class ThemedSiteStatisticsPageComponent extends ThemedComponent { protected getComponentName(): string { return 'SiteStatisticsPageComponent'; From b833407dd1050e919edc33322d4ce5f53f9d9ea2 Mon Sep 17 00:00:00 2001 From: lotte Date: Tue, 16 Mar 2021 16:46:58 +0100 Subject: [PATCH 43/56] 77472: Fix unclickable titles in search lists --- ...arch-result-list-element.component.spec.ts | 5 +++- ...arch-result-list-element.component.spec.ts | 5 +++- ...arch-result-list-element.component.spec.ts | 5 +++- ...in-workflow-list-element.component.spec.ts | 3 +++ ...t-admin-workflow-list-element.component.ts | 8 ++++-- .../core/breadcrumbs/dso-name.service.spec.ts | 2 +- src/app/core/breadcrumbs/dso-name.service.ts | 7 ++++- ...arch-result-list-element.component.spec.ts | 5 +++- ...arch-result-list-element.component.spec.ts | 5 +++- ...arch-result-list-element.component.spec.ts | 5 +++- ...arch-result-list-element.component.spec.ts | 5 +++- ...arch-result-list-element.component.spec.ts | 5 +++- ...arch-result-list-element.component.spec.ts | 5 +++- ...n-sidebar-search-list-element.component.ts | 7 +++-- ...-list-submission-element.component.spec.ts | 3 +++ ...esult-list-submission-element.component.ts | 8 ++++-- ...esult-list-submission-element.component.ts | 8 ++++-- src/app/shared/mocks/dso-name.service.mock.ts | 9 +++++++ ...ch-result-detail-element.component.spec.ts | 5 +++- ...ch-result-detail-element.component.spec.ts | 5 +++- ...ch-result-detail-element.component.spec.ts | 5 +++- ...arch-result-list-element.component.spec.ts | 5 +++- ...ed-search-result-list-element.component.ts | 6 +++-- ...arch-result-list-element.component.spec.ts | 5 +++- ...ed-search-result-list-element.component.ts | 6 +++-- ...arch-result-list-element.component.spec.ts | 5 +++- ...ed-search-result-list-element.component.ts | 6 +++-- ...-list-element-submission.component.spec.ts | 3 +++ ...arch-result-list-element.component.spec.ts | 5 +++- ...ol-search-result-list-element.component.ts | 6 +++-- ...arch-result-list-element.component.spec.ts | 3 +++ ...em-search-result-list-element.component.ts | 6 +++-- ...arch-result-list-element.component.spec.ts | 3 +++ ...em-search-result-list-element.component.ts | 6 +++-- ...-search-result-list-element.component.html | 4 +-- ...arch-result-list-element.component.spec.ts | 3 +++ ...-search-result-list-element.component.html | 4 +-- ...arch-result-list-element.component.spec.ts | 3 +++ ...-search-result-list-element.component.html | 8 +++--- ...arch-result-list-element.component.spec.ts | 27 ++++++++++++------- .../search-result-list-element.component.ts | 5 +++- ...ebar-search-list-element.component.spec.ts | 3 +++ .../sidebar-search-list-element.component.ts | 7 +++-- src/assets/i18n/en.json5 | 4 +++ 44 files changed, 190 insertions(+), 58 deletions(-) create mode 100644 src/app/shared/mocks/dso-name.service.mock.ts diff --git a/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/collection-search-result/collection-admin-search-result-list-element.component.spec.ts b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/collection-search-result/collection-admin-search-result-list-element.component.spec.ts index 218cbc0ca2..56e25264cf 100644 --- a/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/collection-search-result/collection-admin-search-result-list-element.component.spec.ts +++ b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/collection-search-result/collection-admin-search-result-list-element.component.spec.ts @@ -11,6 +11,8 @@ import { Collection } from '../../../../../core/shared/collection.model'; import { By } from '@angular/platform-browser'; import { RouterTestingModule } from '@angular/router/testing'; import { getCollectionEditRoute } from '../../../../../+collection-page/collection-page-routing-paths'; +import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; +import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock'; describe('CollectionAdminSearchResultListElementComponent', () => { let component: CollectionAdminSearchResultListElementComponent; @@ -33,7 +35,8 @@ describe('CollectionAdminSearchResultListElementComponent', () => { RouterTestingModule.withRoutes([]) ], declarations: [CollectionAdminSearchResultListElementComponent], - providers: [{ provide: TruncatableService, useValue: {} }], + providers: [{ provide: TruncatableService, useValue: {} }, + { provide: DSONameService, useClass: DSONameServiceMock }], schemas: [NO_ERRORS_SCHEMA] }) .compileComponents(); diff --git a/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/community-search-result/community-admin-search-result-list-element.component.spec.ts b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/community-search-result/community-admin-search-result-list-element.component.spec.ts index 69fe8856dd..29d9925326 100644 --- a/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/community-search-result/community-admin-search-result-list-element.component.spec.ts +++ b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/community-search-result/community-admin-search-result-list-element.component.spec.ts @@ -11,6 +11,8 @@ import { CommunityAdminSearchResultListElementComponent } from './community-admi import { CommunitySearchResult } from '../../../../../shared/object-collection/shared/community-search-result.model'; import { Community } from '../../../../../core/shared/community.model'; import { getCommunityEditRoute } from '../../../../../+community-page/community-page-routing-paths'; +import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; +import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock'; describe('CommunityAdminSearchResultListElementComponent', () => { let component: CommunityAdminSearchResultListElementComponent; @@ -33,7 +35,8 @@ describe('CommunityAdminSearchResultListElementComponent', () => { RouterTestingModule.withRoutes([]) ], declarations: [CommunityAdminSearchResultListElementComponent], - providers: [{ provide: TruncatableService, useValue: {} }], + providers: [{ provide: TruncatableService, useValue: {} }, + { provide: DSONameService, useClass: DSONameServiceMock }], schemas: [NO_ERRORS_SCHEMA] }) .compileComponents(); diff --git a/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/item-search-result/item-admin-search-result-list-element.component.spec.ts b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/item-search-result/item-admin-search-result-list-element.component.spec.ts index 1d2f25c2eb..3774a07757 100644 --- a/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/item-search-result/item-admin-search-result-list-element.component.spec.ts +++ b/src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/item-search-result/item-admin-search-result-list-element.component.spec.ts @@ -8,6 +8,8 @@ import { RouterTestingModule } from '@angular/router/testing'; import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model'; import { ItemAdminSearchResultListElementComponent } from './item-admin-search-result-list-element.component'; import { Item } from '../../../../../core/shared/item.model'; +import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; +import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock'; describe('ItemAdminSearchResultListElementComponent', () => { let component: ItemAdminSearchResultListElementComponent; @@ -30,7 +32,8 @@ describe('ItemAdminSearchResultListElementComponent', () => { RouterTestingModule.withRoutes([]) ], declarations: [ItemAdminSearchResultListElementComponent], - providers: [{ provide: TruncatableService, useValue: {} }], + providers: [{ provide: TruncatableService, useValue: {} }, + { provide: DSONameService, useClass: DSONameServiceMock }], schemas: [NO_ERRORS_SCHEMA] }) .compileComponents(); diff --git a/src/app/+admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-list-element/workflow-item/workflow-item-search-result-admin-workflow-list-element.component.spec.ts b/src/app/+admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-list-element/workflow-item/workflow-item-search-result-admin-workflow-list-element.component.spec.ts index 47907999a2..a792a606e9 100644 --- a/src/app/+admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-list-element/workflow-item/workflow-item-search-result-admin-workflow-list-element.component.spec.ts +++ b/src/app/+admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-list-element/workflow-item/workflow-item-search-result-admin-workflow-list-element.component.spec.ts @@ -16,6 +16,8 @@ import { Item } from '../../../../../core/shared/item.model'; import { WorkflowItemSearchResult } from '../../../../../shared/object-collection/shared/workflow-item-search-result.model'; import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils'; import { getMockLinkService } from '../../../../../shared/mocks/link-service.mock'; +import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; +import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock'; describe('WorkflowItemAdminWorkflowListElementComponent', () => { let component: WorkflowItemSearchResultAdminWorkflowListElementComponent; @@ -49,6 +51,7 @@ describe('WorkflowItemAdminWorkflowListElementComponent', () => { providers: [ { provide: TruncatableService, useValue: mockTruncatableService }, { provide: LinkService, useValue: linkService }, + { provide: DSONameService, useClass: DSONameServiceMock } ], schemas: [NO_ERRORS_SCHEMA] }) diff --git a/src/app/+admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-list-element/workflow-item/workflow-item-search-result-admin-workflow-list-element.component.ts b/src/app/+admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-list-element/workflow-item/workflow-item-search-result-admin-workflow-list-element.component.ts index 80225db09f..3dd17faf25 100644 --- a/src/app/+admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-list-element/workflow-item/workflow-item-search-result-admin-workflow-list-element.component.ts +++ b/src/app/+admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-list-element/workflow-item/workflow-item-search-result-admin-workflow-list-element.component.ts @@ -12,6 +12,7 @@ import { Item } from '../../../../../core/shared/item.model'; import { SearchResultListElementComponent } from '../../../../../shared/object-list/search-result-list-element/search-result-list-element.component'; import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service'; import { WorkflowItemSearchResult } from '../../../../../shared/object-collection/shared/workflow-item-search-result.model'; +import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; @listableObjectComponent(WorkflowItemSearchResult, ViewMode.ListElement, Context.AdminWorkflowSearch) @Component({ @@ -29,8 +30,11 @@ export class WorkflowItemSearchResultAdminWorkflowListElementComponent extends S */ public item$: Observable; - constructor(private linkService: LinkService, protected truncatableService: TruncatableService) { - super(truncatableService); + constructor(private linkService: LinkService, + protected truncatableService: TruncatableService, + protected dsoNameService: DSONameService + ) { + super(truncatableService, dsoNameService); } /** diff --git a/src/app/core/breadcrumbs/dso-name.service.spec.ts b/src/app/core/breadcrumbs/dso-name.service.spec.ts index 5dd02032b9..7a399ce748 100644 --- a/src/app/core/breadcrumbs/dso-name.service.spec.ts +++ b/src/app/core/breadcrumbs/dso-name.service.spec.ts @@ -45,7 +45,7 @@ describe(`DSONameService`, () => { } }); - service = new DSONameService(); + service = new DSONameService({ instant: (a) => a } as any); }); describe(`getName`, () => { diff --git a/src/app/core/breadcrumbs/dso-name.service.ts b/src/app/core/breadcrumbs/dso-name.service.ts index 7f8af2352b..38363d1989 100644 --- a/src/app/core/breadcrumbs/dso-name.service.ts +++ b/src/app/core/breadcrumbs/dso-name.service.ts @@ -1,6 +1,7 @@ import { Injectable } from '@angular/core'; import { hasValue } from '../../shared/empty.util'; import { DSpaceObject } from '../shared/dspace-object.model'; +import { TranslateService } from '@ngx-translate/core'; /** * Returns a name for a {@link DSpaceObject} based @@ -11,6 +12,10 @@ import { DSpaceObject } from '../shared/dspace-object.model'; }) export class DSONameService { + constructor(private translateService: TranslateService) { + + } + /** * Functions to generate the specific names. * @@ -29,7 +34,7 @@ export class DSONameService { }, Default: (dso: DSpaceObject): string => { // If object doesn't have dc.title metadata use name property - return dso.firstMetadataValue('dc.title') || dso.name; + return dso.firstMetadataValue('dc.title') || dso.name || this.translateService.instant('dso.name.untitled'); } }; diff --git a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.spec.ts b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.spec.ts index 9f15cc9816..b47a767be2 100644 --- a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.spec.ts +++ b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.spec.ts @@ -7,6 +7,8 @@ import { JournalIssueSearchResultListElementComponent } from './journal-issue-se import { Item } from '../../../../../core/shared/item.model'; import { TruncatePipe } from '../../../../../shared/utils/truncate.pipe'; import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service'; +import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; +import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock'; let journalIssueListElementComponent: JournalIssueSearchResultListElementComponent; let fixture: ComponentFixture; @@ -60,7 +62,8 @@ describe('JournalIssueSearchResultListElementComponent', () => { TestBed.configureTestingModule({ declarations: [JournalIssueSearchResultListElementComponent, TruncatePipe], providers: [ - { provide: TruncatableService, useValue: {} } + { provide: TruncatableService, useValue: {} }, + { provide: DSONameService, useClass: DSONameServiceMock } ], schemas: [NO_ERRORS_SCHEMA] diff --git a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-volume/journal-volume-search-result-list-element.component.spec.ts b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-volume/journal-volume-search-result-list-element.component.spec.ts index b39129fbd2..d03bc29d6b 100644 --- a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-volume/journal-volume-search-result-list-element.component.spec.ts +++ b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-volume/journal-volume-search-result-list-element.component.spec.ts @@ -7,6 +7,8 @@ import { Item } from '../../../../../core/shared/item.model'; import { JournalVolumeSearchResultListElementComponent } from './journal-volume-search-result-list-element.component'; import { TruncatePipe } from '../../../../../shared/utils/truncate.pipe'; import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service'; +import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; +import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock'; let journalVolumeListElementComponent: JournalVolumeSearchResultListElementComponent; let fixture: ComponentFixture; @@ -59,7 +61,8 @@ describe('JournalVolumeSearchResultListElementComponent', () => { TestBed.configureTestingModule({ declarations: [JournalVolumeSearchResultListElementComponent, TruncatePipe], providers: [ - { provide: TruncatableService, useValue: {} } + { provide: TruncatableService, useValue: {} }, + { provide: DSONameService, useClass: DSONameServiceMock }, ], schemas: [NO_ERRORS_SCHEMA] diff --git a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal/journal-search-result-list-element.component.spec.ts b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal/journal-search-result-list-element.component.spec.ts index 5ab60e97b7..9aca414ec6 100644 --- a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal/journal-search-result-list-element.component.spec.ts +++ b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal/journal-search-result-list-element.component.spec.ts @@ -7,6 +7,8 @@ import { Item } from '../../../../../core/shared/item.model'; import { TruncatePipe } from '../../../../../shared/utils/truncate.pipe'; import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service'; import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model'; +import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; +import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock'; let journalListElementComponent: JournalSearchResultListElementComponent; let fixture: ComponentFixture; @@ -55,7 +57,8 @@ describe('JournalSearchResultListElementComponent', () => { TestBed.configureTestingModule({ declarations: [JournalSearchResultListElementComponent, TruncatePipe], providers: [ - { provide: TruncatableService, useValue: {} } + { provide: TruncatableService, useValue: {} }, + { provide: DSONameService, useClass: DSONameServiceMock }, ], schemas: [NO_ERRORS_SCHEMA] diff --git a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component.spec.ts b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component.spec.ts index 29bb069121..d116b2ade7 100644 --- a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component.spec.ts +++ b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component.spec.ts @@ -7,6 +7,8 @@ import { Item } from '../../../../../core/shared/item.model'; import { TruncatePipe } from '../../../../../shared/utils/truncate.pipe'; import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service'; import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model'; +import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; +import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock'; let orgUnitListElementComponent: OrgUnitSearchResultListElementComponent; let fixture: ComponentFixture; @@ -53,7 +55,8 @@ describe('OrgUnitSearchResultListElementComponent', () => { TestBed.configureTestingModule({ declarations: [ OrgUnitSearchResultListElementComponent , TruncatePipe], providers: [ - { provide: TruncatableService, useValue: {} } + { provide: TruncatableService, useValue: {} }, + { provide: DSONameService, useClass: DSONameServiceMock } ], schemas: [ NO_ERRORS_SCHEMA ] diff --git a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/person/person-search-result-list-element.component.spec.ts b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/person/person-search-result-list-element.component.spec.ts index 08fdf821d3..2acb02da7d 100644 --- a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/person/person-search-result-list-element.component.spec.ts +++ b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/person/person-search-result-list-element.component.spec.ts @@ -7,6 +7,8 @@ import { PersonSearchResultListElementComponent } from './person-search-result-l import { Item } from '../../../../../core/shared/item.model'; import { TruncatePipe } from '../../../../../shared/utils/truncate.pipe'; import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service'; +import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; +import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock'; let personListElementComponent: PersonSearchResultListElementComponent; let fixture: ComponentFixture; @@ -53,7 +55,8 @@ describe('PersonSearchResultListElementComponent', () => { TestBed.configureTestingModule({ declarations: [PersonSearchResultListElementComponent, TruncatePipe], providers: [ - { provide: TruncatableService, useValue: {} } + { provide: TruncatableService, useValue: {} }, + { provide: DSONameService, useClass: DSONameServiceMock } ], schemas: [NO_ERRORS_SCHEMA] diff --git a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/project/project-search-result-list-element.component.spec.ts b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/project/project-search-result-list-element.component.spec.ts index 1d45d98281..8b27a86f6d 100644 --- a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/project/project-search-result-list-element.component.spec.ts +++ b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/project/project-search-result-list-element.component.spec.ts @@ -6,6 +6,8 @@ import { ProjectSearchResultListElementComponent } from './project-search-result import { Item } from '../../../../../core/shared/item.model'; import { TruncatePipe } from '../../../../../shared/utils/truncate.pipe'; import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service'; +import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; +import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock'; let projectListElementComponent: ProjectSearchResultListElementComponent; let fixture: ComponentFixture; @@ -53,7 +55,8 @@ describe('ProjectSearchResultListElementComponent', () => { TestBed.configureTestingModule({ declarations: [ProjectSearchResultListElementComponent, TruncatePipe], providers: [ - { provide: TruncatableService, useValue: {} } + { provide: TruncatableService, useValue: {} }, + { provide: DSONameService, useClass: DSONameServiceMock } ], schemas: [NO_ERRORS_SCHEMA] diff --git a/src/app/entity-groups/research-entities/item-list-elements/sidebar-search-list-elements/person/person-sidebar-search-list-element.component.ts b/src/app/entity-groups/research-entities/item-list-elements/sidebar-search-list-elements/person/person-sidebar-search-list-element.component.ts index ade080f2e2..281f22ddb4 100644 --- a/src/app/entity-groups/research-entities/item-list-elements/sidebar-search-list-elements/person/person-sidebar-search-list-element.component.ts +++ b/src/app/entity-groups/research-entities/item-list-elements/sidebar-search-list-elements/person/person-sidebar-search-list-element.component.ts @@ -9,6 +9,7 @@ import { isNotEmpty } from '../../../../../shared/empty.util'; import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service'; import { LinkService } from '../../../../../core/cache/builders/link.service'; import { TranslateService } from '@ngx-translate/core'; +import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; @listableObjectComponent('PersonSearchResult', ViewMode.ListElement, Context.SideBarSearchModal) @listableObjectComponent('PersonSearchResult', ViewMode.ListElement, Context.SideBarSearchModalCurrent) @@ -23,8 +24,10 @@ import { TranslateService } from '@ngx-translate/core'; export class PersonSidebarSearchListElementComponent extends SidebarSearchListElementComponent { constructor(protected truncatableService: TruncatableService, protected linkService: LinkService, - protected translateService: TranslateService) { - super(truncatableService, linkService); + protected translateService: TranslateService, + protected dsoNameService: DSONameService + ) { + super(truncatableService, linkService, dsoNameService); } /** diff --git a/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.spec.ts b/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.spec.ts index ff74c6554f..13a307e18f 100644 --- a/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.spec.ts +++ b/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.spec.ts @@ -27,6 +27,8 @@ import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote- import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service'; import { TruncatePipe } from '../../../../../shared/utils/truncate.pipe'; import { OrgUnitSearchResultListSubmissionElementComponent } from './org-unit-search-result-list-submission-element.component'; +import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; +import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock'; let personListElementComponent: OrgUnitSearchResultListSubmissionElementComponent; let fixture: ComponentFixture; @@ -115,6 +117,7 @@ describe('OrgUnitSearchResultListSubmissionElementComponent', () => { { provide: DSOChangeAnalyzer, useValue: {} }, { provide: DefaultChangeAnalyzer, useValue: {} }, { provide: BitstreamDataService, useValue: mockBitstreamDataService }, + { provide: DSONameService, useClass: DSONameServiceMock } ], schemas: [NO_ERRORS_SCHEMA] diff --git a/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.ts b/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.ts index 0c6d2b450c..091549dcea 100644 --- a/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.ts +++ b/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.ts @@ -19,6 +19,8 @@ import { MetadataValue } from '../../../../../core/shared/metadata.models'; import { ItemDataService } from '../../../../../core/data/item-data.service'; import { SelectableListService } from '../../../../../shared/object-list/selectable-list/selectable-list.service'; import { NameVariantModalComponent } from '../../name-variant-modal/name-variant-modal.component'; +import { LinkService } from '../../../../../core/cache/builders/link.service'; +import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; @listableObjectComponent('OrgUnitSearchResult', ViewMode.ListElement, Context.EntitySearchModal) @listableObjectComponent('OrgUnitSearchResult', ViewMode.ListElement, Context.EntitySearchModalWithNameVariants) @@ -44,8 +46,10 @@ export class OrgUnitSearchResultListSubmissionElementComponent extends SearchRes private modalService: NgbModal, private itemDataService: ItemDataService, private bitstreamDataService: BitstreamDataService, - private selectableListService: SelectableListService) { - super(truncatableService); + private selectableListService: SelectableListService, + protected dsoNameService: DSONameService + ) { + super(truncatableService, dsoNameService); } ngOnInit() { diff --git a/src/app/entity-groups/research-entities/submission/item-list-elements/person/person-search-result-list-submission-element.component.ts b/src/app/entity-groups/research-entities/submission/item-list-elements/person/person-search-result-list-submission-element.component.ts index 96be3d1b4e..2d991d5c64 100644 --- a/src/app/entity-groups/research-entities/submission/item-list-elements/person/person-search-result-list-submission-element.component.ts +++ b/src/app/entity-groups/research-entities/submission/item-list-elements/person/person-search-result-list-submission-element.component.ts @@ -19,6 +19,8 @@ import { NameVariantModalComponent } from '../../name-variant-modal/name-variant import { MetadataValue } from '../../../../../core/shared/metadata.models'; import { ItemDataService } from '../../../../../core/data/item-data.service'; import { SelectableListService } from '../../../../../shared/object-list/selectable-list/selectable-list.service'; +import { LinkService } from '../../../../../core/cache/builders/link.service'; +import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; @listableObjectComponent('PersonSearchResult', ViewMode.ListElement, Context.EntitySearchModalWithNameVariants) @Component({ @@ -42,8 +44,10 @@ export class PersonSearchResultListSubmissionElementComponent extends SearchResu private modalService: NgbModal, private itemDataService: ItemDataService, private bitstreamDataService: BitstreamDataService, - private selectableListService: SelectableListService) { - super(truncatableService); + private selectableListService: SelectableListService, + protected dsoNameService: DSONameService + ) { + super(truncatableService, dsoNameService); } ngOnInit() { diff --git a/src/app/shared/mocks/dso-name.service.mock.ts b/src/app/shared/mocks/dso-name.service.mock.ts new file mode 100644 index 0000000000..f4947cc860 --- /dev/null +++ b/src/app/shared/mocks/dso-name.service.mock.ts @@ -0,0 +1,9 @@ +import { DSpaceObject } from '../../core/shared/dspace-object.model'; + +export const UNDEFINED_NAME = 'Undefined'; + +export class DSONameServiceMock { + public getName(dso: DSpaceObject) { + return UNDEFINED_NAME; + } +} diff --git a/src/app/shared/object-detail/my-dspace-result-detail-element/pool-search-result/pool-search-result-detail-element.component.spec.ts b/src/app/shared/object-detail/my-dspace-result-detail-element/pool-search-result/pool-search-result-detail-element.component.spec.ts index f5f19fc041..8e0458d49f 100644 --- a/src/app/shared/object-detail/my-dspace-result-detail-element/pool-search-result/pool-search-result-detail-element.component.spec.ts +++ b/src/app/shared/object-detail/my-dspace-result-detail-element/pool-search-result/pool-search-result-detail-element.component.spec.ts @@ -15,6 +15,8 @@ import { VarDirective } from '../../../utils/var.directive'; import { LinkService } from '../../../../core/cache/builders/link.service'; import { getMockLinkService } from '../../../mocks/link-service.mock'; import { By } from '@angular/platform-browser'; +import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; +import { DSONameServiceMock } from '../../../mocks/dso-name.service.mock'; let component: PoolSearchResultDetailElementComponent; let fixture: ComponentFixture; @@ -67,7 +69,8 @@ describe('PoolSearchResultDetailElementComponent', () => { providers: [ { provide: 'objectElementProvider', useValue: (mockResultObject) }, { provide: 'indexElementProvider', useValue: (compIndex) }, - { provide: LinkService, useValue: linkService } + { provide: LinkService, useValue: linkService }, + { provide: DSONameService, useClass: DSONameServiceMock }, ], schemas: [NO_ERRORS_SCHEMA] }).overrideComponent(PoolSearchResultDetailElementComponent, { diff --git a/src/app/shared/object-detail/my-dspace-result-detail-element/workflow-item-search-result/workflow-item-search-result-detail-element.component.spec.ts b/src/app/shared/object-detail/my-dspace-result-detail-element/workflow-item-search-result/workflow-item-search-result-detail-element.component.spec.ts index 8745697fbe..536bd6d0bb 100644 --- a/src/app/shared/object-detail/my-dspace-result-detail-element/workflow-item-search-result/workflow-item-search-result-detail-element.component.spec.ts +++ b/src/app/shared/object-detail/my-dspace-result-detail-element/workflow-item-search-result/workflow-item-search-result-detail-element.component.spec.ts @@ -12,6 +12,8 @@ import { createSuccessfulRemoteDataObject } from '../../../remote-data.utils'; import { WorkflowItemSearchResult } from '../../../object-collection/shared/workflow-item-search-result.model'; import { getMockLinkService } from '../../../mocks/link-service.mock'; import { LinkService } from '../../../../core/cache/builders/link.service'; +import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; +import { DSONameServiceMock } from '../../../mocks/dso-name.service.mock'; let component: WorkflowItemSearchResultDetailElementComponent; let fixture: ComponentFixture; @@ -62,7 +64,8 @@ describe('WorkflowItemSearchResultDetailElementComponent', () => { providers: [ { provide: 'objectElementProvider', useValue: (mockResultObject) }, { provide: 'indexElementProvider', useValue: (compIndex) }, - { provide: LinkService, useValue: linkService } + { provide: LinkService, useValue: linkService }, + { provide: DSONameService, useClass: DSONameServiceMock }, ], schemas: [NO_ERRORS_SCHEMA] }).overrideComponent(WorkflowItemSearchResultDetailElementComponent, { diff --git a/src/app/shared/object-detail/my-dspace-result-detail-element/workspace-item-search-result/workspace-item-search-result-detail-element.component.spec.ts b/src/app/shared/object-detail/my-dspace-result-detail-element/workspace-item-search-result/workspace-item-search-result-detail-element.component.spec.ts index 1662e033d7..00a20b006a 100644 --- a/src/app/shared/object-detail/my-dspace-result-detail-element/workspace-item-search-result/workspace-item-search-result-detail-element.component.spec.ts +++ b/src/app/shared/object-detail/my-dspace-result-detail-element/workspace-item-search-result/workspace-item-search-result-detail-element.component.spec.ts @@ -12,6 +12,8 @@ import { createSuccessfulRemoteDataObject } from '../../../remote-data.utils'; import { WorkflowItemSearchResult } from '../../../object-collection/shared/workflow-item-search-result.model'; import { getMockLinkService } from '../../../mocks/link-service.mock'; import { LinkService } from '../../../../core/cache/builders/link.service'; +import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; +import { DSONameServiceMock } from '../../../mocks/dso-name.service.mock'; let component: WorkspaceItemSearchResultDetailElementComponent; let fixture: ComponentFixture; @@ -62,7 +64,8 @@ describe('WorkspaceItemSearchResultDetailElementComponent', () => { providers: [ { provide: 'objectElementProvider', useValue: (mockResultObject) }, { provide: 'indexElementProvider', useValue: (compIndex) }, - { provide: LinkService, useValue: linkService } + { provide: LinkService, useValue: linkService }, + { provide: DSONameService, useClass: DSONameServiceMock }, ], schemas: [NO_ERRORS_SCHEMA] }).overrideComponent(WorkspaceItemSearchResultDetailElementComponent, { diff --git a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-approved-search-result/claimed-approved-search-result-list-element.component.spec.ts b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-approved-search-result/claimed-approved-search-result-list-element.component.spec.ts index e56999472e..b85b31c8ba 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-approved-search-result/claimed-approved-search-result-list-element.component.spec.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-approved-search-result/claimed-approved-search-result-list-element.component.spec.ts @@ -15,6 +15,8 @@ import { LinkService } from '../../../../../core/cache/builders/link.service'; import { MyDspaceItemStatusType } from '../../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type'; import { ClaimedApprovedTaskSearchResult } from '../../../../object-collection/shared/claimed-approved-task-search-result.model'; import { ClaimedApprovedSearchResultListElementComponent } from './claimed-approved-search-result-list-element.component'; +import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; +import { DSONameServiceMock } from '../../../../mocks/dso-name.service.mock'; let component: ClaimedApprovedSearchResultListElementComponent; let fixture: ComponentFixture; @@ -64,7 +66,8 @@ describe('ClaimedApprovedSearchResultListElementComponent', () => { declarations: [ClaimedApprovedSearchResultListElementComponent, VarDirective], providers: [ { provide: TruncatableService, useValue: {} }, - { provide: LinkService, useValue: linkService } + { provide: LinkService, useValue: linkService }, + { provide: DSONameService, useClass: DSONameServiceMock } ], schemas: [NO_ERRORS_SCHEMA] }).overrideComponent(ClaimedApprovedSearchResultListElementComponent, { diff --git a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-approved-search-result/claimed-approved-search-result-list-element.component.ts b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-approved-search-result/claimed-approved-search-result-list-element.component.ts index 36e468808a..5571782ce2 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-approved-search-result/claimed-approved-search-result-list-element.component.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-approved-search-result/claimed-approved-search-result-list-element.component.ts @@ -12,6 +12,7 @@ import { followLink } from '../../../../utils/follow-link-config.model'; import { SearchResultListElementComponent } from '../../../search-result-list-element/search-result-list-element.component'; import { ClaimedTaskSearchResult } from '../../../../object-collection/shared/claimed-task-search-result.model'; import { ClaimedTask } from '../../../../../core/tasks/models/claimed-task-object.model'; +import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; /** * This component renders claimed task approved object for the search result in the list view. @@ -41,9 +42,10 @@ export class ClaimedApprovedSearchResultListElementComponent extends SearchResul public constructor( protected linkService: LinkService, - protected truncatableService: TruncatableService + protected truncatableService: TruncatableService, + protected dsoNameService: DSONameService ) { - super(truncatableService); + super(truncatableService, dsoNameService); } /** diff --git a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-declined-search-result/claimed-declided-search-result-list-element.component.spec.ts b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-declined-search-result/claimed-declided-search-result-list-element.component.spec.ts index 8a8d542063..291c8244b7 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-declined-search-result/claimed-declided-search-result-list-element.component.spec.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-declined-search-result/claimed-declided-search-result-list-element.component.spec.ts @@ -15,6 +15,8 @@ import { VarDirective } from '../../../../utils/var.directive'; import { TruncatableService } from '../../../../truncatable/truncatable.service'; import { LinkService } from '../../../../../core/cache/builders/link.service'; import { MyDspaceItemStatusType } from '../../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type'; +import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; +import { DSONameServiceMock } from '../../../../mocks/dso-name.service.mock'; let component: ClaimedDeclinedSearchResultListElementComponent; let fixture: ComponentFixture; @@ -64,7 +66,8 @@ describe('ClaimedDeclinedSearchResultListElementComponent', () => { declarations: [ClaimedDeclinedSearchResultListElementComponent, VarDirective], providers: [ { provide: TruncatableService, useValue: {} }, - { provide: LinkService, useValue: linkService } + { provide: LinkService, useValue: linkService }, + { provide: DSONameService, useClass: DSONameServiceMock } ], schemas: [NO_ERRORS_SCHEMA] }).overrideComponent(ClaimedDeclinedSearchResultListElementComponent, { diff --git a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-declined-search-result/claimed-declined-search-result-list-element.component.ts b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-declined-search-result/claimed-declined-search-result-list-element.component.ts index 7db12c1725..630aa699a7 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-declined-search-result/claimed-declined-search-result-list-element.component.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-declined-search-result/claimed-declined-search-result-list-element.component.ts @@ -13,6 +13,7 @@ import { followLink } from '../../../../utils/follow-link-config.model'; import { SearchResultListElementComponent } from '../../../search-result-list-element/search-result-list-element.component'; import { ClaimedTaskSearchResult } from '../../../../object-collection/shared/claimed-task-search-result.model'; import { ClaimedTask } from '../../../../../core/tasks/models/claimed-task-object.model'; +import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; /** * This component renders claimed task declined object for the search result in the list view. @@ -42,9 +43,10 @@ export class ClaimedDeclinedSearchResultListElementComponent extends SearchResul public constructor( protected linkService: LinkService, - protected truncatableService: TruncatableService + protected truncatableService: TruncatableService, + protected dsoNameService: DSONameService ) { - super(truncatableService); + super(truncatableService, dsoNameService); } /** diff --git a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.spec.ts b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.spec.ts index 5dad421f68..7896061a73 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.spec.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.spec.ts @@ -16,6 +16,8 @@ import { VarDirective } from '../../../utils/var.directive'; import { LinkService } from '../../../../core/cache/builders/link.service'; import { getMockLinkService } from '../../../mocks/link-service.mock'; import { By } from '@angular/platform-browser'; +import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; +import { DSONameServiceMock } from '../../../mocks/dso-name.service.mock'; let component: ClaimedSearchResultListElementComponent; let fixture: ComponentFixture; @@ -65,7 +67,8 @@ describe('ClaimedSearchResultListElementComponent', () => { declarations: [ClaimedSearchResultListElementComponent, VarDirective], providers: [ { provide: TruncatableService, useValue: {} }, - { provide: LinkService, useValue: linkService } + { provide: LinkService, useValue: linkService }, + { provide: DSONameService, useClass: DSONameServiceMock } ], schemas: [NO_ERRORS_SCHEMA] }).overrideComponent(ClaimedSearchResultListElementComponent, { diff --git a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.ts b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.ts index cef1056401..dae3272889 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.ts @@ -12,6 +12,7 @@ import { WorkflowItem } from '../../../../core/submission/models/workflowitem.mo import { followLink } from '../../../utils/follow-link-config.model'; import { SearchResultListElementComponent } from '../../search-result-list-element/search-result-list-element.component'; import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model'; +import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; @Component({ selector: 'ds-claimed-search-result-list-element', @@ -38,9 +39,10 @@ export class ClaimedSearchResultListElementComponent extends SearchResultListEle public constructor( protected linkService: LinkService, - protected truncatableService: TruncatableService + protected truncatableService: TruncatableService, + protected dsoNameService: DSONameService ) { - super(truncatableService); + super(truncatableService, dsoNameService); } /** diff --git a/src/app/shared/object-list/my-dspace-result-list-element/item-search-result/item-search-result-list-element-submission.component.spec.ts b/src/app/shared/object-list/my-dspace-result-list-element/item-search-result/item-search-result-list-element-submission.component.spec.ts index e2017e8748..d1871d0996 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/item-search-result/item-search-result-list-element-submission.component.spec.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/item-search-result/item-search-result-list-element-submission.component.spec.ts @@ -10,6 +10,8 @@ import { ItemSearchResult } from '../../../object-collection/shared/item-search- import { ItemSearchResultListElementSubmissionComponent } from './item-search-result-list-element-submission.component'; import { TruncatableService } from '../../../truncatable/truncatable.service'; import { By } from '@angular/platform-browser'; +import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; +import { DSONameServiceMock } from '../../../mocks/dso-name.service.mock'; let component: ItemSearchResultListElementSubmissionComponent; let fixture: ComponentFixture; @@ -54,6 +56,7 @@ describe('ItemMyDSpaceResultListElementComponent', () => { declarations: [ItemSearchResultListElementSubmissionComponent], providers: [ { provide: TruncatableService, useValue: {} }, + { provide: DSONameService, useClass: DSONameServiceMock } ], schemas: [NO_ERRORS_SCHEMA] }).overrideComponent(ItemSearchResultListElementSubmissionComponent, { diff --git a/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.spec.ts b/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.spec.ts index e55b45aed7..18db9abd67 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.spec.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.spec.ts @@ -16,6 +16,8 @@ import { VarDirective } from '../../../utils/var.directive'; import { LinkService } from '../../../../core/cache/builders/link.service'; import { getMockLinkService } from '../../../mocks/link-service.mock'; import { By } from '@angular/platform-browser'; +import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; +import { DSONameServiceMock } from '../../../mocks/dso-name.service.mock'; let component: PoolSearchResultListElementComponent; let fixture: ComponentFixture; @@ -65,7 +67,8 @@ describe('PoolSearchResultListElementComponent', () => { declarations: [PoolSearchResultListElementComponent, VarDirective], providers: [ { provide: TruncatableService, useValue: {} }, - { provide: LinkService, useValue: linkService } + { provide: LinkService, useValue: linkService }, + { provide: DSONameService, useClass: DSONameServiceMock } ], schemas: [NO_ERRORS_SCHEMA] }).overrideComponent(PoolSearchResultListElementComponent, { diff --git a/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.ts b/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.ts index b130d5001c..fe4fa715ee 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.ts @@ -13,6 +13,7 @@ import { SearchResultListElementComponent } from '../../search-result-list-eleme import { TruncatableService } from '../../../truncatable/truncatable.service'; import { followLink } from '../../../utils/follow-link-config.model'; import { LinkService } from '../../../../core/cache/builders/link.service'; +import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; /** * This component renders pool task object for the search result in the list view. @@ -48,9 +49,10 @@ export class PoolSearchResultListElementComponent extends SearchResultListElemen constructor( protected linkService: LinkService, - protected truncatableService: TruncatableService + protected truncatableService: TruncatableService, + protected dsoNameService: DSONameService ) { - super(truncatableService); + super(truncatableService, dsoNameService); } /** diff --git a/src/app/shared/object-list/my-dspace-result-list-element/workflow-item-search-result/workflow-item-search-result-list-element.component.spec.ts b/src/app/shared/object-list/my-dspace-result-list-element/workflow-item-search-result/workflow-item-search-result-list-element.component.spec.ts index 3743a9bd22..61337b0cb3 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/workflow-item-search-result/workflow-item-search-result-list-element.component.spec.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/workflow-item-search-result/workflow-item-search-result-list-element.component.spec.ts @@ -16,6 +16,8 @@ import { createSuccessfulRemoteDataObject } from '../../../remote-data.utils'; import { TruncatableService } from '../../../truncatable/truncatable.service'; import { WorkflowItemSearchResultListElementComponent } from './workflow-item-search-result-list-element.component'; import { By } from '@angular/platform-browser'; +import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; +import { DSONameServiceMock } from '../../../mocks/dso-name.service.mock'; let component: WorkflowItemSearchResultListElementComponent; let fixture: ComponentFixture; @@ -67,6 +69,7 @@ describe('WorkflowItemSearchResultListElementComponent', () => { { provide: TruncatableService, useValue: {} }, { provide: ItemDataService, useValue: {} }, { provide: LinkService, useValue: linkService }, + { provide: DSONameService, useClass: DSONameServiceMock } ], schemas: [NO_ERRORS_SCHEMA] }).overrideComponent(WorkflowItemSearchResultListElementComponent, { diff --git a/src/app/shared/object-list/my-dspace-result-list-element/workflow-item-search-result/workflow-item-search-result-list-element.component.ts b/src/app/shared/object-list/my-dspace-result-list-element/workflow-item-search-result/workflow-item-search-result-list-element.component.ts index 432f69f28c..836a3ee268 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/workflow-item-search-result/workflow-item-search-result-list-element.component.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/workflow-item-search-result/workflow-item-search-result-list-element.component.ts @@ -15,6 +15,7 @@ import { WorkflowItemSearchResult } from '../../../object-collection/shared/work import { TruncatableService } from '../../../truncatable/truncatable.service'; import { followLink } from '../../../utils/follow-link-config.model'; import { SearchResultListElementComponent } from '../../search-result-list-element/search-result-list-element.component'; +import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; /** * This component renders workflowitem object for the search result in the list view. @@ -40,9 +41,10 @@ export class WorkflowItemSearchResultListElementComponent extends SearchResultLi constructor( protected truncatableService: TruncatableService, - protected linkService: LinkService + protected linkService: LinkService, + protected dsoNameService: DSONameService ) { - super(truncatableService); + super(truncatableService, dsoNameService); } /** diff --git a/src/app/shared/object-list/my-dspace-result-list-element/workspace-item-search-result/workspace-item-search-result-list-element.component.spec.ts b/src/app/shared/object-list/my-dspace-result-list-element/workspace-item-search-result/workspace-item-search-result-list-element.component.spec.ts index b1f2a2aeb9..87f5fd3733 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/workspace-item-search-result/workspace-item-search-result-list-element.component.spec.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/workspace-item-search-result/workspace-item-search-result-list-element.component.spec.ts @@ -16,6 +16,8 @@ import { createSuccessfulRemoteDataObject } from '../../../remote-data.utils'; import { TruncatableService } from '../../../truncatable/truncatable.service'; import { WorkspaceItemSearchResultListElementComponent } from './workspace-item-search-result-list-element.component'; import { By } from '@angular/platform-browser'; +import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; +import { DSONameServiceMock } from '../../../mocks/dso-name.service.mock'; let component: WorkspaceItemSearchResultListElementComponent; let fixture: ComponentFixture; @@ -66,6 +68,7 @@ describe('WorkspaceItemSearchResultListElementComponent', () => { { provide: TruncatableService, useValue: {} }, { provide: ItemDataService, useValue: {} }, { provide: LinkService, useValue: linkService }, + { provide: DSONameService, useClass: DSONameServiceMock } ], schemas: [NO_ERRORS_SCHEMA] }).overrideComponent(WorkspaceItemSearchResultListElementComponent, { diff --git a/src/app/shared/object-list/my-dspace-result-list-element/workspace-item-search-result/workspace-item-search-result-list-element.component.ts b/src/app/shared/object-list/my-dspace-result-list-element/workspace-item-search-result/workspace-item-search-result-list-element.component.ts index b9d89ef6ab..5edfc1929e 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/workspace-item-search-result/workspace-item-search-result-list-element.component.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/workspace-item-search-result/workspace-item-search-result-list-element.component.ts @@ -15,6 +15,7 @@ import { WorkspaceItemSearchResult } from '../../../object-collection/shared/wor import { TruncatableService } from '../../../truncatable/truncatable.service'; import { followLink } from '../../../utils/follow-link-config.model'; import { SearchResultListElementComponent } from '../../search-result-list-element/search-result-list-element.component'; +import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; /** * This component renders workspaceitem object for the search result in the list view. @@ -40,9 +41,10 @@ export class WorkspaceItemSearchResultListElementComponent extends SearchResultL constructor( protected truncatableService: TruncatableService, - protected linkService: LinkService + protected linkService: LinkService, + protected dsoNameService: DSONameService ) { - super(truncatableService); + super(truncatableService, dsoNameService); } /** diff --git a/src/app/shared/object-list/search-result-list-element/collection-search-result/collection-search-result-list-element.component.html b/src/app/shared/object-list/search-result-list-element/collection-search-result/collection-search-result-list-element.component.html index 1c081e2805..c98003cd1d 100644 --- a/src/app/shared/object-list/search-result-list-element/collection-search-result/collection-search-result-list-element.component.html +++ b/src/app/shared/object-list/search-result-list-element/collection-search-result/collection-search-result-list-element.component.html @@ -1,4 +1,4 @@ - - + +
diff --git a/src/app/shared/object-list/search-result-list-element/collection-search-result/collection-search-result-list-element.component.spec.ts b/src/app/shared/object-list/search-result-list-element/collection-search-result/collection-search-result-list-element.component.spec.ts index d50fdccb80..bdfe825c82 100644 --- a/src/app/shared/object-list/search-result-list-element/collection-search-result/collection-search-result-list-element.component.spec.ts +++ b/src/app/shared/object-list/search-result-list-element/collection-search-result/collection-search-result-list-element.component.spec.ts @@ -7,6 +7,8 @@ import { TruncatePipe } from '../../../utils/truncate.pipe'; import { Collection } from '../../../../core/shared/collection.model'; import { TruncatableService } from '../../../truncatable/truncatable.service'; import { CollectionSearchResult } from '../../../object-collection/shared/collection-search-result.model'; +import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; +import { DSONameServiceMock } from '../../../mocks/dso-name.service.mock'; let collectionSearchResultListElementComponent: CollectionSearchResultListElementComponent; let fixture: ComponentFixture; @@ -47,6 +49,7 @@ describe('CollectionSearchResultListElementComponent', () => { declarations: [CollectionSearchResultListElementComponent, TruncatePipe], providers: [ { provide: TruncatableService, useValue: truncatableServiceStub }, + { provide: DSONameService, useClass: DSONameServiceMock } ], schemas: [NO_ERRORS_SCHEMA] }).overrideComponent(CollectionSearchResultListElementComponent, { diff --git a/src/app/shared/object-list/search-result-list-element/community-search-result/community-search-result-list-element.component.html b/src/app/shared/object-list/search-result-list-element/community-search-result/community-search-result-list-element.component.html index 08b02d123a..e0f0319ffc 100644 --- a/src/app/shared/object-list/search-result-list-element/community-search-result/community-search-result-list-element.component.html +++ b/src/app/shared/object-list/search-result-list-element/community-search-result/community-search-result-list-element.component.html @@ -1,4 +1,4 @@ - - + +
diff --git a/src/app/shared/object-list/search-result-list-element/community-search-result/community-search-result-list-element.component.spec.ts b/src/app/shared/object-list/search-result-list-element/community-search-result/community-search-result-list-element.component.spec.ts index c5588f5371..529594671c 100644 --- a/src/app/shared/object-list/search-result-list-element/community-search-result/community-search-result-list-element.component.spec.ts +++ b/src/app/shared/object-list/search-result-list-element/community-search-result/community-search-result-list-element.component.spec.ts @@ -7,6 +7,8 @@ import { TruncatePipe } from '../../../utils/truncate.pipe'; import { Community } from '../../../../core/shared/community.model'; import { TruncatableService } from '../../../truncatable/truncatable.service'; import { CommunitySearchResult } from '../../../object-collection/shared/community-search-result.model'; +import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; +import { DSONameServiceMock } from '../../../mocks/dso-name.service.mock'; let communitySearchResultListElementComponent: CommunitySearchResultListElementComponent; let fixture: ComponentFixture; @@ -47,6 +49,7 @@ describe('CommunitySearchResultListElementComponent', () => { declarations: [CommunitySearchResultListElementComponent, TruncatePipe], providers: [ { provide: TruncatableService, useValue: truncatableServiceStub }, + { provide: DSONameService, useClass: DSONameServiceMock } ], schemas: [NO_ERRORS_SCHEMA] diff --git a/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.html b/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.html index 01a026eec9..80a4fd81fd 100644 --- a/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.html +++ b/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.html @@ -2,10 +2,10 @@ - + [routerLink]="[itemPageRoute]" class="lead item-list-title" + [innerHTML]="dsoTitle"> + diff --git a/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.spec.ts b/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.spec.ts index f8a8be415d..d32ac8058d 100644 --- a/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.spec.ts +++ b/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.spec.ts @@ -7,6 +7,8 @@ import { Item } from '../../../../../../core/shared/item.model'; import { TruncatePipe } from '../../../../../utils/truncate.pipe'; import { TruncatableService } from '../../../../../truncatable/truncatable.service'; import { ItemSearchResult } from '../../../../../object-collection/shared/item-search-result.model'; +import { DSONameService } from '../../../../../../core/breadcrumbs/dso-name.service'; +import { DSONameServiceMock, UNDEFINED_NAME } from '../../../../../mocks/dso-name.service.mock'; let publicationListElementComponent: ItemSearchResultListElementComponent; let fixture: ComponentFixture; @@ -53,23 +55,18 @@ const mockItemWithoutMetadata: ItemSearchResult = Object.assign(new ItemSearchRe indexableObject: Object.assign(new Item(), { bundles: observableOf({}), - metadata: { - 'dc.title': [ - { - language: 'en_US', - value: 'This is just another title' - } - ] - } + metadata: {} }) }); + describe('ItemListElementComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ItemSearchResultListElementComponent, TruncatePipe], providers: [ - { provide: TruncatableService, useValue: {} } + { provide: TruncatableService, useValue: {} }, + { provide: DSONameService, useClass: DSONameServiceMock } ], schemas: [NO_ERRORS_SCHEMA] @@ -179,4 +176,16 @@ describe('ItemListElementComponent', () => { expect(abstractField).toBeNull(); }); }); + + describe('When the item has no title', () => { + beforeEach(() => { + publicationListElementComponent.object = mockItemWithoutMetadata; + fixture.detectChanges(); + }); + + it('should show the fallback untitled translation', () => { + const titleField = fixture.debugElement.query(By.css('.item-list-title')); + expect(titleField.nativeElement.textContent.trim()).toEqual(UNDEFINED_NAME); + }); + }); }); diff --git a/src/app/shared/object-list/search-result-list-element/search-result-list-element.component.ts b/src/app/shared/object-list/search-result-list-element/search-result-list-element.component.ts index 3ad4f5e7e6..afd0363e7a 100644 --- a/src/app/shared/object-list/search-result-list-element/search-result-list-element.component.ts +++ b/src/app/shared/object-list/search-result-list-element/search-result-list-element.component.ts @@ -7,6 +7,7 @@ import { hasValue } from '../../empty.util'; import { AbstractListableElementComponent } from '../../object-collection/shared/object-collection-element/abstract-listable-element.component'; import { TruncatableService } from '../../truncatable/truncatable.service'; import { Metadata } from '../../../core/shared/metadata.utils'; +import { DSONameService } from '../../../core/breadcrumbs/dso-name.service'; @Component({ selector: 'ds-search-result-list-element', @@ -17,8 +18,9 @@ export class SearchResultListElementComponent, K exten * The DSpaceObject of the search result */ dso: K; + dsoTitle: string; - public constructor(protected truncatableService: TruncatableService) { + public constructor(protected truncatableService: TruncatableService, protected dsoNameService: DSONameService) { super(); } @@ -28,6 +30,7 @@ export class SearchResultListElementComponent, K exten ngOnInit(): void { if (hasValue(this.object)) { this.dso = this.object.indexableObject; + this.dsoTitle = this.dsoNameService.getName(this.dso); } } diff --git a/src/app/shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.spec.ts b/src/app/shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.spec.ts index 3d402ca0e4..31ad68ec63 100644 --- a/src/app/shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.spec.ts +++ b/src/app/shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.spec.ts @@ -10,6 +10,8 @@ import { LinkService } from '../../../core/cache/builders/link.service'; import { createSuccessfulRemoteDataObject$ } from '../../remote-data.utils'; import { HALResource } from '../../../core/shared/hal-resource.model'; import { ChildHALResource } from '../../../core/shared/child-hal-resource.model'; +import { DSONameService } from '../../../core/breadcrumbs/dso-name.service'; +import { DSONameServiceMock } from '../../mocks/dso-name.service.mock'; export function createSidebarSearchListElementTests( componentClass: any, @@ -38,6 +40,7 @@ export function createSidebarSearchListElementTests( providers: [ { provide: TruncatableService, useValue: {} }, { provide: LinkService, useValue: linkService }, + { provide: DSONameService, useClass: DSONameServiceMock }, ...extraProviders ], schemas: [NO_ERRORS_SCHEMA] diff --git a/src/app/shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.ts b/src/app/shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.ts index 478bf50771..a0d70a43c7 100644 --- a/src/app/shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.ts +++ b/src/app/shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.ts @@ -12,6 +12,7 @@ import { followLink } from '../../utils/follow-link-config.model'; import { RemoteData } from '../../../core/data/remote-data'; import { of as observableOf } from 'rxjs'; import { Context } from '../../../core/shared/context.model'; +import { DSONameService } from '../../../core/breadcrumbs/dso-name.service'; @Component({ selector: 'ds-sidebar-search-list-element', @@ -39,8 +40,10 @@ export class SidebarSearchListElementComponent, K exte description: string; public constructor(protected truncatableService: TruncatableService, - protected linkService: LinkService) { - super(truncatableService); + protected linkService: LinkService, + protected dsoNameService: DSONameService + ) { + super(truncatableService, dsoNameService); } /** diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index a16d41a9e3..f60abfb253 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1098,6 +1098,10 @@ + "dso.name.untitled": "Untitled", + + + "dso-selector.create.collection.head": "New collection", "dso-selector.create.collection.sub-level": "Create a new collection in", From 01def4e8e4cd99193e22cf85c9fbca8cc0c6bfc3 Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Wed, 17 Mar 2021 12:35:33 +0100 Subject: [PATCH 44/56] Fixed other item types title --- ...ournal-issue-search-result-list-element.component.html | 8 ++++---- ...urnal-volume-search-result-list-element.component.html | 8 ++++---- .../journal-search-result-list-element.component.html | 8 ++++---- .../project-search-result-list-element.component.html | 8 ++++---- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.html b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.html index 0e783a4843..fa4c06d36a 100644 --- a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.html +++ b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.html @@ -1,11 +1,11 @@ + [routerLink]="[itemPageRoute]" class="lead item-list-title" + [innerHTML]="dsoTitle"> + class="lead item-list-title" + [innerHTML]="dsoTitle"> + [routerLink]="[itemPageRoute]" class="lead item-list-title" + [innerHTML]="dsoTitle"> + class="lead item-list-title" + [innerHTML]="dsoTitle"> + [routerLink]="[itemPageRoute]" class="lead item-list-title" + [innerHTML]="dsoTitle"> + class="lead item-list-title" + [innerHTML]="dsoTitle"> + [routerLink]="[itemPageRoute]" class="lead item-list-title" + [innerHTML]="dsoTitle"> + class="lead item-list-title" + [innerHTML]="dsoTitle"> From 3a0dc277e48dc7ab3b4e90177646e2d68331761c Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Wed, 17 Mar 2021 13:33:27 +0100 Subject: [PATCH 45/56] fix lgtm issues --- .../org-unit-search-result-list-submission-element.component.ts | 1 - .../person-search-result-list-submission-element.component.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.ts b/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.ts index 091549dcea..e3cc8fccd3 100644 --- a/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.ts +++ b/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.ts @@ -19,7 +19,6 @@ import { MetadataValue } from '../../../../../core/shared/metadata.models'; import { ItemDataService } from '../../../../../core/data/item-data.service'; import { SelectableListService } from '../../../../../shared/object-list/selectable-list/selectable-list.service'; import { NameVariantModalComponent } from '../../name-variant-modal/name-variant-modal.component'; -import { LinkService } from '../../../../../core/cache/builders/link.service'; import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; @listableObjectComponent('OrgUnitSearchResult', ViewMode.ListElement, Context.EntitySearchModal) diff --git a/src/app/entity-groups/research-entities/submission/item-list-elements/person/person-search-result-list-submission-element.component.ts b/src/app/entity-groups/research-entities/submission/item-list-elements/person/person-search-result-list-submission-element.component.ts index 2d991d5c64..64cf73cfb9 100644 --- a/src/app/entity-groups/research-entities/submission/item-list-elements/person/person-search-result-list-submission-element.component.ts +++ b/src/app/entity-groups/research-entities/submission/item-list-elements/person/person-search-result-list-submission-element.component.ts @@ -19,7 +19,6 @@ import { NameVariantModalComponent } from '../../name-variant-modal/name-variant import { MetadataValue } from '../../../../../core/shared/metadata.models'; import { ItemDataService } from '../../../../../core/data/item-data.service'; import { SelectableListService } from '../../../../../shared/object-list/selectable-list/selectable-list.service'; -import { LinkService } from '../../../../../core/cache/builders/link.service'; import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; @listableObjectComponent('PersonSearchResult', ViewMode.ListElement, Context.EntitySearchModalWithNameVariants) From 8d6156df37558607cb63061fdde5c1e4d703e3fd Mon Sep 17 00:00:00 2001 From: lotte Date: Tue, 23 Mar 2021 16:30:38 +0100 Subject: [PATCH 46/56] 77818: edit page permission fixes --- .../edit-item-page.component.html | 15 ++++++--- .../edit-item-page.component.ts | 29 ++++++++++++----- .../edit-item-page.routing.module.ts | 25 ++++++++++----- .../item-page-edit-metadata.guard.ts | 31 +++++++++++++++++++ .../+item-page/item-page-routing.module.ts | 4 +-- .../dso-page-feature.guard.ts | 12 ++++++- .../dso-page-edit-button.component.html | 2 +- src/assets/i18n/en.json5 | 2 ++ 8 files changed, 97 insertions(+), 23 deletions(-) create mode 100644 src/app/+item-page/item-page-edit-metadata.guard.ts diff --git a/src/app/+item-page/edit-item-page/edit-item-page.component.html b/src/app/+item-page/edit-item-page/edit-item-page.component.html index ca1c809cd9..6b6f74309c 100644 --- a/src/app/+item-page/edit-item-page/edit-item-page.component.html +++ b/src/app/+item-page/edit-item-page/edit-item-page.component.html @@ -5,11 +5,18 @@
diff --git a/src/app/+item-page/edit-item-page/edit-item-page.component.ts b/src/app/+item-page/edit-item-page/edit-item-page.component.ts index ec7cdb022d..d0b0fe9356 100644 --- a/src/app/+item-page/edit-item-page/edit-item-page.component.ts +++ b/src/app/+item-page/edit-item-page/edit-item-page.component.ts @@ -1,12 +1,13 @@ import { fadeIn, fadeInOut } from '../../shared/animations/fade'; -import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; -import { ActivatedRoute, Router } from '@angular/router'; +import { ChangeDetectionStrategy, Component, Injector, OnInit } from '@angular/core'; +import { ActivatedRoute, CanActivate, Route, Router } from '@angular/router'; import { RemoteData } from '../../core/data/remote-data'; import { Item } from '../../core/shared/item.model'; -import { Observable } from 'rxjs'; -import { map } from 'rxjs/operators'; +import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs'; +import { map, tap } from 'rxjs/operators'; import { isNotEmpty } from '../../shared/empty.util'; import { getItemPageRoute } from '../item-page-routing-paths'; +import { GenericConstructor } from '../../core/shared/generic-constructor'; @Component({ selector: 'ds-edit-item-page', @@ -35,9 +36,9 @@ export class EditItemPageComponent implements OnInit { /** * All possible page outlet strings */ - pages: string[]; + pages: { page: string, enabled: Observable }[]; - constructor(private route: ActivatedRoute, private router: Router) { + constructor(private route: ActivatedRoute, private router: Router, private injector: Injector) { this.router.events.subscribe(() => { this.currentPage = this.route.snapshot.firstChild.routeConfig.path; }); @@ -45,8 +46,20 @@ export class EditItemPageComponent implements OnInit { ngOnInit(): void { this.pages = this.route.routeConfig.children - .map((child: any) => child.path) - .filter((path: string) => isNotEmpty(path)); // ignore reroutes + .filter((child: Route) => isNotEmpty(child.path)) + .map((child: Route) => { + let enabled = observableOf(true); + if (isNotEmpty(child.canActivate)) { + enabled = observableCombineLatest(child.canActivate.map((guardConstructor: GenericConstructor) => { + const guard: CanActivate = this.injector.get(guardConstructor); + return guard.canActivate(this.route.snapshot, this.router.routerState.snapshot); + }) + ).pipe( + map((canActivateOutcomes: any[]) => canActivateOutcomes.every((e) => e === true)) + ); + } + return { page: child.path, enabled: enabled }; + }); // ignore reroutes this.itemRD$ = this.route.data.pipe(map((data) => data.dso)); } diff --git a/src/app/+item-page/edit-item-page/edit-item-page.routing.module.ts b/src/app/+item-page/edit-item-page/edit-item-page.routing.module.ts index da10d33add..0e36cc9894 100644 --- a/src/app/+item-page/edit-item-page/edit-item-page.routing.module.ts +++ b/src/app/+item-page/edit-item-page/edit-item-page.routing.module.ts @@ -31,6 +31,9 @@ import { } from './edit-item-page.routing-paths'; import { ItemPageReinstateGuard } from './item-page-reinstate.guard'; import { ItemPageWithdrawGuard } from './item-page-withdraw.guard'; +import { ItemPageEditMetadataGuard } from '../item-page-edit-metadata.guard'; +import { ItemPageAdministratorGuard } from '../item-page-administrator.guard'; +import { AuthenticatedGuard } from '../../core/auth/authenticated.guard'; /** * Routing module that handles the routing for the Edit Item page administrator functionality @@ -57,22 +60,26 @@ import { ItemPageWithdrawGuard } from './item-page-withdraw.guard'; { path: 'status', component: ItemStatusComponent, - data: { title: 'item.edit.tabs.status.title', showBreadcrumbs: true } + data: { title: 'item.edit.tabs.status.title', showBreadcrumbs: true }, + canActivate: [ItemPageAdministratorGuard] }, { path: 'bitstreams', component: ItemBitstreamsComponent, - data: { title: 'item.edit.tabs.bitstreams.title', showBreadcrumbs: true } + data: { title: 'item.edit.tabs.bitstreams.title', showBreadcrumbs: true }, + canActivate: [ItemPageAdministratorGuard] }, { path: 'metadata', component: ItemMetadataComponent, - data: { title: 'item.edit.tabs.metadata.title', showBreadcrumbs: true } + data: { title: 'item.edit.tabs.metadata.title', showBreadcrumbs: true }, + canActivate: [ItemPageEditMetadataGuard] }, { path: 'relationships', component: ItemRelationshipsComponent, - data: { title: 'item.edit.tabs.relationships.title', showBreadcrumbs: true } + data: { title: 'item.edit.tabs.relationships.title', showBreadcrumbs: true }, + canActivate: [ItemPageEditMetadataGuard] }, /* TODO - uncomment & fix when view page exists { @@ -89,12 +96,14 @@ import { ItemPageWithdrawGuard } from './item-page-withdraw.guard'; { path: 'versionhistory', component: ItemVersionHistoryComponent, - data: { title: 'item.edit.tabs.versionhistory.title', showBreadcrumbs: true } + data: { title: 'item.edit.tabs.versionhistory.title', showBreadcrumbs: true }, + canActivate: [ItemPageAdministratorGuard] }, { path: 'mapper', component: ItemCollectionMapperComponent, - data: { title: 'item.edit.tabs.item-mapper.title', showBreadcrumbs: true } + data: { title: 'item.edit.tabs.item-mapper.title', showBreadcrumbs: true }, + canActivate: [ItemPageAdministratorGuard] } ] }, @@ -165,7 +174,9 @@ import { ItemPageWithdrawGuard } from './item-page-withdraw.guard'; ResourcePolicyResolver, ResourcePolicyTargetResolver, ItemPageReinstateGuard, - ItemPageWithdrawGuard + ItemPageWithdrawGuard, + ItemPageAdministratorGuard, + ItemPageEditMetadataGuard, ] }) export class EditItemPageRoutingModule { diff --git a/src/app/+item-page/item-page-edit-metadata.guard.ts b/src/app/+item-page/item-page-edit-metadata.guard.ts new file mode 100644 index 0000000000..821ca44c30 --- /dev/null +++ b/src/app/+item-page/item-page-edit-metadata.guard.ts @@ -0,0 +1,31 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router'; +import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service'; +import { ItemPageResolver } from './item-page.resolver'; +import { Item } from '../core/shared/item.model'; +import { DsoPageFeatureGuard } from '../core/data/feature-authorization/feature-authorization-guard/dso-page-feature.guard'; +import { Observable, of as observableOf } from 'rxjs'; +import { FeatureID } from '../core/data/feature-authorization/feature-id'; +import { AuthService } from '../core/auth/auth.service'; + +@Injectable({ + providedIn: 'root' +}) +/** + * Guard for preventing unauthorized access to certain {@link Item} pages requiring edit metadata rights + */ +export class ItemPageEditMetadataGuard extends DsoPageFeatureGuard { + constructor(protected resolver: ItemPageResolver, + protected authorizationService: AuthorizationDataService, + protected router: Router, + protected authService: AuthService) { + super(resolver, authorizationService, router, authService); + } + + /** + * Check administrator authorization rights + */ + getFeatureID(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { + return observableOf(FeatureID.CanEditMetadata); + } +} diff --git a/src/app/+item-page/item-page-routing.module.ts b/src/app/+item-page/item-page-routing.module.ts index b04a783b2f..80564b5d21 100644 --- a/src/app/+item-page/item-page-routing.module.ts +++ b/src/app/+item-page/item-page-routing.module.ts @@ -13,6 +13,7 @@ import { UPLOAD_BITSTREAM_PATH, ITEM_EDIT_PATH } from './item-page-routing-paths import { ItemPageAdministratorGuard } from './item-page-administrator.guard'; import { MenuItemType } from '../shared/menu/initial-menus-state'; import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model'; +import { ItemPageEditMetadataGuard } from './item-page-edit-metadata.guard'; @NgModule({ imports: [ @@ -38,7 +39,6 @@ import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model'; path: ITEM_EDIT_PATH, loadChildren: () => import('./edit-item-page/edit-item-page.module') .then((m) => m.EditItemPageModule), - canActivate: [ItemPageAdministratorGuard] }, { path: UPLOAD_BITSTREAM_PATH, @@ -68,7 +68,7 @@ import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model'; ItemBreadcrumbResolver, DSOBreadcrumbsService, LinkService, - ItemPageAdministratorGuard + ItemPageAdministratorGuard, ] }) diff --git a/src/app/core/data/feature-authorization/feature-authorization-guard/dso-page-feature.guard.ts b/src/app/core/data/feature-authorization/feature-authorization-guard/dso-page-feature.guard.ts index c9ac7155d4..c85af60913 100644 --- a/src/app/core/data/feature-authorization/feature-authorization-guard/dso-page-feature.guard.ts +++ b/src/app/core/data/feature-authorization/feature-authorization-guard/dso-page-feature.guard.ts @@ -7,6 +7,7 @@ import { map } from 'rxjs/operators'; import { DSpaceObject } from '../../../shared/dspace-object.model'; import { FeatureAuthorizationGuard } from './feature-authorization.guard'; import { AuthService } from '../../../auth/auth.service'; +import { hasNoValue, hasValue } from '../../../../shared/empty.util'; /** * Abstract Guard for preventing unauthorized access to {@link DSpaceObject} pages that require rights for a specific feature @@ -24,9 +25,18 @@ export abstract class DsoPageFeatureGuard extends Featur * Check authorization rights for the object resolved using the provided resolver */ getObjectUrl(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return (this.resolver.resolve(route, state) as Observable>).pipe( + const routeWithObjectID = this.getRouteWithDSOId(route); + return (this.resolver.resolve(routeWithObjectID, state) as Observable>).pipe( getAllSucceededRemoteDataPayload(), map((dso) => dso.self) ); } + + protected getRouteWithDSOId(route: ActivatedRouteSnapshot): ActivatedRouteSnapshot { + let routeWithDSOId = route; + while (hasNoValue(routeWithDSOId.params.id) && hasValue(routeWithDSOId.parent)) { + routeWithDSOId = routeWithDSOId.parent; + } + return routeWithDSOId; + } } diff --git a/src/app/shared/dso-page/dso-page-edit-button/dso-page-edit-button.component.html b/src/app/shared/dso-page/dso-page-edit-button/dso-page-edit-button.component.html index 36661c895a..d845f852c8 100644 --- a/src/app/shared/dso-page/dso-page-edit-button/dso-page-edit-button.component.html +++ b/src/app/shared/dso-page/dso-page-edit-button/dso-page-edit-button.component.html @@ -1,5 +1,5 @@ diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index f60abfb253..e81df2f336 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1471,6 +1471,8 @@ "item.edit.breadcrumbs": "Edit Item", + "item.edit.tabs.disabled.tooltip": "You don't have permission to access this tab", + "item.edit.tabs.mapper.head": "Collection Mapper", From 11f3962326d62ff91344bdb5b9f9a1b5a50f0bee Mon Sep 17 00:00:00 2001 From: lotte Date: Wed, 24 Mar 2021 14:33:32 +0100 Subject: [PATCH 47/56] added tests --- .../edit-item-page.component.spec.ts | 107 ++++++++++++++++++ .../dso-page-feature.guard.spec.ts | 21 +++- 2 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 src/app/+item-page/edit-item-page/edit-item-page.component.spec.ts diff --git a/src/app/+item-page/edit-item-page/edit-item-page.component.spec.ts b/src/app/+item-page/edit-item-page/edit-item-page.component.spec.ts new file mode 100644 index 0000000000..6c9f79c36b --- /dev/null +++ b/src/app/+item-page/edit-item-page/edit-item-page.component.spec.ts @@ -0,0 +1,107 @@ +import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { TranslateLoaderMock } from '../../shared/mocks/translate-loader.mock'; +import { ChangeDetectionStrategy, Injector, NO_ERRORS_SCHEMA } from '@angular/core'; +import { ActivatedRoute, ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree } from '@angular/router'; +import { EditItemPageComponent } from './edit-item-page.component'; +import { Observable, of as observableOf } from 'rxjs'; +import { By } from '@angular/platform-browser'; +import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils'; +import { Item } from '../../core/shared/item.model'; + +describe('ItemPageComponent', () => { + let comp: EditItemPageComponent; + let fixture: ComponentFixture; + + class AcceptAllGuard implements CanActivate { + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable | Promise | boolean | UrlTree { + return observableOf(true); + } + } + + // tslint:disable-next-line:max-classes-per-file + class AcceptNoneGuard implements CanActivate { + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable | Promise | boolean | UrlTree { + console.log('BLA'); + return observableOf(false); + } + } + + const accesiblePages = ['accessible']; + const inaccesiblePages = ['inaccessible', 'inaccessibleDoubleGuard']; + const mockRoute = { + snapshot: { + firstChild: { + routeConfig: { + path: accesiblePages[0] + } + }, + routerState: { + snapshot: undefined + } + }, + routeConfig: { + children: [ + { + path: accesiblePages[0], + canActivate: [AcceptAllGuard] + }, { + path: inaccesiblePages[0], + canActivate: [AcceptNoneGuard] + }, { + path: inaccesiblePages[1], + canActivate: [AcceptAllGuard, AcceptNoneGuard] + }, + ] + }, + data: observableOf({dso: createSuccessfulRemoteDataObject(new Item())}) + }; + + const mockRouter = { + routerState: { + snapshot: undefined + }, + events: observableOf(undefined) + }; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + })], + declarations: [EditItemPageComponent], + providers: [ + { provide: ActivatedRoute, useValue: mockRoute }, + { provide: Router, useValue: mockRouter }, + AcceptAllGuard, + AcceptNoneGuard, + ], + + schemas: [NO_ERRORS_SCHEMA] + }).overrideComponent(EditItemPageComponent, { + set: { changeDetection: ChangeDetectionStrategy.Default } + }).compileComponents(); + })); + + beforeEach(waitForAsync(() => { + fixture = TestBed.createComponent(EditItemPageComponent); + comp = fixture.componentInstance; + spyOn((comp as any).injector, 'get').and.callFake((a) => new a()); + fixture.detectChanges(); + })); + + describe('ngOnInit', () => { + it('should enable tabs that the user can activate', fakeAsync(() => { + const enabledItems = fixture.debugElement.queryAll(By.css('a.nav-link')); + expect(enabledItems.length).toBe(accesiblePages.length); + })); + + it('should disable tabs that the user can not activate', () => { + const disabledItems = fixture.debugElement.queryAll(By.css('button.nav-link.disabled')); + expect(disabledItems.length).toBe(inaccesiblePages.length); + }); + }); +}); diff --git a/src/app/core/data/feature-authorization/feature-authorization-guard/dso-page-feature.guard.spec.ts b/src/app/core/data/feature-authorization/feature-authorization-guard/dso-page-feature.guard.spec.ts index 4041e588ed..f98e3f1837 100644 --- a/src/app/core/data/feature-authorization/feature-authorization-guard/dso-page-feature.guard.spec.ts +++ b/src/app/core/data/feature-authorization/feature-authorization-guard/dso-page-feature.guard.spec.ts @@ -32,6 +32,8 @@ describe('DsoPageAdministratorGuard', () => { let authService: AuthService; let resolver: Resolve>; let object: DSpaceObject; + let route; + let parentRoute; function init() { object = { @@ -50,6 +52,16 @@ describe('DsoPageAdministratorGuard', () => { authService = jasmine.createSpyObj('authService', { isAuthenticated: observableOf(true) }); + parentRoute = { + params: { + id: '3e1a5327-dabb-41ff-af93-e6cab9d032f0' + } + }; + route = { + params: { + }, + parent: parentRoute + }; guard = new DsoPageFeatureGuardImpl(resolver, authorizationService, router, authService, undefined); } @@ -59,10 +71,17 @@ describe('DsoPageAdministratorGuard', () => { describe('getObjectUrl', () => { it('should return the resolved object\'s selflink', (done) => { - guard.getObjectUrl(undefined, undefined).subscribe((selflink) => { + guard.getObjectUrl(route, undefined).subscribe((selflink) => { expect(selflink).toEqual(object.self); done(); }); }); }); + + describe('getRouteWithDSOId', () => { + it('should return the route that has the UUID of the DSO', () => { + const foundRoute = (guard as any).getRouteWithDSOId(route); + expect(foundRoute).toBe(parentRoute); + }); + }); }); From 650f7106c24d30836157fd94a4aec9a1648d9c68 Mon Sep 17 00:00:00 2001 From: lotte Date: Wed, 24 Mar 2021 14:36:23 +0100 Subject: [PATCH 48/56] removed unused imports --- .../edit-item-page/edit-item-page.component.spec.ts | 4 ++-- src/app/+item-page/edit-item-page/edit-item-page.component.ts | 2 +- src/app/+item-page/item-page-routing.module.ts | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/app/+item-page/edit-item-page/edit-item-page.component.spec.ts b/src/app/+item-page/edit-item-page/edit-item-page.component.spec.ts index 6c9f79c36b..cff9104c01 100644 --- a/src/app/+item-page/edit-item-page/edit-item-page.component.spec.ts +++ b/src/app/+item-page/edit-item-page/edit-item-page.component.spec.ts @@ -1,7 +1,7 @@ -import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; +import { ComponentFixture, fakeAsync, TestBed, waitForAsync } from '@angular/core/testing'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { TranslateLoaderMock } from '../../shared/mocks/translate-loader.mock'; -import { ChangeDetectionStrategy, Injector, NO_ERRORS_SCHEMA } from '@angular/core'; +import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; import { ActivatedRoute, ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree } from '@angular/router'; import { EditItemPageComponent } from './edit-item-page.component'; import { Observable, of as observableOf } from 'rxjs'; diff --git a/src/app/+item-page/edit-item-page/edit-item-page.component.ts b/src/app/+item-page/edit-item-page/edit-item-page.component.ts index d0b0fe9356..5f1889a404 100644 --- a/src/app/+item-page/edit-item-page/edit-item-page.component.ts +++ b/src/app/+item-page/edit-item-page/edit-item-page.component.ts @@ -4,7 +4,7 @@ import { ActivatedRoute, CanActivate, Route, Router } from '@angular/router'; import { RemoteData } from '../../core/data/remote-data'; import { Item } from '../../core/shared/item.model'; import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs'; -import { map, tap } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; import { isNotEmpty } from '../../shared/empty.util'; import { getItemPageRoute } from '../item-page-routing-paths'; import { GenericConstructor } from '../../core/shared/generic-constructor'; diff --git a/src/app/+item-page/item-page-routing.module.ts b/src/app/+item-page/item-page-routing.module.ts index 80564b5d21..8d2853735b 100644 --- a/src/app/+item-page/item-page-routing.module.ts +++ b/src/app/+item-page/item-page-routing.module.ts @@ -9,11 +9,10 @@ import { ItemBreadcrumbResolver } from '../core/breadcrumbs/item-breadcrumb.reso import { DSOBreadcrumbsService } from '../core/breadcrumbs/dso-breadcrumbs.service'; import { LinkService } from '../core/cache/builders/link.service'; import { UploadBitstreamComponent } from './bitstreams/upload/upload-bitstream.component'; -import { UPLOAD_BITSTREAM_PATH, ITEM_EDIT_PATH } from './item-page-routing-paths'; +import { ITEM_EDIT_PATH, UPLOAD_BITSTREAM_PATH } from './item-page-routing-paths'; import { ItemPageAdministratorGuard } from './item-page-administrator.guard'; import { MenuItemType } from '../shared/menu/initial-menus-state'; import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model'; -import { ItemPageEditMetadataGuard } from './item-page-edit-metadata.guard'; @NgModule({ imports: [ From 029308b52fb2c5ee51de8a0a3c7c466c4179610b Mon Sep 17 00:00:00 2001 From: lotte Date: Wed, 24 Mar 2021 15:06:27 +0100 Subject: [PATCH 49/56] Added missing typedoc --- src/app/+item-page/item-page-edit-metadata.guard.ts | 2 +- .../feature-authorization-guard/dso-page-feature.guard.ts | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/app/+item-page/item-page-edit-metadata.guard.ts b/src/app/+item-page/item-page-edit-metadata.guard.ts index 821ca44c30..a9b870b1cd 100644 --- a/src/app/+item-page/item-page-edit-metadata.guard.ts +++ b/src/app/+item-page/item-page-edit-metadata.guard.ts @@ -23,7 +23,7 @@ export class ItemPageEditMetadataGuard extends DsoPageFeatureGuard { } /** - * Check administrator authorization rights + * Check edit metadata authorization rights */ getFeatureID(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { return observableOf(FeatureID.CanEditMetadata); diff --git a/src/app/core/data/feature-authorization/feature-authorization-guard/dso-page-feature.guard.ts b/src/app/core/data/feature-authorization/feature-authorization-guard/dso-page-feature.guard.ts index c85af60913..c50dd7f95d 100644 --- a/src/app/core/data/feature-authorization/feature-authorization-guard/dso-page-feature.guard.ts +++ b/src/app/core/data/feature-authorization/feature-authorization-guard/dso-page-feature.guard.ts @@ -32,6 +32,10 @@ export abstract class DsoPageFeatureGuard extends Featur ); } + /** + * Method to resolve resolve (parent) route that contains the UUID of the DSO + * @param route The current route + */ protected getRouteWithDSOId(route: ActivatedRouteSnapshot): ActivatedRouteSnapshot { let routeWithDSOId = route; while (hasNoValue(routeWithDSOId.params.id) && hasValue(routeWithDSOId.parent)) { From 623688304ddf674420912415dd8a730d6f4acf11 Mon Sep 17 00:00:00 2001 From: lotte Date: Wed, 24 Mar 2021 15:30:54 +0100 Subject: [PATCH 50/56] removed unused import --- .../edit-item-page/edit-item-page.routing.module.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/app/+item-page/edit-item-page/edit-item-page.routing.module.ts b/src/app/+item-page/edit-item-page/edit-item-page.routing.module.ts index 0e36cc9894..b7d650d8c3 100644 --- a/src/app/+item-page/edit-item-page/edit-item-page.routing.module.ts +++ b/src/app/+item-page/edit-item-page/edit-item-page.routing.module.ts @@ -22,10 +22,10 @@ import { ResourcePolicyEditComponent } from '../../shared/resource-policies/edit import { I18nBreadcrumbsService } from '../../core/breadcrumbs/i18n-breadcrumbs.service'; import { ITEM_EDIT_AUTHORIZATIONS_PATH, - ITEM_EDIT_MOVE_PATH, ITEM_EDIT_DELETE_PATH, - ITEM_EDIT_PUBLIC_PATH, + ITEM_EDIT_MOVE_PATH, ITEM_EDIT_PRIVATE_PATH, + ITEM_EDIT_PUBLIC_PATH, ITEM_EDIT_REINSTATE_PATH, ITEM_EDIT_WITHDRAW_PATH } from './edit-item-page.routing-paths'; @@ -33,7 +33,6 @@ import { ItemPageReinstateGuard } from './item-page-reinstate.guard'; import { ItemPageWithdrawGuard } from './item-page-withdraw.guard'; import { ItemPageEditMetadataGuard } from '../item-page-edit-metadata.guard'; import { ItemPageAdministratorGuard } from '../item-page-administrator.guard'; -import { AuthenticatedGuard } from '../../core/auth/authenticated.guard'; /** * Routing module that handles the routing for the Edit Item page administrator functionality From 2f7060a7528048b6dbf138414291ec88ee32436e Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Mon, 15 Mar 2021 14:07:43 +0100 Subject: [PATCH 51/56] fix issue where stale remotedata objects could be returned first from findBy methods on dataservices --- src/app/core/data/data.service.spec.ts | 484 ++++++++++++++++++++++++- src/app/core/data/data.service.ts | 62 ++-- 2 files changed, 519 insertions(+), 27 deletions(-) diff --git a/src/app/core/data/data.service.spec.ts b/src/app/core/data/data.service.spec.ts index a8bbfa79a0..88b15754af 100644 --- a/src/app/core/data/data.service.spec.ts +++ b/src/app/core/data/data.service.spec.ts @@ -20,6 +20,9 @@ import { getMockRequestService } from '../../shared/mocks/request.service.mock'; import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service.stub'; import { RequestParam } from '../cache/models/request-param.model'; import { getMockRemoteDataBuildService } from '../../shared/mocks/remote-data-build.service.mock'; +import { TestScheduler } from 'rxjs/testing'; +import { RemoteData } from './remote-data'; +import { RequestEntryState } from './request.reducer'; const endpoint = 'https://rest.api/core'; @@ -63,6 +66,10 @@ describe('DataService', () => { let comparator; let objectCache; let store; + let selfLink; + let linksToFollow; + let testScheduler; + let remoteDataMocks; function initTestService(): TestService { requestService = getMockRequestService(); @@ -81,6 +88,34 @@ describe('DataService', () => { } } as any; store = {} as Store; + selfLink = 'https://rest.api/endpoint/1698f1d3-be98-4c51-9fd8-6bfedcbd59b7'; + linksToFollow = [ + followLink('a'), + followLink('b') + ]; + + testScheduler = new TestScheduler((actual, expected) => { + // asserting the two objects are equal + // e.g. using chai. + expect(actual).toEqual(expected); + }); + + const timeStamp = new Date().getTime(); + const msToLive = 15 * 60 * 1000; + const payload = { foo: 'bar' }; + const statusCodeSuccess = 200; + const statusCodeError = 404; + const errorMessage = 'not found'; + remoteDataMocks = { + RequestPending: new RemoteData(undefined, msToLive, timeStamp, RequestEntryState.RequestPending, undefined, undefined, undefined), + ResponsePending: new RemoteData(undefined, msToLive, timeStamp, RequestEntryState.ResponsePending, undefined, undefined, undefined), + Success: new RemoteData(timeStamp, msToLive, timeStamp, RequestEntryState.Success, undefined, payload, statusCodeSuccess), + SuccessStale: new RemoteData(timeStamp, msToLive, timeStamp, RequestEntryState.SuccessStale, undefined, payload, statusCodeSuccess), + Error: new RemoteData(timeStamp, msToLive, timeStamp, RequestEntryState.Error, errorMessage, undefined, statusCodeError), + ErrorStale: new RemoteData(timeStamp, msToLive, timeStamp, RequestEntryState.ErrorStale, errorMessage, undefined, statusCodeError), + }; + + return new TestService( requestService, rdbService, @@ -307,14 +342,12 @@ describe('DataService', () => { describe('update', () => { let operations; - let selfLink; let dso; let dso2; const name1 = 'random string'; const name2 = 'another random string'; beforeEach(() => { operations = [{ op: 'replace', path: '/0/value', value: name2 } as Operation]; - selfLink = 'https://rest.api/endpoint/1698f1d3-be98-4c51-9fd8-6bfedcbd59b7'; dso = Object.assign(new DSpaceObject(), { _links: { self: { href: selfLink } }, @@ -340,5 +373,452 @@ describe('DataService', () => { expect(objectCache.addPatch).not.toHaveBeenCalled(); }); }); + + describe(`reRequestStaleRemoteData`, () => { + let callback: jasmine.Spy; + + beforeEach(() => { + callback = jasmine.createSpy(); + }); + + + describe(`when shouldReRequest is false`, () => { + it(`shouldn't do anything`, () => { + testScheduler.run(({ cold, expectObservable, flush }) => { + const expected = 'a-b-c-d-e-f'; + const values = { + a: remoteDataMocks.RequestPending, + b: remoteDataMocks.ResponsePending, + c: remoteDataMocks.Success, + d: remoteDataMocks.SuccessStale, + e: remoteDataMocks.Error, + f: remoteDataMocks.ErrorStale, + }; + + expectObservable((service as any).reRequestStaleRemoteData(false, callback)(cold(expected, values))).toBe(expected, values); + // since the callback happens in a tap(), flush to ensure it has been executed + flush(); + expect(callback).not.toHaveBeenCalled(); + }); + }); + }); + + describe(`when shouldReRequest is true`, () => { + it(`should call the callback for stale RemoteData objects, but still pass the source observable unmodified`, () => { + testScheduler.run(({ cold, expectObservable, flush }) => { + const expected = 'a-b'; + const values = { + a: remoteDataMocks.SuccessStale, + b: remoteDataMocks.ErrorStale, + }; + + expectObservable((service as any).reRequestStaleRemoteData(true, callback)(cold(expected, values))).toBe(expected, values); + // since the callback happens in a tap(), flush to ensure it has been executed + flush(); + expect(callback).toHaveBeenCalledTimes(2); + }); + }); + + it(`should only call the callback for stale RemoteData objects if something is subscribed to it`, (done) => { + testScheduler.run(({ cold, expectObservable }) => { + const expected = 'a'; + const values = { + a: remoteDataMocks.SuccessStale, + }; + + const result$ = (service as any).reRequestStaleRemoteData(true, callback)(cold(expected, values)); + expectObservable(result$).toBe(expected, values); + expect(callback).not.toHaveBeenCalled(); + result$.subscribe(() => { + expect(callback).toHaveBeenCalled(); + done(); + }); + }); + }); + + it(`shouldn't do anything for RemoteData objects that aren't stale`, () => { + testScheduler.run(({ cold, expectObservable, flush }) => { + const expected = 'a-b-c-d'; + const values = { + a: remoteDataMocks.RequestPending, + b: remoteDataMocks.ResponsePending, + c: remoteDataMocks.Success, + d: remoteDataMocks.Error, + }; + + expectObservable((service as any).reRequestStaleRemoteData(true, callback)(cold(expected, values))).toBe(expected, values); + // since the callback happens in a tap(), flush to ensure it has been executed + flush(); + expect(callback).not.toHaveBeenCalled(); + }); + }); + }); + + }); + + describe(`findByHref`, () => { + beforeEach(() => { + spyOn(service as any, 'createAndSendGetRequest').and.callFake((href$) => { href$.subscribe().unsubscribe(); }); + }); + + it(`should call buildHrefFromFindOptions with href and linksToFollow`, () => { + testScheduler.run(({ cold }) => { + spyOn(service, 'buildHrefFromFindOptions').and.returnValue(selfLink); + spyOn(rdbService, 'buildSingle').and.returnValue(cold('a', { a: remoteDataMocks.Success })); + spyOn(service as any, 'reRequestStaleRemoteData').and.returnValue(() => cold('a', { a: remoteDataMocks.Success })); + + service.findByHref(selfLink, true, true, ...linksToFollow); + expect(service.buildHrefFromFindOptions).toHaveBeenCalledWith(selfLink, {}, [], ...linksToFollow); + }); + }); + + it(`should call createAndSendGetRequest with the result from buildHrefFromFindOptions and useCachedVersionIfAvailable`, () => { + testScheduler.run(({ cold, expectObservable }) => { + spyOn(service, 'buildHrefFromFindOptions').and.returnValue('bingo!'); + spyOn(rdbService, 'buildSingle').and.returnValue(cold('a', { a: remoteDataMocks.Success })); + spyOn(service as any, 'reRequestStaleRemoteData').and.returnValue(() => cold('a', { a: remoteDataMocks.Success })); + + service.findByHref(selfLink, true, true, ...linksToFollow); + expect((service as any).createAndSendGetRequest).toHaveBeenCalledWith(jasmine.anything(), true); + expectObservable(rdbService.buildSingle.calls.argsFor(0)[0]).toBe('(a|)', { a: 'bingo!' }); + + service.findByHref(selfLink, false, true, ...linksToFollow); + expect((service as any).createAndSendGetRequest).toHaveBeenCalledWith(jasmine.anything(), false); + expectObservable(rdbService.buildSingle.calls.argsFor(1)[0]).toBe('(a|)', { a: 'bingo!' }); + }); + }); + + it(`should call rdbService.buildSingle with the result from buildHrefFromFindOptions and linksToFollow`, () => { + testScheduler.run(({ cold, expectObservable }) => { + spyOn(service, 'buildHrefFromFindOptions').and.returnValue('bingo!'); + spyOn(rdbService, 'buildSingle').and.returnValue(cold('a', { a: remoteDataMocks.Success })); + spyOn(service as any, 'reRequestStaleRemoteData').and.returnValue(() => cold('a', { a: remoteDataMocks.Success })); + + service.findByHref(selfLink, true, true, ...linksToFollow); + expect(rdbService.buildSingle).toHaveBeenCalledWith(jasmine.anything() as any, ...linksToFollow); + expectObservable(rdbService.buildSingle.calls.argsFor(0)[0]).toBe('(a|)', { a: 'bingo!' }); + }); + }); + + it(`should return a the output from reRequestStaleRemoteData`, () => { + testScheduler.run(({ cold, expectObservable }) => { + spyOn(service, 'buildHrefFromFindOptions').and.returnValue(selfLink); + spyOn(rdbService, 'buildSingle').and.returnValue(cold('a', { a: remoteDataMocks.Success })); + spyOn(service as any, 'reRequestStaleRemoteData').and.returnValue(() => cold('a', { a: 'bingo!' })); + const expected = 'a'; + const values = { + a: 'bingo!', + }; + + expectObservable(service.findByHref(selfLink, true, true, ...linksToFollow)).toBe(expected, values); + }); + }); + + it(`should call reRequestStaleRemoteData with reRequestOnStale and the exact same findByHref call as a callback`, () => { + testScheduler.run(({ cold, expectObservable }) => { + spyOn(service, 'buildHrefFromFindOptions').and.returnValue(selfLink); + spyOn(rdbService, 'buildSingle').and.returnValue(cold('a', { a: remoteDataMocks.SuccessStale })); + spyOn(service as any, 'reRequestStaleRemoteData').and.returnValue(() => cold('a', { a: remoteDataMocks.SuccessStale })); + + service.findByHref(selfLink, true, true, ...linksToFollow); + expect((service as any).reRequestStaleRemoteData.calls.argsFor(0)[0]).toBeTrue(); + spyOn(service, 'findByHref').and.returnValue(cold('a', { a: remoteDataMocks.SuccessStale })); + // prove that the spy we just added hasn't been called yet + expect(service.findByHref).not.toHaveBeenCalled(); + // call the callback passed to reRequestStaleRemoteData + (service as any).reRequestStaleRemoteData.calls.argsFor(0)[1](); + // verify that findByHref _has_ been called now, with the same params as the original call + expect(service.findByHref).toHaveBeenCalledWith(jasmine.anything(), true, true, ...linksToFollow); + // ... except for selflink, which will have been turned in to an observable. + expectObservable((service.findByHref as jasmine.Spy).calls.argsFor(0)[0]).toBe('(a|)', { a: selfLink }); + }); + }); + + describe(`when useCachedVersionIfAvailable is true`, () => { + beforeEach(() => { + spyOn(service, 'buildHrefFromFindOptions').and.returnValue(selfLink); + spyOn(service as any, 'reRequestStaleRemoteData').and.callFake(() => (source) => source); + }); + + it(`should emit a cached completed RemoteData immediately, and keep emitting if it gets rerequested`, () => { + testScheduler.run(({ cold, expectObservable }) => { + spyOn(rdbService, 'buildSingle').and.returnValue(cold('a-b-c-d-e', { + a: remoteDataMocks.Success, + b: remoteDataMocks.RequestPending, + c: remoteDataMocks.ResponsePending, + d: remoteDataMocks.Success, + e: remoteDataMocks.SuccessStale, + })); + const expected = 'a-b-c-d-e'; + const values = { + a: remoteDataMocks.Success, + b: remoteDataMocks.RequestPending, + c: remoteDataMocks.ResponsePending, + d: remoteDataMocks.Success, + e: remoteDataMocks.SuccessStale, + }; + + expectObservable(service.findByHref(selfLink, true, true, ...linksToFollow)).toBe(expected, values); + }); + }); + + it(`should not emit a cached stale RemoteData, but only start emitting after the state first changes to RequestPending`, () => { + testScheduler.run(({ cold, expectObservable }) => { + spyOn(rdbService, 'buildSingle').and.returnValue(cold('a-b-c-d-e', { + a: remoteDataMocks.SuccessStale, + b: remoteDataMocks.RequestPending, + c: remoteDataMocks.ResponsePending, + d: remoteDataMocks.Success, + e: remoteDataMocks.SuccessStale, + })); + const expected = '--b-c-d-e'; + const values = { + b: remoteDataMocks.RequestPending, + c: remoteDataMocks.ResponsePending, + d: remoteDataMocks.Success, + e: remoteDataMocks.SuccessStale, + }; + + expectObservable(service.findByHref(selfLink, true, true, ...linksToFollow)).toBe(expected, values); + }); + }); + + }); + + describe(`when useCachedVersionIfAvailable is false`, () => { + beforeEach(() => { + spyOn(service, 'buildHrefFromFindOptions').and.returnValue(selfLink); + spyOn(service as any, 'reRequestStaleRemoteData').and.callFake(() => (source) => source); + }); + + + it(`should not emit a cached completed RemoteData, but only start emitting after the state first changes to RequestPending`, () => { + testScheduler.run(({ cold, expectObservable }) => { + spyOn(rdbService, 'buildSingle').and.returnValue(cold('a-b-c-d-e', { + a: remoteDataMocks.Success, + b: remoteDataMocks.RequestPending, + c: remoteDataMocks.ResponsePending, + d: remoteDataMocks.Success, + e: remoteDataMocks.SuccessStale, + })); + const expected = '--b-c-d-e'; + const values = { + b: remoteDataMocks.RequestPending, + c: remoteDataMocks.ResponsePending, + d: remoteDataMocks.Success, + e: remoteDataMocks.SuccessStale, + }; + + expectObservable(service.findByHref(selfLink, false, true, ...linksToFollow)).toBe(expected, values); + }); + }); + + it(`should not emit a cached stale RemoteData, but only start emitting after the state first changes to RequestPending`, () => { + testScheduler.run(({ cold, expectObservable }) => { + spyOn(rdbService, 'buildSingle').and.returnValue(cold('a-b-c-d-e', { + a: remoteDataMocks.SuccessStale, + b: remoteDataMocks.RequestPending, + c: remoteDataMocks.ResponsePending, + d: remoteDataMocks.Success, + e: remoteDataMocks.SuccessStale, + })); + const expected = '--b-c-d-e'; + const values = { + b: remoteDataMocks.RequestPending, + c: remoteDataMocks.ResponsePending, + d: remoteDataMocks.Success, + e: remoteDataMocks.SuccessStale, + }; + + expectObservable(service.findByHref(selfLink, false, true, ...linksToFollow)).toBe(expected, values); + }); + }); + + }); + + }); + + describe(`findAllByHref`, () => { + let findListOptions; + beforeEach(() => { + findListOptions = { currentPage: 5 }; + spyOn(service as any, 'createAndSendGetRequest').and.callFake((href$) => { href$.subscribe().unsubscribe(); }); + }); + + it(`should call buildHrefFromFindOptions with href and linksToFollow`, () => { + testScheduler.run(({ cold }) => { + spyOn(service, 'buildHrefFromFindOptions').and.returnValue(selfLink); + spyOn(rdbService, 'buildList').and.returnValue(cold('a', { a: remoteDataMocks.Success })); + spyOn(service as any, 'reRequestStaleRemoteData').and.returnValue(() => cold('a', { a: remoteDataMocks.Success })); + + service.findAllByHref(selfLink, findListOptions, true, true, ...linksToFollow); + expect(service.buildHrefFromFindOptions).toHaveBeenCalledWith(selfLink, findListOptions, [], ...linksToFollow); + }); + }); + + it(`should call createAndSendGetRequest with the result from buildHrefFromFindOptions and useCachedVersionIfAvailable`, () => { + testScheduler.run(({ cold, expectObservable }) => { + spyOn(service, 'buildHrefFromFindOptions').and.returnValue('bingo!'); + spyOn(rdbService, 'buildList').and.returnValue(cold('a', { a: remoteDataMocks.Success })); + spyOn(service as any, 'reRequestStaleRemoteData').and.returnValue(() => cold('a', { a: remoteDataMocks.Success })); + + service.findAllByHref(selfLink, findListOptions, true, true, ...linksToFollow); + expect((service as any).createAndSendGetRequest).toHaveBeenCalledWith(jasmine.anything(), true); + expectObservable(rdbService.buildList.calls.argsFor(0)[0]).toBe('(a|)', { a: 'bingo!' }); + + service.findAllByHref(selfLink, findListOptions, false, true, ...linksToFollow); + expect((service as any).createAndSendGetRequest).toHaveBeenCalledWith(jasmine.anything(), false); + expectObservable(rdbService.buildList.calls.argsFor(1)[0]).toBe('(a|)', { a: 'bingo!' }); + }); + }); + + it(`should call rdbService.buildList with the result from buildHrefFromFindOptions and linksToFollow`, () => { + testScheduler.run(({ cold, expectObservable }) => { + spyOn(service, 'buildHrefFromFindOptions').and.returnValue('bingo!'); + spyOn(rdbService, 'buildList').and.returnValue(cold('a', { a: remoteDataMocks.Success })); + spyOn(service as any, 'reRequestStaleRemoteData').and.returnValue(() => cold('a', { a: remoteDataMocks.Success })); + + service.findAllByHref(selfLink, findListOptions, true, true, ...linksToFollow); + expect(rdbService.buildList).toHaveBeenCalledWith(jasmine.anything() as any, ...linksToFollow); + expectObservable(rdbService.buildList.calls.argsFor(0)[0]).toBe('(a|)', { a: 'bingo!' }); + }); + }); + + it(`should call reRequestStaleRemoteData with reRequestOnStale and the exact same findAllByHref call as a callback`, () => { + testScheduler.run(({ cold, expectObservable }) => { + spyOn(service, 'buildHrefFromFindOptions').and.returnValue('bingo!'); + spyOn(rdbService, 'buildList').and.returnValue(cold('a', { a: remoteDataMocks.SuccessStale })); + spyOn(service as any, 'reRequestStaleRemoteData').and.returnValue(() => cold('a', { a: remoteDataMocks.SuccessStale })); + + service.findAllByHref(selfLink, findListOptions, true, true, ...linksToFollow); + expect((service as any).reRequestStaleRemoteData.calls.argsFor(0)[0]).toBeTrue(); + spyOn(service, 'findAllByHref').and.returnValue(cold('a', { a: remoteDataMocks.SuccessStale })); + // prove that the spy we just added hasn't been called yet + expect(service.findAllByHref).not.toHaveBeenCalled(); + // call the callback passed to reRequestStaleRemoteData + (service as any).reRequestStaleRemoteData.calls.argsFor(0)[1](); + // verify that findAllByHref _has_ been called now, with the same params as the original call + expect(service.findAllByHref).toHaveBeenCalledWith(jasmine.anything(), findListOptions, true, true, ...linksToFollow); + // ... except for selflink, which will have been turned in to an observable. + expectObservable((service.findAllByHref as jasmine.Spy).calls.argsFor(0)[0]).toBe('(a|)', { a: selfLink }); + }); + }); + + it(`should return a the output from reRequestStaleRemoteData`, () => { + testScheduler.run(({ cold, expectObservable }) => { + spyOn(service, 'buildHrefFromFindOptions').and.returnValue(selfLink); + spyOn(rdbService, 'buildList').and.returnValue(cold('a', { a: remoteDataMocks.Success })); + spyOn(service as any, 'reRequestStaleRemoteData').and.returnValue(() => cold('a', { a: 'bingo!' })); + const expected = 'a'; + const values = { + a: 'bingo!', + }; + + expectObservable(service.findAllByHref(selfLink, findListOptions, true, true, ...linksToFollow)).toBe(expected, values); + }); + }); + + describe(`when useCachedVersionIfAvailable is true`, () => { + beforeEach(() => { + spyOn(service, 'buildHrefFromFindOptions').and.returnValue(selfLink); + spyOn(service as any, 'reRequestStaleRemoteData').and.callFake(() => (source) => source); + }); + + it(`should emit a cached completed RemoteData immediately, and keep emitting if it gets rerequested`, () => { + testScheduler.run(({ cold, expectObservable }) => { + spyOn(rdbService, 'buildList').and.returnValue(cold('a-b-c-d-e', { + a: remoteDataMocks.Success, + b: remoteDataMocks.RequestPending, + c: remoteDataMocks.ResponsePending, + d: remoteDataMocks.Success, + e: remoteDataMocks.SuccessStale, + })); + const expected = 'a-b-c-d-e'; + const values = { + a: remoteDataMocks.Success, + b: remoteDataMocks.RequestPending, + c: remoteDataMocks.ResponsePending, + d: remoteDataMocks.Success, + e: remoteDataMocks.SuccessStale, + }; + + expectObservable(service.findAllByHref(selfLink, findListOptions, true, true, ...linksToFollow)).toBe(expected, values); + }); + }); + + it(`should not emit a cached stale RemoteData, but only start emitting after the state first changes to RequestPending`, () => { + testScheduler.run(({ cold, expectObservable }) => { + spyOn(rdbService, 'buildList').and.returnValue(cold('a-b-c-d-e', { + a: remoteDataMocks.SuccessStale, + b: remoteDataMocks.RequestPending, + c: remoteDataMocks.ResponsePending, + d: remoteDataMocks.Success, + e: remoteDataMocks.SuccessStale, + })); + const expected = '--b-c-d-e'; + const values = { + b: remoteDataMocks.RequestPending, + c: remoteDataMocks.ResponsePending, + d: remoteDataMocks.Success, + e: remoteDataMocks.SuccessStale, + }; + + expectObservable(service.findAllByHref(selfLink, findListOptions, true, true, ...linksToFollow)).toBe(expected, values); + }); + }); + + }); + + describe(`when useCachedVersionIfAvailable is false`, () => { + beforeEach(() => { + spyOn(service, 'buildHrefFromFindOptions').and.returnValue(selfLink); + spyOn(service as any, 'reRequestStaleRemoteData').and.callFake(() => (source) => source); + }); + + + it(`should not emit a cached completed RemoteData, but only start emitting after the state first changes to RequestPending`, () => { + testScheduler.run(({ cold, expectObservable }) => { + spyOn(rdbService, 'buildList').and.returnValue(cold('a-b-c-d-e', { + a: remoteDataMocks.Success, + b: remoteDataMocks.RequestPending, + c: remoteDataMocks.ResponsePending, + d: remoteDataMocks.Success, + e: remoteDataMocks.SuccessStale, + })); + const expected = '--b-c-d-e'; + const values = { + b: remoteDataMocks.RequestPending, + c: remoteDataMocks.ResponsePending, + d: remoteDataMocks.Success, + e: remoteDataMocks.SuccessStale, + }; + + expectObservable(service.findAllByHref(selfLink, findListOptions, false, true, ...linksToFollow)).toBe(expected, values); + }); + }); + + it(`should not emit a cached stale RemoteData, but only start emitting after the state first changes to RequestPending`, () => { + testScheduler.run(({ cold, expectObservable }) => { + spyOn(rdbService, 'buildList').and.returnValue(cold('a-b-c-d-e', { + a: remoteDataMocks.SuccessStale, + b: remoteDataMocks.RequestPending, + c: remoteDataMocks.ResponsePending, + d: remoteDataMocks.Success, + e: remoteDataMocks.SuccessStale, + })); + const expected = '--b-c-d-e'; + const values = { + b: remoteDataMocks.RequestPending, + c: remoteDataMocks.ResponsePending, + d: remoteDataMocks.Success, + e: remoteDataMocks.SuccessStale, + }; + + expectObservable(service.findAllByHref(selfLink, findListOptions, false, true, ...linksToFollow)).toBe(expected, values); + }); + }); + + }); + }); }); /* tslint:enable:max-classes-per-file */ diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index 814c281a37..6bad02e776 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -12,6 +12,7 @@ import { takeWhile, switchMap, tap, + skipWhile, } from 'rxjs/operators'; import { hasValue, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util'; import { NotificationOptions } from '../../shared/notifications/models/notification-options.model'; @@ -45,29 +46,6 @@ import { UpdateDataService } from './update-data.service'; import { GenericConstructor } from '../shared/generic-constructor'; import { NoContent } from '../shared/NoContent.model'; -/** - * An operator that will call the given function if the incoming RemoteData is stale and - * shouldReRequest is true - * - * @param shouldReRequest Whether or not to call the re-request function if the RemoteData is stale - * @param requestFn The function to call if the RemoteData is stale and shouldReRequest is - * true - */ -export const reRequestStaleRemoteData = (shouldReRequest: boolean, requestFn: () => Observable>) => - (source: Observable>): Observable> => { - if (shouldReRequest === true) { - return source.pipe( - tap((remoteData: RemoteData) => { - if (hasValue(remoteData) && remoteData.isStale) { - requestFn(); - } - }) - ); - } else { - return source; - } - }; - export abstract class DataService implements UpdateDataService { protected abstract requestService: RequestService; protected abstract rdbService: RemoteDataBuildService; @@ -332,6 +310,30 @@ export abstract class DataService implements UpdateDa return this.findByHref(href$, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow); } + /** + * An operator that will call the given function if the incoming RemoteData is stale and + * shouldReRequest is true + * + * @param shouldReRequest Whether or not to call the re-request function if the RemoteData is stale + * @param requestFn The function to call if the RemoteData is stale and shouldReRequest is + * true + */ + protected reRequestStaleRemoteData(shouldReRequest: boolean, requestFn: () => Observable>) { + return (source: Observable>): Observable> => { + if (shouldReRequest === true) { + return source.pipe( + tap((remoteData: RemoteData) => { + if (hasValue(remoteData) && remoteData.isStale) { + requestFn(); + } + }) + ); + } else { + return source; + } + }; + } + /** * Returns an observable of {@link RemoteData} of an object, based on an href, with a list of * {@link FollowLinkConfig}, to automatically resolve {@link HALLink}s of the object @@ -358,7 +360,12 @@ export abstract class DataService implements UpdateDa this.createAndSendGetRequest(requestHref$, useCachedVersionIfAvailable); return this.rdbService.buildSingle(requestHref$, ...linksToFollow).pipe( - reRequestStaleRemoteData(reRequestOnStale, () => + // This skip ensures that if a stale object is present in the cache when you do a + // call it isn't immediately returned, but we wait until the remote data for the new request + // is created. If useCachedVersionIfAvailable is false it also ensures you don't get a + // cached completed object + skipWhile((rd: RemoteData) => useCachedVersionIfAvailable ? rd.isStale : rd.hasCompleted), + this.reRequestStaleRemoteData(reRequestOnStale, () => this.findByHref(href$, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow)) ); } @@ -390,7 +397,12 @@ export abstract class DataService implements UpdateDa this.createAndSendGetRequest(requestHref$, useCachedVersionIfAvailable); return this.rdbService.buildList(requestHref$, ...linksToFollow).pipe( - reRequestStaleRemoteData(reRequestOnStale, () => + // This skip ensures that if a stale object is present in the cache when you do a + // call it isn't immediately returned, but we wait until the remote data for the new request + // is created. If useCachedVersionIfAvailable is false it also ensures you don't get a + // cached completed object + skipWhile((rd: RemoteData>) => useCachedVersionIfAvailable ? rd.isStale : rd.hasCompleted), + this.reRequestStaleRemoteData(reRequestOnStale, () => this.findAllByHref(href$, findListOptions, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow)) ); } From 9964f07ff676dca0f0bd6ee3a0b269e614793d32 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Mon, 15 Mar 2021 13:59:29 +0100 Subject: [PATCH 52/56] rewrite community scope test that sometimes failed at random --- src/app/core/data/comcol-data.service.spec.ts | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/src/app/core/data/comcol-data.service.spec.ts b/src/app/core/data/comcol-data.service.spec.ts index 94b050855a..b188f4b50a 100644 --- a/src/app/core/data/comcol-data.service.spec.ts +++ b/src/app/core/data/comcol-data.service.spec.ts @@ -1,6 +1,6 @@ import { HttpClient } from '@angular/common/http'; import { Store } from '@ngrx/store'; -import { cold, getTestScheduler, hot } from 'jasmine-marbles'; +import { cold, getTestScheduler } from 'jasmine-marbles'; import { Observable, of as observableOf } from 'rxjs'; import { TestScheduler } from 'rxjs/testing'; import { getMockRequestService } from '../../shared/mocks/request.service.mock'; @@ -19,10 +19,10 @@ import { RequestService } from './request.service'; import { createFailedRemoteDataObject$, createNoContentRemoteDataObject$, - createSuccessfulRemoteDataObject$ + createSuccessfulRemoteDataObject$, + createFailedRemoteDataObject } from '../../shared/remote-data.utils'; import { BitstreamDataService } from './bitstream-data.service'; -import { take } from 'rxjs/operators'; const LINK_NAME = 'test'; @@ -59,6 +59,7 @@ describe('ComColDataService', () => { let halService: any = {}; let bitstreamDataService: BitstreamDataService; let rdbService: RemoteDataBuildService; + let testScheduler: TestScheduler; const store = {} as Store; const notificationsService = {} as NotificationsService; @@ -98,8 +99,8 @@ describe('ComColDataService', () => { } function initMockCommunityDataService(): CommunityDataService { - return jasmine.createSpyObj('responseCache', { - getEndpoint: hot('--a-', { a: communitiesEndpoint }), + return jasmine.createSpyObj('cds', { + getEndpoint: cold('--a-', { a: communitiesEndpoint }), getIDHref: communityEndpoint }); } @@ -134,7 +135,14 @@ describe('ComColDataService', () => { ); } + const initTestScheduler = (): TestScheduler => { + return new TestScheduler((actual, expected) => { + expect(actual).toEqual(expected); + }); + }; + beforeEach(() => { + testScheduler = initTestScheduler(); cds = initMockCommunityDataService(); requestService = getMockRequestService(); objectCache = initMockObjectCacheService(); @@ -165,10 +173,16 @@ describe('ComColDataService', () => { describe('if the scope Community can\'t be found', () => { it('should throw an error', () => { - const result = service.getBrowseEndpoint(options).pipe(take(1)); - const expected = cold('--#-', undefined, new Error(`The Community with scope ${scopeID} couldn't be retrieved`)); - - expect(result).toBeObservable(expected); + // tslint:disable-next-line:no-shadowed-variable + testScheduler.run(({ cold, expectObservable }) => { + // spies re-defined here to use the "cold" function from rxjs's TestScheduler + // rather than the one imported from jasmine-marbles. + // Mixing the two seems to lead to unpredictable results + (cds.getEndpoint as jasmine.Spy).and.returnValue(cold('a', { a: communitiesEndpoint })); + (rdbService.buildSingle as jasmine.Spy).and.returnValue(cold('a', { a: createFailedRemoteDataObject() })); + const expectedError = new Error(`The Community with scope ${scopeID} couldn't be retrieved`); + expectObservable(service.getBrowseEndpoint(options)).toBe('#', undefined, expectedError); + }); }); }); From 4a15720b4b1c09f42a7349c4c52fded174bbcc9e Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Tue, 16 Mar 2021 12:55:15 +0100 Subject: [PATCH 53/56] rewrite all comcoldataservice tests in hopes of catching the one that sometimes fails at random --- src/app/core/data/comcol-data.service.spec.ts | 178 +++++++----------- 1 file changed, 73 insertions(+), 105 deletions(-) diff --git a/src/app/core/data/comcol-data.service.spec.ts b/src/app/core/data/comcol-data.service.spec.ts index b188f4b50a..864c583dc2 100644 --- a/src/app/core/data/comcol-data.service.spec.ts +++ b/src/app/core/data/comcol-data.service.spec.ts @@ -1,6 +1,6 @@ import { HttpClient } from '@angular/common/http'; import { Store } from '@ngrx/store'; -import { cold, getTestScheduler } from 'jasmine-marbles'; +import { cold } from 'jasmine-marbles'; import { Observable, of as observableOf } from 'rxjs'; import { TestScheduler } from 'rxjs/testing'; import { getMockRequestService } from '../../shared/mocks/request.service.mock'; @@ -13,14 +13,13 @@ import { HALEndpointService } from '../shared/hal-endpoint.service'; import { ComColDataService } from './comcol-data.service'; import { CommunityDataService } from './community-data.service'; import { DSOChangeAnalyzer } from './dso-change-analyzer.service'; -import { FindListOptions, GetRequest } from './request.models'; -import { RequestEntry } from './request.reducer'; +import { FindListOptions } from './request.models'; import { RequestService } from './request.service'; import { createFailedRemoteDataObject$, - createNoContentRemoteDataObject$, createSuccessfulRemoteDataObject$, - createFailedRemoteDataObject + createFailedRemoteDataObject, + createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils'; import { BitstreamDataService } from './bitstream-data.service'; @@ -50,8 +49,8 @@ class TestService extends ComColDataService { } } +// tslint:disable:no-shadowed-variable describe('ComColDataService', () => { - let scheduler: TestScheduler; let service: TestService; let requestService: RequestService; let cds: CommunityDataService; @@ -60,6 +59,7 @@ describe('ComColDataService', () => { let bitstreamDataService: BitstreamDataService; let rdbService: RemoteDataBuildService; let testScheduler: TestScheduler; + let topEndpoint: string; const store = {} as Store; const notificationsService = {} as NotificationsService; @@ -70,17 +70,9 @@ describe('ComColDataService', () => { const options = Object.assign(new FindListOptions(), { scopeID: scopeID }); - const getRequestEntry$ = (successful: boolean) => { - return observableOf({ - response: { isSuccessful: successful } as any - } as RequestEntry); - }; - const communitiesEndpoint = 'https://rest.api/core/communities'; const communityEndpoint = `${communitiesEndpoint}/${scopeID}`; const scopedEndpoint = `${communityEndpoint}/${LINK_NAME}`; - const serviceEndpoint = `https://rest.api/core/${LINK_NAME}`; - const authHeader = 'Bearer eyJhbGciOiJIUzI1NiJ9.eyJlaWQiOiJhNjA4NmIzNC0zOTE4LTQ1YjctOGRkZC05MzI5YTcwMmEyNmEiLCJzZyI6W10sImV4cCI6MTUzNDk0MDcyNX0.RV5GAtiX6cpwBN77P_v16iG9ipeyiO7faNYSNMzq_sQ'; const mockHalService = { getEndpoint: (linkPath) => observableOf(communitiesEndpoint) @@ -142,6 +134,7 @@ describe('ComColDataService', () => { }; beforeEach(() => { + topEndpoint = 'https://rest.api/core/communities/search/top'; testScheduler = initTestScheduler(); cds = initMockCommunityDataService(); requestService = getMockRequestService(); @@ -153,27 +146,20 @@ describe('ComColDataService', () => { }); describe('getBrowseEndpoint', () => { - beforeEach(() => { - scheduler = getTestScheduler(); - }); - - it('should send a new FindByIDRequest for the scope Community', () => { - cds = initMockCommunityDataService(); - requestService = getMockRequestService(getRequestEntry$(true)); - objectCache = initMockObjectCacheService(); - service = initTestService(); - - const expected = new GetRequest(requestService.generateRequestId(), communityEndpoint); - - scheduler.schedule(() => service.getBrowseEndpoint(options).subscribe()); - scheduler.flush(); - - expect(requestService.send).toHaveBeenCalledWith(expected, true); + it(`should call createAndSendGetRequest with the scope Community's self link`, () => { + testScheduler.run(({ cold, flush, expectObservable }) => { + (cds.getEndpoint as jasmine.Spy).and.returnValue(cold('a', { a: communitiesEndpoint })); + (rdbService.buildSingle as jasmine.Spy).and.returnValue(cold('a', { a: createFailedRemoteDataObject() })); + spyOn(service as any, 'createAndSendGetRequest'); + service.getBrowseEndpoint(options); + flush(); + expectObservable((service as any).createAndSendGetRequest.calls.argsFor(0)[0]).toBe('(a|)', { a: communityEndpoint }); + expect((service as any).createAndSendGetRequest.calls.argsFor(0)[1]).toBeTrue(); + }); }); describe('if the scope Community can\'t be found', () => { it('should throw an error', () => { - // tslint:disable-next-line:no-shadowed-variable testScheduler.run(({ cold, expectObservable }) => { // spies re-defined here to use the "cold" function from rxjs's TestScheduler // rather than the one imported from jasmine-marbles. @@ -186,86 +172,68 @@ describe('ComColDataService', () => { }); }); - describe('cache refresh', () => { - let communityWithoutParentHref; - let data; + }); - beforeEach(() => { - spyOn(halService, 'getEndpoint').and.returnValue(observableOf('https://rest.api/core/communities/search/top')); + describe('cache refresh', () => { + let communityWithoutParentHref; + let communityWithParentHref; + + beforeEach(() => { + communityWithParentHref = { + _links: { + parentCommunity: { + href: 'topLevel/parentCommunity' + } + } + } as Community; + communityWithoutParentHref = { + _links: {} + } as Community; + }); + + describe('cache refreshed top level community', () => { + it(`should refresh the top level community cache when the dso has a parent link that can't be resolved`, () => { + testScheduler.run(({ flush, cold }) => { + spyOn(halService, 'getEndpoint').and.returnValue(cold('a', { a: topEndpoint })); + spyOn(service, 'findByHref').and.returnValue(cold('a', { a: createSuccessfulRemoteDataObject({}) })); + service.refreshCache(communityWithParentHref); + flush(); + expect(requestService.setStaleByHrefSubstring).toHaveBeenCalledWith(topEndpoint); + }); }); - - describe('cache refreshed top level community', () => { - beforeEach(() => { - (rdbService.buildSingle as jasmine.Spy).and.returnValue(createNoContentRemoteDataObject$()); - data = { - dso: Object.assign(new Community(), { - metadata: [{ - key: 'dc.title', - value: 'top level community' - }] - }), - _links: { - parentCommunity: { - href: 'topLevel/parentCommunity' - } - } - }; - communityWithoutParentHref = { - dso: Object.assign(new Community(), { - metadata: [{ - key: 'dc.title', - value: 'top level community' - }] - }), - _links: {} - }; - }); - it('top level community cache refreshed', () => { - scheduler.schedule(() => (service as any).refreshCache(data)); - scheduler.flush(); - expect(requestService.setStaleByHrefSubstring).toHaveBeenCalledWith('https://rest.api/core/communities/search/top'); - }); - it('top level community without parent link, cache not refreshed', () => { - scheduler.schedule(() => (service as any).refreshCache(communityWithoutParentHref)); - scheduler.flush(); + it(`shouldn't do anything when the dso doesn't have a parent link`, () => { + testScheduler.run(({ flush, cold }) => { + spyOn(halService, 'getEndpoint').and.returnValue(cold('a', { a: topEndpoint })); + spyOn(service, 'findByHref').and.returnValue(cold('a', { a: createSuccessfulRemoteDataObject({}) })); + service.refreshCache(communityWithoutParentHref); + flush(); expect(requestService.setStaleByHrefSubstring).not.toHaveBeenCalled(); }); }); - - describe('cache refreshed child community', () => { - beforeEach(() => { - const parentCommunity = Object.assign(new Community(), { - uuid: 'a20da287-e174-466a-9926-f66as300d399', - id: 'a20da287-e174-466a-9926-f66as300d399', - metadata: [{ - key: 'dc.title', - value: 'parent community' - }], - _links: {} - }); - (rdbService.buildSingle as jasmine.Spy).and.returnValue(createSuccessfulRemoteDataObject$(parentCommunity)); - data = { - dso: Object.assign(new Community(), { - metadata: [{ - key: 'dc.title', - value: 'child community' - }] - }), - _links: { - parentCommunity: { - href: 'child/parentCommunity' - } - } - }; - }); - it('child level community cache refreshed', () => { - scheduler.schedule(() => (service as any).refreshCache(data)); - scheduler.flush(); - expect(requestService.setStaleByHrefSubstring).toHaveBeenCalledWith('a20da287-e174-466a-9926-f66as300d399'); - }); - }); }); + describe('cache refreshed child community', () => { + let parentCommunity: Community; + beforeEach(() => { + parentCommunity = Object.assign(new Community(), { + uuid: 'a20da287-e174-466a-9926-f66as300d399', + id: 'a20da287-e174-466a-9926-f66as300d399', + metadata: [{ + key: 'dc.title', + value: 'parent community' + }], + _links: {} + }); + }); + it('should refresh a specific cached community when the parent link can be resolved', () => { + testScheduler.run(({ flush, cold }) => { + spyOn(halService, 'getEndpoint').and.returnValue(cold('a', { a: topEndpoint })); + spyOn(service, 'findByHref').and.returnValue(cold('a', { a: createSuccessfulRemoteDataObject(parentCommunity) })); + service.refreshCache(communityWithParentHref); + flush(); + expect(requestService.setStaleByHrefSubstring).toHaveBeenCalledWith('a20da287-e174-466a-9926-f66as300d399'); + }); + }); + }); }); - }); From d7a0f27f08a5031861ae4c121360a71c0d583c57 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Thu, 25 Mar 2021 17:23:27 +0100 Subject: [PATCH 54/56] remove workaround --- src/app/core/tasks/tasks.service.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/core/tasks/tasks.service.ts b/src/app/core/tasks/tasks.service.ts index f23c71e65e..7aeb522170 100644 --- a/src/app/core/tasks/tasks.service.ts +++ b/src/app/core/tasks/tasks.service.ts @@ -110,7 +110,6 @@ export abstract class TasksService extends DataServic find((href: string) => hasValue(href)), mergeMap((href) => this.findByHref(href, false, true).pipe( getAllCompletedRemoteData(), - filter((rd: RemoteData) => !rd.isSuccessStale), tap(() => this.requestService.setStaleByHrefSubstring(href))) ) ); From 6cc56176b2b721ae1259be57effc0fbd0f208fbb Mon Sep 17 00:00:00 2001 From: lotte Date: Thu, 25 Mar 2021 17:27:34 +0100 Subject: [PATCH 55/56] fixed path for custom theme item-page --- src/themes/custom/app/+item-page/simple/item-page.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/themes/custom/app/+item-page/simple/item-page.component.ts b/src/themes/custom/app/+item-page/simple/item-page.component.ts index 1ba534972e..dfd792b3e2 100644 --- a/src/themes/custom/app/+item-page/simple/item-page.component.ts +++ b/src/themes/custom/app/+item-page/simple/item-page.component.ts @@ -9,7 +9,7 @@ import { fadeInOut } from '../../../../../app/shared/animations/fade'; */ @Component({ selector: 'ds-item-page', - // styleUrls: ['../item-page.component.scss'], + // styleUrls: ['./item-page.component.scss'], styleUrls: ['../../../../../app/+item-page/simple/item-page.component.scss'], // templateUrl: './item-page.component.html', templateUrl: '../../../../../app/+item-page/simple/item-page.component.html', From 05545f703b9d15bbbcb1d2cdbe369f77cdd279ce Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Mon, 29 Mar 2021 17:09:26 +0200 Subject: [PATCH 56/56] fix issue where the themed submission page would crash because the component couldn't be found --- src/app/submission/submit/themed-submission-submit.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/submission/submit/themed-submission-submit.component.ts b/src/app/submission/submit/themed-submission-submit.component.ts index ad313bf7bb..328113252f 100644 --- a/src/app/submission/submit/themed-submission-submit.component.ts +++ b/src/app/submission/submit/themed-submission-submit.component.ts @@ -12,7 +12,7 @@ import { SubmissionSubmitComponent } from './submission-submit.component'; }) export class ThemedSubmissionSubmitComponent extends ThemedComponent { protected getComponentName(): string { - return 'SubmissionImportExternalComponent'; + return 'SubmissionSubmitComponent'; } protected importThemedComponent(themeName: string): Promise {