Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { TruncatableService } from '../../../../../shared/truncatable/truncatabl
import { LinkService } from '../../../../../core/cache/builders/link.service';
import { TranslateService } from '@ngx-translate/core';
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
import { DSOBreadcrumbsService } from '../../../../../core/breadcrumbs/dso-breadcrumbs.service';

@listableObjectComponent('PersonSearchResult', ViewMode.ListElement, Context.SideBarSearchModal)
@listableObjectComponent('PersonSearchResult', ViewMode.ListElement, Context.SideBarSearchModalCurrent)
Expand All @@ -26,8 +27,9 @@ export class PersonSidebarSearchListElementComponent extends SidebarSearchListEl
protected linkService: LinkService,
protected translateService: TranslateService,
public dsoNameService: DSONameService,
protected dsoBreadcrumbsService: DSOBreadcrumbsService,
) {
super(truncatableService, linkService, dsoNameService);
super(truncatableService, linkService, dsoNameService, dsoBreadcrumbsService);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { CollectionSidebarSearchListElementComponent } from './collection-sideba
import { CollectionSearchResult } from '../../../object-collection/shared/collection-search-result.model';
import { Collection } from '../../../../core/shared/collection.model';
import { Community } from '../../../../core/shared/community.model';
import { createSidebarSearchListElementTests } from '../sidebar-search-list-element.component.spec';
import { createHierarchicalParentTitleTests, createSidebarSearchListElementTests } from '../sidebar-search-list-element.component.spec';

const object = Object.assign(new CollectionSearchResult(), {
indexableObject: Object.assign(new Collection(), {
Expand Down Expand Up @@ -33,5 +33,9 @@ const parent = Object.assign(new Community(), {
});

describe('CollectionSidebarSearchListElementComponent',
createSidebarSearchListElementTests(CollectionSidebarSearchListElementComponent, object, parent, 'parent title', 'title', 'description')
createSidebarSearchListElementTests(CollectionSidebarSearchListElementComponent, object, parent, 'parent title', 'title', 'description', [], true)
);

describe('CollectionSidebarSearchListElementComponent - hierarchical path',
createHierarchicalParentTitleTests(CollectionSidebarSearchListElementComponent, object, 'title')
);
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import { listableObjectComponent } from '../../../object-collection/shared/lista
import { Context } from '../../../../core/shared/context.model';
import { ViewMode } from '../../../../core/shared/view-mode.model';
import { SidebarSearchListElementComponent } from '../sidebar-search-list-element.component';
import { TruncatableService } from '../../../truncatable/truncatable.service';
import { LinkService } from '../../../../core/cache/builders/link.service';
import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service';
import { DSOBreadcrumbsService } from '../../../../core/breadcrumbs/dso-breadcrumbs.service';

@listableObjectComponent(CollectionSearchResult, ViewMode.ListElement, Context.SideBarSearchModal)
@listableObjectComponent(CollectionSearchResult, ViewMode.ListElement, Context.SideBarSearchModalCurrent)
Expand All @@ -16,6 +20,16 @@ import { SidebarSearchListElementComponent } from '../sidebar-search-list-elemen
* Component displaying a list element for a {@link CollectionSearchResult} within the context of a sidebar search modal
*/
export class CollectionSidebarSearchListElementComponent extends SidebarSearchListElementComponent<CollectionSearchResult, Collection> {

constructor(
protected truncatableService: TruncatableService,
protected linkService: LinkService,
public dsoNameService: DSONameService,
protected dsoBreadcrumbsService: DSOBreadcrumbsService,
) {
super(truncatableService, linkService, dsoNameService, dsoBreadcrumbsService);
}

/**
* Get the description of the Collection by returning its abstract
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Community } from '../../../../core/shared/community.model';
import { createSidebarSearchListElementTests } from '../sidebar-search-list-element.component.spec';
import { createHierarchicalParentTitleTests, createSidebarSearchListElementTests } from '../sidebar-search-list-element.component.spec';
import { CommunitySidebarSearchListElementComponent } from './community-sidebar-search-list-element.component';
import { CommunitySearchResult } from '../../../object-collection/shared/community-search-result.model';

Expand Down Expand Up @@ -32,5 +32,9 @@ const parent = Object.assign(new Community(), {
});

describe('CommunitySidebarSearchListElementComponent',
createSidebarSearchListElementTests(CommunitySidebarSearchListElementComponent, object, parent, 'parent title', 'title', 'description')
createSidebarSearchListElementTests(CommunitySidebarSearchListElementComponent, object, parent, 'parent title', 'title', 'description', [], true)
);

describe('CommunitySidebarSearchListElementComponent - hierarchical path',
createHierarchicalParentTitleTests(CommunitySidebarSearchListElementComponent, object, 'title')
);
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,31 @@ import { ViewMode } from '../../../../core/shared/view-mode.model';
import { SidebarSearchListElementComponent } from '../sidebar-search-list-element.component';
import { CommunitySearchResult } from '../../../object-collection/shared/community-search-result.model';
import { Community } from '../../../../core/shared/community.model';
import { TruncatableService } from '../../../truncatable/truncatable.service';
import { LinkService } from '../../../../core/cache/builders/link.service';
import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service';
import { DSOBreadcrumbsService } from '../../../../core/breadcrumbs/dso-breadcrumbs.service';

@listableObjectComponent(CommunitySearchResult, ViewMode.ListElement, Context.SideBarSearchModal)
@listableObjectComponent(CommunitySearchResult, ViewMode.ListElement, Context.SideBarSearchModalCurrent)
@Component({
selector: 'ds-collection-sidebar-search-list-element',
selector: 'ds-community-sidebar-search-list-element',
templateUrl: '../sidebar-search-list-element.component.html'
})
/**
* Component displaying a list element for a {@link CommunitySearchResult} within the context of a sidebar search modal
*/
export class CommunitySidebarSearchListElementComponent extends SidebarSearchListElementComponent<CommunitySearchResult, Community> {

constructor(
protected truncatableService: TruncatableService,
protected linkService: LinkService,
public dsoNameService: DSONameService,
protected dsoBreadcrumbsService: DSOBreadcrumbsService,
) {
super(truncatableService, linkService, dsoNameService, dsoBreadcrumbsService);
}

/**
* Get the description of the Community by returning its abstract
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
<ds-truncatable-part [maxLines]="1" [background]="isCurrent() ? 'primary' : 'default'" [showToggle]="false">
<div [ngClass]="isCurrent() ? 'text-light' : 'text-body'"
[innerHTML]="(parentTitle$ && parentTitle$ | async) ? (parentTitle$ | async) : ('home.breadcrumbs' | translate)"></div>
<ng-container *ngIf="parentTitle$ | async as parentTitle; else defaultParentTitle">
<div [ngClass]="isCurrent() ? 'text-light' : 'text-body'"
[title]="parentTitle || ('home.breadcrumbs' | translate)"
[innerHTML]="parentTitle || ('home.breadcrumbs' | translate)"></div>
</ng-container>
<ng-template #defaultParentTitle>
<div [ngClass]="isCurrent() ? 'text-light' : 'text-body'"
[title]="'home.breadcrumbs' | translate"
[innerHTML]="'home.breadcrumbs' | translate"></div>
</ng-template>
</ds-truncatable-part>
<ds-truncatable-part [maxLines]="1" [background]="isCurrent() ? 'primary' : 'default'" [showToggle]="false">
<div class="font-weight-bold"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,15 @@ import { SearchResult } from '../../search/models/search-result.model';
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
import { TruncatableService } from '../../truncatable/truncatable.service';
import { LinkService } from '../../../core/cache/builders/link.service';
import { createSuccessfulRemoteDataObject$ } from '../../remote-data.utils';
import { createSuccessfulRemoteDataObject$, createNoContentRemoteDataObject$ } 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 { DSOBreadcrumbsService } from '../../../core/breadcrumbs/dso-breadcrumbs.service';
import { Breadcrumb } from '../../../breadcrumbs/breadcrumb/breadcrumb.model';
import { of as observableOf } from 'rxjs';
import { BREADCRUMB_SEPARATOR } from './sidebar-search-list-element.component';
import { ResourceType } from '../../../core/shared/resource-type';

export function createSidebarSearchListElementTests(
componentClass: any,
Expand All @@ -19,26 +24,44 @@ export function createSidebarSearchListElementTests(
expectedParentTitle: string,
expectedTitle: string,
expectedDescription: string,
extraProviders: any[] = []
extraProviders: any[] = [],
assertBreadcrumbsUsed = false
) {
return () => {
let component;
let fixture: ComponentFixture<any>;

let linkService;
let dsoBreadcrumbsService;

beforeEach(waitForAsync(() => {
// Propagate the class-level static ResourceType onto the instance so that
// the community/collection branch in getParentTitle() is reached correctly.
const staticType: ResourceType | undefined = (object.indexableObject.constructor as any).type;
if (staticType) {
(object.indexableObject as any).type = staticType;
}

linkService = jasmine.createSpyObj('linkService', {
resolveLink: Object.assign(new HALResource(), {
[object.indexableObject.getParentLinkKey()]: createSuccessfulRemoteDataObject$(parent)
})
});
const breadcrumbs: Breadcrumb[] = [];
if (expectedParentTitle) {
breadcrumbs.push(new Breadcrumb(expectedParentTitle, ''));
}
breadcrumbs.push(new Breadcrumb(expectedTitle, ''));
dsoBreadcrumbsService = jasmine.createSpyObj('dsoBreadcrumbsService', {
getBreadcrumbs: observableOf(breadcrumbs)
});
TestBed.configureTestingModule({
declarations: [componentClass, VarDirective],
imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([])],
providers: [
{ provide: TruncatableService, useValue: {} },
{ provide: LinkService, useValue: linkService },
{ provide: DSOBreadcrumbsService, useValue: dsoBreadcrumbsService },
DSONameService,
...extraProviders
],
Expand All @@ -61,6 +84,18 @@ export function createSidebarSearchListElementTests(
});
});

if (assertBreadcrumbsUsed) {
it('should delegate to DSOBreadcrumbsService.getBreadcrumbs to resolve the parent title', (done) => {
component.parentTitle$.subscribe(() => {
expect(dsoBreadcrumbsService.getBreadcrumbs).toHaveBeenCalledWith(
object.indexableObject,
''
);
done();
});
});
}

it('should contain the correct title', () => {
expect(component.dsoTitle).toEqual(expectedTitle);
});
Expand All @@ -70,3 +105,93 @@ export function createSidebarSearchListElementTests(
});
};
}

/**
* Shared test suite that verifies the hierarchical parent-path behaviour for community/collection
* list elements: when the DSO has multiple ancestor breadcrumbs the component must join them with
* {@link BREADCRUMB_SEPARATOR} and must delegate to {@link DSOBreadcrumbsService#getBreadcrumbs} rather than the simple
* parent link.
*
* @param componentClass The component under test (community or collection sidebar element)
* @param object A {@link SearchResult} whose `indexableObject` is a Community/Collection
* @param expectedTitle The dc.title of the current item (last breadcrumb)
* @param extraProviders Any additional providers required by the component
*/
export function createHierarchicalParentTitleTests(
componentClass: any,
object: SearchResult<DSpaceObject & ChildHALResource>,
expectedTitle: string,
extraProviders: any[] = []
) {
return () => {
let component;
let fixture: ComponentFixture<any>;
let dsoBreadcrumbsService;

// Three-level hierarchy: Root → Parent → Current
const rootBreadcrumb = new Breadcrumb('Root', '');
const parentBreadcrumb = new Breadcrumb('Parent', '');
const currentBreadcrumb = new Breadcrumb(expectedTitle, '');
const breadcrumbs = [rootBreadcrumb, parentBreadcrumb, currentBreadcrumb];

beforeEach(waitForAsync(() => {
// Propagate the class-level static ResourceType onto the instance so that
// the community/collection branch in getParentTitle() is reached correctly.
const staticType: ResourceType | undefined = (object.indexableObject.constructor as any).type;
if (staticType) {
(object.indexableObject as any).type = staticType;
}

// Set up the linkService with a safe RemoteData observable for the parent link so that
// even if the type-check guard ever regresses, the fallback getParent() path resolves
// cleanly via the find() predicate (statusCode === 204) without a TypeError.
const parentLinkKey = (object.indexableObject as ChildHALResource).getParentLinkKey() as string;
const linkService = jasmine.createSpyObj('linkService', {
resolveLink: Object.assign(new HALResource(), {
[parentLinkKey]: createNoContentRemoteDataObject$()
})
});
dsoBreadcrumbsService = jasmine.createSpyObj('dsoBreadcrumbsService', {
getBreadcrumbs: observableOf(breadcrumbs)
});

TestBed.configureTestingModule({
declarations: [componentClass, VarDirective],
imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([])],
providers: [
{ provide: TruncatableService, useValue: {} },
{ provide: LinkService, useValue: linkService },
{ provide: DSOBreadcrumbsService, useValue: dsoBreadcrumbsService },
DSONameService,
...extraProviders
],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(componentClass);
component = fixture.componentInstance;
component.object = object;
component.ngOnInit();
fixture.detectChanges();
});

it('should join multiple ancestor breadcrumbs with BREADCRUMB_SEPARATOR as the parent title', (done) => {
component.parentTitle$.subscribe((title) => {
expect(title).toEqual(['Root', 'Parent'].join(BREADCRUMB_SEPARATOR));
done();
});
});

it('should call DSOBreadcrumbsService.getBreadcrumbs to build the hierarchy path', (done) => {
component.parentTitle$.subscribe(() => {
expect(dsoBreadcrumbsService.getBreadcrumbs).toHaveBeenCalledWith(
object.indexableObject,
''
);
done();
});
});
};
}
Loading
Loading