diff --git a/src/app/item-page/versions/notice/item-versions-notice.component.spec.ts b/src/app/item-page/versions/notice/item-versions-notice.component.spec.ts index fd3c5096359..db03559bc71 100644 --- a/src/app/item-page/versions/notice/item-versions-notice.component.spec.ts +++ b/src/app/item-page/versions/notice/item-versions-notice.component.spec.ts @@ -1,3 +1,7 @@ +import { + APP_BASE_HREF, + Location, +} from '@angular/common'; import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, @@ -7,7 +11,10 @@ import { import { By } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; -import { TranslateModule } from '@ngx-translate/core'; +import { + TranslateModule, + TranslateService, +} from '@ngx-translate/core'; import { of } from 'rxjs'; import { take } from 'rxjs/operators'; @@ -61,6 +68,7 @@ describe('ItemVersionsNoticeComponent', () => { const versionHistoryServiceSpy = jasmine.createSpyObj('versionHistoryService', ['getVersions', 'getLatestVersionFromHistory$', 'isLatest$' ], ); + const locationStub = jasmine.createSpyObj('location', ['prepareExternalUrl']); beforeEach(waitForAsync(() => { @@ -73,6 +81,7 @@ describe('ItemVersionsNoticeComponent', () => { ], providers: [ { provide: VersionHistoryDataService, useValue: versionHistoryServiceSpy }, + { provide: Location, useValue: locationStub }, ], schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); @@ -84,6 +93,8 @@ describe('ItemVersionsNoticeComponent', () => { versionHistoryServiceSpy.getVersions.and.returnValue(createSuccessfulRemoteDataObject$(createPaginatedList(versions))); versionHistoryServiceSpy.getLatestVersionFromHistory$.and.returnValue(of(latestVersion)); versionHistoryServiceSpy.isLatest$.and.callFake(isLatestFcn); + // Simulate a UI deployed under a sub-path namespace, i.e. + locationStub.prepareExternalUrl.and.callFake((url: string) => `/repository${url}`); })); describe('when the item is the latest version', () => { @@ -121,6 +132,87 @@ describe('ItemVersionsNoticeComponent', () => { }); }); + describe('getItemPage', () => { + beforeEach(() => { + initComponentWithItem(firstItem); + }); + + it('should resolve the latest version item page url against the base href', () => { + locationStub.prepareExternalUrl.calls.reset(); + + expect(component.getItemPage(latestItem)).toEqual('/repository/items/latest_item_id'); + // The plain router path must be what is handed to Location, otherwise the prefix would be applied twice + expect(locationStub.prepareExternalUrl).toHaveBeenCalledOnceWith('/items/latest_item_id'); + }); + + it('should not resolve a url when no item is provided', () => { + locationStub.prepareExternalUrl.calls.reset(); + + expect(component.getItemPage(undefined)).toBeUndefined(); + expect(locationStub.prepareExternalUrl).not.toHaveBeenCalled(); + }); + + it('should render the notice anchor with the base href applied', () => { + const translate = TestBed.inject(TranslateService); + translate.setTranslation('en', { + 'item.version.notice': 'The latest version can be found here.', + }, true); + translate.use('en'); + fixture.detectChanges(); + + const anchor = fixture.debugElement.query(By.css('ds-alert a')); + expect(anchor).not.toBeNull(); + expect(anchor.nativeElement.getAttribute('href')).toEqual('/repository/items/latest_item_id'); + }); + }); + + describe('getItemPage with the real Location', () => { + // the real PathLocationStrategy, so the "no-op for NAMESPACE=/" claim is actually covered + [ + { baseHref: '/', expected: '/items/latest_item_id' }, + { baseHref: '/repository/', expected: '/repository/items/latest_item_id' }, + // the form express hands to the SSR platform injector (req.baseUrl, no trailing slash) + { baseHref: '/repository', expected: '/repository/items/latest_item_id' }, + ].forEach(({ baseHref, expected }) => { + it(`should resolve the item page url against base href '${baseHref}'`, () => { + expect(createComponentWithBaseHref(baseHref).getItemPage(latestItem)).toEqual(expected); + }); + }); + + it('should keep the entity route shape and only add the prefix', () => { + const entityItem = Object.assign(new Item(), { + id: 'entity_item_id', + uuid: 'entity_item_id', + metadata: { 'dspace.entity.type': [{ value: 'Publication' }] }, + }); + + expect(createComponentWithBaseHref('/repository/').getItemPage(entityItem)) + .toEqual('/repository/entities/publication/entity_item_id'); + }); + + function createComponentWithBaseHref(baseHref: string): ItemVersionsNoticeComponent { + TestBed.resetTestingModule(); + TestBed.configureTestingModule({ + // no RouterTestingModule on purpose: its SpyLocation/MockLocationStrategy ignore APP_BASE_HREF + imports: [ + TranslateModule.forRoot(), + ItemVersionsNoticeComponent, + NoopAnimationsModule, + ], + providers: [ + { provide: VersionHistoryDataService, useValue: versionHistoryServiceSpy }, + { provide: APP_BASE_HREF, useValue: baseHref }, + ], + schemas: [NO_ERRORS_SCHEMA], + }); + + const realLocationFixture = TestBed.createComponent(ItemVersionsNoticeComponent); + realLocationFixture.componentInstance.item = firstItem; + realLocationFixture.detectChanges(); + return realLocationFixture.componentInstance; + } + }); + function initComponentWithItem(item: Item) { fixture = TestBed.createComponent(ItemVersionsNoticeComponent); component = fixture.componentInstance; diff --git a/src/app/item-page/versions/notice/item-versions-notice.component.ts b/src/app/item-page/versions/notice/item-versions-notice.component.ts index b44c1fb3411..47484ca215e 100644 --- a/src/app/item-page/versions/notice/item-versions-notice.component.ts +++ b/src/app/item-page/versions/notice/item-versions-notice.component.ts @@ -1,4 +1,7 @@ -import { AsyncPipe } from '@angular/common'; +import { + AsyncPipe, + Location, +} from '@angular/common'; import { Component, Input, @@ -84,7 +87,10 @@ export class ItemVersionsNoticeComponent implements OnInit { */ public AlertTypeEnum = AlertType; - constructor(private versionHistoryService: VersionHistoryDataService) { + constructor( + private versionHistoryService: VersionHistoryDataService, + private location: Location, + ) { } /** @@ -128,12 +134,17 @@ export class ItemVersionsNoticeComponent implements OnInit { } /** - * Get the item page url + * Get the item page url, resolved against the base href. The url lands in the raw `` of the + * `item.version.notice` translation, so the browser resolves it and not the router - a plain + * `/items/` would ignore ``. No-op when the base href is `/`. + * + * Undefined while the latest version is still loading. + * * @param item The item for which the url is requested */ - getItemPage(item: Item): string { + getItemPage(item: Item | undefined): string | undefined { if (hasValue(item)) { - return getItemPageRoute(item); + return this.location.prepareExternalUrl(getItemPageRoute(item)); } } }