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
8 changes: 7 additions & 1 deletion src/app/item-page/full/full-item-page.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,13 @@
@for (mdValue of mdEntry.value; track mdValue) {
<tr>
<td>{{mdEntry.key}}</td>
<td>{{mdValue.value}}</td>
<td>
@if (isHttpUrl(mdValue.value)) {
<a [href]="mdValue.value.trim()" target="_blank" rel="noopener noreferrer" class="dont-break-out">{{mdValue.value.trim()}}</a>
} @else {
{{mdValue.value}}
}
</td>
<td>{{mdValue.language}}</td>
</tr>
}
Expand Down
71 changes: 71 additions & 0 deletions src/app/item-page/full/full-item-page.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,30 @@ const mockItem: Item = Object.assign(new Item(), {
},
});

const mockItemWithUrl: Item = Object.assign(new Item(), {
bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])),
metadata: {
'dc.title': [
{
language: 'en_US',
value: 'test item',
},
],
'dc.identifier.uri': [
{
language: null,
value: 'https://hdl.handle.net/123456789/1',
},
],
'dc.subject': [
{
language: 'en_US',
value: 'plain text value',
},
],
},
});

const mockWithdrawnItem: Item = Object.assign(new Item(), {
bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])),
metadata: [],
Expand Down Expand Up @@ -265,4 +289,51 @@ describe('FullItemPageComponent', () => {
expect(linkHeadService.addTag).toHaveBeenCalledTimes(3);
});
});

describe('isHttpUrl', () => {
it('should return true for https URLs', () => {
expect(comp.isHttpUrl('https://example.com')).toBeTrue();
});

it('should return true for http URLs', () => {
expect(comp.isHttpUrl('http://example.com')).toBeTrue();
});

it('should return false for plain text', () => {
expect(comp.isHttpUrl('just some text')).toBeFalse();
});

it('should return false for null', () => {
expect(comp.isHttpUrl(null)).toBeFalse();
});

it('should return false for undefined', () => {
expect(comp.isHttpUrl(undefined)).toBeFalse();
});
});

describe('metadata URL rendering', () => {
beforeEach(() => {
routeData.dso = createSuccessfulRemoteDataObject(mockItemWithUrl);
comp.ngOnInit();
fixture.detectChanges();
});

it('should render URL metadata values as clickable links', () => {
const links = fixture.debugElement.queryAll(By.css('table a'));
const urlLink = links.find(l => l.nativeElement.textContent.includes('https://hdl.handle.net/123456789/1'));
expect(urlLink).toBeTruthy();
expect(urlLink.nativeElement.getAttribute('href')).toBe('https://hdl.handle.net/123456789/1');
expect(urlLink.nativeElement.getAttribute('target')).toBe('_blank');
expect(urlLink.nativeElement.getAttribute('rel')).toBe('noopener noreferrer');
});

it('should render non-URL metadata values as plain text', () => {
const table = fixture.debugElement.query(By.css('table'));
const links = fixture.debugElement.queryAll(By.css('table a'));
expect(table.nativeElement.innerHTML).toContain('plain text value');
const plainTextLink = links.find(l => l.nativeElement.textContent.includes('plain text value'));
expect(plainTextLink).toBeFalsy();
});
});
});
8 changes: 8 additions & 0 deletions src/app/item-page/full/full-item-page.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,14 @@ export class FullItemPageComponent extends ItemPageComponent implements OnInit,
);
}

/**
* Check if a metadata value is an HTTP(S) URL.
*/
isHttpUrl(value: string | null | undefined): boolean {
const v = value?.trim().toLowerCase();
return !!v && (v.startsWith('http://') || v.startsWith('https://'));
}
Comment on lines +122 to +128
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This introduces a second, slightly different HTTP(S) URL detection implementation compared to MetadataRepresentationListElementComponent.isLink() (regex-based). Consider centralizing URL detection in a shared util/pipe so future tweaks (e.g., trimming, stricter validation) stay consistent across the UI.

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@milanmajchrak too complex for now?


/**
* Navigate back in browser history.
*/
Expand Down
Loading