@@ -6,19 +6,28 @@ import {
66} from '@angular/core/testing' ;
77import { RouterTestingModule } from '@angular/router/testing' ;
88import { APP_CONFIG } from '@dspace/config/app-config.interface' ;
9+ import { DSOBreadcrumbsService } from '@dspace/core/breadcrumbs/dso-breadcrumbs.service' ;
910import { DSONameService } from '@dspace/core/breadcrumbs/dso-name.service' ;
11+ import { Breadcrumb } from '@dspace/core/breadcrumbs/models/breadcrumb.model' ;
1012import { LinkService } from '@dspace/core/cache/builders/link.service' ;
1113import { ChildHALResource } from '@dspace/core/shared/child-hal-resource.model' ;
1214import { DSpaceObject } from '@dspace/core/shared/dspace-object.model' ;
1315import { HALResource } from '@dspace/core/shared/hal-resource.model' ;
16+ import { ResourceType } from '@dspace/core/shared/resource-type' ;
1417import { SearchResult } from '@dspace/core/shared/search/models/search-result.model' ;
1518import { mockTruncatableService } from '@dspace/core/testing/mock-trucatable.service' ;
16- import { createSuccessfulRemoteDataObject$ } from '@dspace/core/utilities/remote-data.utils' ;
19+ import {
20+ createNoContentRemoteDataObject$ ,
21+ createSuccessfulRemoteDataObject$ ,
22+ } from '@dspace/core/utilities/remote-data.utils' ;
1723import { TranslateModule } from '@ngx-translate/core' ;
24+ import { of } from 'rxjs' ;
1825
1926import { TruncatableService } from '../../truncatable/truncatable.service' ;
2027import { TruncatablePartComponent } from '../../truncatable/truncatable-part/truncatable-part.component' ;
2128import { VarDirective } from '../../utils/var.directive' ;
29+ import { BREADCRUMB_SEPARATOR } from './sidebar-search-list-element.component' ;
30+
2231
2332export function createSidebarSearchListElementTests (
2433 componentClass : any ,
@@ -28,12 +37,14 @@ export function createSidebarSearchListElementTests(
2837 expectedTitle : string ,
2938 expectedDescription : string ,
3039 extraProviders : any [ ] = [ ] ,
40+ assertBreadcrumbsUsed = false ,
3141) {
3242 return ( ) => {
3343 let component ;
3444 let fixture : ComponentFixture < any > ;
3545
3646 let linkService ;
47+ let dsoBreadcrumbsService ;
3748
3849 const environment = {
3950 browseBy : {
@@ -42,17 +53,33 @@ export function createSidebarSearchListElementTests(
4253 } ;
4354
4455 beforeEach ( waitForAsync ( ( ) => {
56+ // Propagate the class-level static ResourceType onto the instance so that
57+ // the community/collection branch in getParentTitle() is reached correctly.
58+ const staticType : ResourceType | undefined = ( object . indexableObject . constructor as any ) . type ;
59+ if ( staticType ) {
60+ ( object . indexableObject as any ) . type = staticType ;
61+ }
62+
4563 linkService = jasmine . createSpyObj ( 'linkService' , {
4664 resolveLink : Object . assign ( new HALResource ( ) , {
4765 [ object . indexableObject . getParentLinkKey ( ) ] : createSuccessfulRemoteDataObject$ ( parent ) ,
4866 } ) ,
4967 } ) ;
68+ const breadcrumbs : Breadcrumb [ ] = [ ] ;
69+ if ( expectedParentTitle ) {
70+ breadcrumbs . push ( new Breadcrumb ( expectedParentTitle , '' ) ) ;
71+ }
72+ breadcrumbs . push ( new Breadcrumb ( expectedTitle , '' ) ) ;
73+ dsoBreadcrumbsService = jasmine . createSpyObj ( 'dsoBreadcrumbsService' , {
74+ getBreadcrumbs : of ( breadcrumbs ) ,
75+ } ) ;
5076 TestBed . configureTestingModule ( {
5177 imports : [ TranslateModule . forRoot ( ) , RouterTestingModule . withRoutes ( [ ] ) , VarDirective ] ,
5278 providers : [
5379 { provide : TruncatableService , useValue : mockTruncatableService } ,
5480 { provide : LinkService , useValue : linkService } ,
5581 { provide : APP_CONFIG , useValue : environment } ,
82+ { provide : DSOBreadcrumbsService , useValue : dsoBreadcrumbsService } ,
5683 DSONameService ,
5784 ...extraProviders ,
5885 ] ,
@@ -75,6 +102,18 @@ export function createSidebarSearchListElementTests(
75102 } ) ;
76103 } ) ;
77104
105+ if ( assertBreadcrumbsUsed ) {
106+ it ( 'should delegate to DSOBreadcrumbsService.getBreadcrumbs to resolve the parent title' , ( done ) => {
107+ component . parentTitle$ . subscribe ( ( ) => {
108+ expect ( dsoBreadcrumbsService . getBreadcrumbs ) . toHaveBeenCalledWith (
109+ object . indexableObject ,
110+ '' ,
111+ ) ;
112+ done ( ) ;
113+ } ) ;
114+ } ) ;
115+ }
116+
78117 it ( 'should contain the correct title' , ( ) => {
79118 expect ( component . dsoTitle ) . toEqual ( expectedTitle ) ;
80119 } ) ;
@@ -84,3 +123,92 @@ export function createSidebarSearchListElementTests(
84123 } ) ;
85124 } ;
86125}
126+
127+ /**
128+ * Shared test suite that verifies the hierarchical parent-path behaviour for community/collection
129+ * list elements: when the DSO has multiple ancestor breadcrumbs the component must join them with
130+ * {@link BREADCRUMB_SEPARATOR} and must delegate to {@link DSOBreadcrumbsService#getBreadcrumbs} rather than the simple
131+ * parent link.
132+ *
133+ * @param componentClass The component under test (community or collection sidebar element)
134+ * @param object A {@link SearchResult} whose `indexableObject` is a Community/Collection
135+ * @param expectedTitle The dc.title of the current item (last breadcrumb)
136+ * @param extraProviders Any additional providers required by the component
137+ */
138+ export function createHierarchicalParentTitleTests (
139+ componentClass : any ,
140+ object : SearchResult < DSpaceObject & ChildHALResource > ,
141+ expectedTitle : string ,
142+ extraProviders : any [ ] = [ ] ,
143+ ) {
144+ return ( ) => {
145+ let component ;
146+ let fixture : ComponentFixture < any > ;
147+ let dsoBreadcrumbsService ;
148+
149+ // Three-level hierarchy: Root → Parent → Current
150+ const rootBreadcrumb = new Breadcrumb ( 'Root' , '' ) ;
151+ const parentBreadcrumb = new Breadcrumb ( 'Parent' , '' ) ;
152+ const currentBreadcrumb = new Breadcrumb ( expectedTitle , '' ) ;
153+ const breadcrumbs = [ rootBreadcrumb , parentBreadcrumb , currentBreadcrumb ] ;
154+
155+ beforeEach ( waitForAsync ( ( ) => {
156+ // Propagate the class-level static ResourceType onto the instance so that
157+ // the community/collection branch in getParentTitle() is reached correctly.
158+ const staticType : ResourceType | undefined = ( object . indexableObject . constructor as any ) . type ;
159+ if ( staticType ) {
160+ ( object . indexableObject as any ) . type = staticType ;
161+ }
162+
163+ // Set up the linkService with a safe RemoteData observable for the parent link so that
164+ // even if the type-check guard ever regresses, the fallback getParent() path resolves
165+ // cleanly via the find() predicate (statusCode === 204) without a TypeError.
166+ const parentLinkKey = ( object . indexableObject as ChildHALResource ) . getParentLinkKey ( ) as string ;
167+ const linkService = jasmine . createSpyObj ( 'linkService' , {
168+ resolveLink : Object . assign ( new HALResource ( ) , {
169+ [ parentLinkKey ] : createNoContentRemoteDataObject$ ( ) ,
170+ } ) ,
171+ } ) ;
172+ dsoBreadcrumbsService = jasmine . createSpyObj ( 'dsoBreadcrumbsService' , {
173+ getBreadcrumbs : of ( breadcrumbs ) ,
174+ } ) ;
175+
176+ TestBed . configureTestingModule ( {
177+ imports : [ TranslateModule . forRoot ( ) , RouterTestingModule . withRoutes ( [ ] ) , VarDirective ] ,
178+ providers : [
179+ { provide : TruncatableService , useValue : { } } ,
180+ { provide : LinkService , useValue : linkService } ,
181+ { provide : DSOBreadcrumbsService , useValue : dsoBreadcrumbsService } ,
182+ DSONameService ,
183+ ...extraProviders ,
184+ ] ,
185+ schemas : [ NO_ERRORS_SCHEMA ] ,
186+ } ) . overrideComponent ( componentClass , { remove : { imports : [ TruncatablePartComponent ] } } ) . compileComponents ( ) ;
187+ } ) ) ;
188+
189+ beforeEach ( ( ) => {
190+ fixture = TestBed . createComponent ( componentClass ) ;
191+ component = fixture . componentInstance ;
192+ component . object = object ;
193+ component . ngOnInit ( ) ;
194+ fixture . detectChanges ( ) ;
195+ } ) ;
196+
197+ it ( 'should join multiple ancestor breadcrumbs with BREADCRUMB_SEPARATOR as the parent title' , ( done ) => {
198+ component . parentTitle$ . subscribe ( ( title ) => {
199+ expect ( title ) . toEqual ( [ 'Root' , 'Parent' ] . join ( BREADCRUMB_SEPARATOR ) ) ;
200+ done ( ) ;
201+ } ) ;
202+ } ) ;
203+
204+ it ( 'should call DSOBreadcrumbsService.getBreadcrumbs to build the hierarchy path' , ( done ) => {
205+ component . parentTitle$ . subscribe ( ( ) => {
206+ expect ( dsoBreadcrumbsService . getBreadcrumbs ) . toHaveBeenCalledWith (
207+ object . indexableObject ,
208+ '' ,
209+ ) ;
210+ done ( ) ;
211+ } ) ;
212+ } ) ;
213+ } ;
214+ }
0 commit comments