Skip to content

Commit 1c69eae

Browse files
Merge branch 'dspace-cris-2023_02_x' into gdpr-metrics_2023_02_x_DSC-1413
2 parents 63d65cb + 152acdd commit 1c69eae

12 files changed

Lines changed: 462 additions & 6 deletions

File tree

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import { ComponentFixture, TestBed, fakeAsync } from '@angular/core/testing';
2+
import { CollectionPageComponent } from './collection-page.component';
3+
import { ActivatedRoute, Router } from '@angular/router';
4+
import { of } from 'rxjs';
5+
import { CollectionDataService } from '../core/data/collection-data.service';
6+
import { AuthService } from '../core/auth/auth.service';
7+
import { PaginationService } from '../core/pagination/pagination.service';
8+
import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service';
9+
import { DSONameService } from '../core/breadcrumbs/dso-name.service';
10+
import { APP_CONFIG } from '../../../src/config/app-config.interface';
11+
import { PLATFORM_ID } from '@angular/core';
12+
import { ActivatedRouteStub } from '../shared/testing/active-router.stub';
13+
import { RouterStub } from '../shared/testing/router.stub';
14+
import { environment } from 'src/environments/environment.test';
15+
import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
16+
import { Collection } from '../core/shared/collection.model';
17+
import { SearchService } from '../core/shared/search/search.service';
18+
import { By } from '@angular/platform-browser';
19+
import { FormsModule } from '@angular/forms';
20+
import { RouterTestingModule } from '@angular/router/testing';
21+
import { TranslateModule } from '@ngx-translate/core';
22+
import { VarDirective } from '../shared/utils/var.directive';
23+
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
24+
import { Bitstream } from '../core/shared/bitstream.model';
25+
26+
describe('CollectionPageComponent', () => {
27+
let component: CollectionPageComponent;
28+
let compAsAny: any;
29+
let fixture: ComponentFixture<CollectionPageComponent>;
30+
31+
let collectionDataServiceSpy: jasmine.SpyObj<CollectionDataService>;
32+
let authServiceSpy: jasmine.SpyObj<AuthService>;
33+
let paginationServiceSpy: jasmine.SpyObj<PaginationService>;
34+
let authorizationDataServiceSpy: jasmine.SpyObj<AuthorizationDataService>;
35+
let dsoNameServiceSpy: jasmine.SpyObj<DSONameService>;
36+
let searchServiceSpy: jasmine.SpyObj<SearchService>;
37+
let aroute = new ActivatedRouteStub();
38+
let router = new RouterStub();
39+
40+
const collection = Object.assign(new Collection(), {});
41+
42+
beforeEach(async () => {
43+
authServiceSpy = jasmine.createSpyObj('AuthService', ['isAuthenticated']);
44+
paginationServiceSpy = jasmine.createSpyObj('PaginationService', ['getCurrentPagination', 'getCurrentSort', 'clearPagination']);
45+
authorizationDataServiceSpy = jasmine.createSpyObj('AuthorizationDataService', ['isAuthorized']);
46+
collectionDataServiceSpy = jasmine.createSpyObj('CollectionDataService', ['findById', 'getAuthorizedCollection']);
47+
searchServiceSpy = jasmine.createSpyObj('SearchService', ['search']);
48+
dsoNameServiceSpy = jasmine.createSpyObj('DSONameService', ['getName']);
49+
50+
await TestBed.configureTestingModule({
51+
imports: [RouterTestingModule, FormsModule, TranslateModule.forRoot(), BrowserAnimationsModule],
52+
declarations: [CollectionPageComponent, VarDirective],
53+
providers: [
54+
{ provide: ActivatedRoute, useValue: aroute },
55+
{ provide: Router, useValue: router },
56+
{ provide: CollectionDataService, useValue: collectionDataServiceSpy },
57+
{ provide: AuthService, useValue: authServiceSpy },
58+
{ provide: PaginationService, useValue: paginationServiceSpy },
59+
{ provide: AuthorizationDataService, useValue: authorizationDataServiceSpy },
60+
{ provide: DSONameService, useValue: dsoNameServiceSpy },
61+
{ provide: SearchService, useValue: searchServiceSpy },
62+
{ provide: APP_CONFIG, useValue: environment },
63+
{ provide: PLATFORM_ID, useValue: 'browser' },
64+
]
65+
})
66+
.compileComponents();
67+
});
68+
69+
beforeEach(() => {
70+
fixture = TestBed.createComponent(CollectionPageComponent);
71+
component = fixture.componentInstance;
72+
compAsAny = component as any;
73+
component.collectionRD$ = createSuccessfulRemoteDataObject$(Object.assign(new Collection(), {
74+
name: 'Test Collection',
75+
}));
76+
fixture.detectChanges();
77+
});
78+
79+
it('should create', () => {
80+
expect(component).toBeTruthy();
81+
});
82+
83+
it('should initialize the component', () => {
84+
const routeData = {
85+
dso: createSuccessfulRemoteDataObject$(collection),
86+
};
87+
Object.defineProperty(TestBed.inject(ActivatedRoute), 'data', {
88+
get: () => of(routeData),
89+
});
90+
authorizationDataServiceSpy.isAuthorized.and.returnValue(of(true));
91+
component.ngOnInit();
92+
93+
expect(component.collectionRD$).toBeDefined();
94+
expect(component.logoRD$).toBeDefined();
95+
expect(component.isCollectionAdmin$).toBeDefined();
96+
expect(compAsAny.paginationChanges$).toBeDefined();
97+
expect(component.itemRD$).toBeDefined();
98+
expect(component.collectionPageRoute$).toBeDefined();
99+
});
100+
101+
it('should display collection name', fakeAsync(() => {
102+
component.collectionRD$ = createSuccessfulRemoteDataObject$(Object.assign(new Collection(), {
103+
name: 'Test Collection',
104+
}));
105+
fixture.detectChanges();
106+
fixture.whenStable().then(() => {
107+
const collectionNameElement = fixture.debugElement.query(By.css('ds-comcol-page-header')).nativeElement;
108+
expect(collectionNameElement.textContent.trim()).toBe('Test Collection');
109+
});
110+
}));
111+
112+
it('should display collection logo if available', () => {
113+
component.collectionRD$ = createSuccessfulRemoteDataObject$(Object.assign(new Collection(), {
114+
name: 'Test Collection',
115+
}));
116+
component.logoRD$ = createSuccessfulRemoteDataObject$(Object.assign(new Bitstream(), {
117+
name: 'Test Logo',
118+
}));
119+
fixture.detectChanges();
120+
121+
fixture.whenStable().then(() => {
122+
const logoElement = fixture.debugElement.query(By.css('ds-comcol-page-logo')).nativeElement;
123+
expect(logoElement).toBeTruthy();
124+
});
125+
});
126+
127+
it('should not display collection logo if not available', () => {
128+
component.collectionRD$ = createSuccessfulRemoteDataObject$(Object.assign(new Collection(), {
129+
name: 'Test Collection',
130+
}));
131+
component.logoRD$ = of({ hasSucceeded: false, payload: null }) as any;
132+
fixture.detectChanges();
133+
134+
fixture.whenStable().then(() => {
135+
const logoElement = fixture.debugElement.query(By.css('ds-comcol-page-logo'));
136+
expect(logoElement).toBeNull();
137+
});
138+
});
139+
140+
it('should clear pagination on ngOnDestroy', () => {
141+
component.ngOnDestroy();
142+
expect(paginationServiceSpy.clearPagination).toHaveBeenCalledWith(component.paginationConfig.id);
143+
});
144+
});
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
import { ActivatedRoute, Router } from '@angular/router';
3+
import { CommunityPageComponent } from './community-page.component';
4+
import { AuthService } from '../core/auth/auth.service';
5+
import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service';
6+
import { DSONameService } from '../core/breadcrumbs/dso-name.service';
7+
import { ActivatedRouteStub } from '../shared/testing/active-router.stub';
8+
import { RouterStub } from '../shared/testing/router.stub';
9+
import { RouterTestingModule } from '@angular/router/testing';
10+
import { FormsModule } from '@angular/forms';
11+
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
12+
import { TranslateModule } from '@ngx-translate/core';
13+
import { VarDirective } from '../shared/utils/var.directive';
14+
import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
15+
import { Community } from '../core/shared/community.model';
16+
import { of } from 'rxjs';
17+
import { CommunityDataService } from '../core/data/community-data.service';
18+
import { MetadataService } from '../core/metadata/metadata.service';
19+
import { Bitstream } from '../core/shared/bitstream.model';
20+
import { By } from '@angular/platform-browser';
21+
22+
describe('CommunityPageComponent', () => {
23+
let component: CommunityPageComponent;
24+
let fixture: ComponentFixture<CommunityPageComponent>;
25+
26+
let authServiceSpy: jasmine.SpyObj<AuthService>;
27+
let authorizationDataServiceSpy: jasmine.SpyObj<AuthorizationDataService>;
28+
let dsoNameServiceSpy: jasmine.SpyObj<DSONameService>;
29+
let aroute = new ActivatedRouteStub();
30+
let router = new RouterStub();
31+
32+
const community = Object.assign(new Community(), {
33+
id: 'test-community',
34+
uuid: 'test-community',
35+
metadata: [
36+
{
37+
key: 'dc.title',
38+
language: 'en_US',
39+
value: 'test community'
40+
}
41+
],
42+
logo: createSuccessfulRemoteDataObject$(new Bitstream()),
43+
});
44+
45+
beforeEach(async () => {
46+
authServiceSpy = jasmine.createSpyObj('AuthService', ['isAuthenticated']);
47+
authorizationDataServiceSpy = jasmine.createSpyObj('AuthorizationDataService', ['isAuthorized']);
48+
dsoNameServiceSpy = jasmine.createSpyObj('DSONameService', ['getName']);
49+
await TestBed.configureTestingModule({
50+
imports: [RouterTestingModule, FormsModule, TranslateModule.forRoot(), BrowserAnimationsModule],
51+
declarations: [CommunityPageComponent, VarDirective],
52+
providers: [
53+
{ provide: ActivatedRoute, useValue: aroute },
54+
{ provide: Router, useValue: router },
55+
{ provide: AuthService, useValue: authServiceSpy },
56+
{ provide: AuthorizationDataService, useValue: authorizationDataServiceSpy },
57+
{ provide: DSONameService, useValue: dsoNameServiceSpy },
58+
{ provide: CommunityDataService, useValue: {} },
59+
{ provide: MetadataService, useValue: {} }
60+
]
61+
}).compileComponents();
62+
});
63+
64+
beforeEach(() => {
65+
fixture = TestBed.createComponent(CommunityPageComponent);
66+
component = fixture.componentInstance;
67+
fixture.detectChanges();
68+
});
69+
70+
it('should create', () => {
71+
expect(component).toBeTruthy();
72+
});
73+
74+
it('should initialize the component', () => {
75+
const routeData = {
76+
data: of({ dso: createSuccessfulRemoteDataObject$(community) }),
77+
};
78+
authorizationDataServiceSpy.isAuthorized.and.returnValue(of(true));
79+
80+
Object.defineProperty(TestBed.inject(ActivatedRoute), 'data', {
81+
get: () => of(routeData),
82+
});
83+
84+
component.ngOnInit();
85+
expect(component.communityRD$).toBeDefined();
86+
expect(component.logoRD$).toBeDefined();
87+
expect(component.communityPageRoute$).toBeDefined();
88+
expect(component.isCommunityAdmin$).toBeDefined();
89+
});
90+
91+
it('should display community logo if available', () => {
92+
component.communityRD$ = createSuccessfulRemoteDataObject$(community);
93+
fixture.detectChanges();
94+
95+
fixture.whenStable().then(() => {
96+
const logoElement = fixture.debugElement.query(By.css('ds-comcol-page-logo')).nativeElement;
97+
expect(logoElement).toBeTruthy();
98+
});
99+
});
100+
101+
102+
it('should not display community logo if not available', () => {
103+
component.communityRD$ = createSuccessfulRemoteDataObject$(Object.assign(new Community(), {
104+
name: 'Test',
105+
logo: createSuccessfulRemoteDataObject$(null),
106+
}));
107+
fixture.detectChanges();
108+
109+
fixture.whenStable().then(() => {
110+
const logoElement = fixture.debugElement.query(By.css('ds-comcol-page-logo'));
111+
expect(logoElement).toBeNull();
112+
});
113+
});
114+
115+
it('should display collection name', () => {
116+
component.communityRD$ = createSuccessfulRemoteDataObject$(Object.assign(community));
117+
fixture.detectChanges();
118+
fixture.whenStable().then(() => {
119+
const collectionNameElement = fixture.debugElement.query(By.css('ds-comcol-page-header')).nativeElement;
120+
expect(collectionNameElement.textContent.trim()).toBe('Test Collection');
121+
});
122+
});
123+
});
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { TestBed, waitForAsync } from '@angular/core/testing';
2+
import { InternalLinkService } from './internal-link.service';
3+
import { NativeWindowService } from './window.service';
4+
5+
describe('InternalLinkService', () => {
6+
let service: InternalLinkService;
7+
8+
beforeEach(waitForAsync(() => {
9+
return TestBed.configureTestingModule({
10+
providers: [
11+
InternalLinkService,
12+
{ provide: NativeWindowService, useValue: { nativeWindow: { location: { origin: 'https://currentdomain' } } } },
13+
],
14+
}).compileComponents();
15+
}));
16+
17+
beforeEach(() => {
18+
service = TestBed.inject(InternalLinkService);
19+
});
20+
21+
describe('isLinkInternal', () => {
22+
it('should return true for internal link starting with "/"', () => {
23+
const result = service.isLinkInternal('/my-link');
24+
expect(result).toBe(true);
25+
});
26+
27+
it('should return true for internal link starting with currentURL', () => {
28+
const result = service.isLinkInternal('https://currentdomain/my-link');
29+
expect(result).toBe(true);
30+
});
31+
32+
it('should return true for internal link starting with "currentdomain"', () => {
33+
const result = service.isLinkInternal('currentdomain/my-link');
34+
expect(result).toBe(true);
35+
});
36+
37+
it('should return false for external link', () => {
38+
const result = service.isLinkInternal('https://externaldomain/my-link');
39+
expect(result).toBe(false);
40+
});
41+
42+
it('should return true for internal link without leading "/"', () => {
43+
const result = service.isLinkInternal('my-link');
44+
expect(result).toBe(true);
45+
});
46+
});
47+
48+
describe('transformInternalLink', () => {
49+
it('should transform internal link by removing currentURL', () => {
50+
const result = service.getRelativePath('https://currentdomain/my-link');
51+
expect(result).toBe('/my-link');
52+
});
53+
54+
it('should transform internal link by adding leading "/" if missing', () => {
55+
const result = service.getRelativePath('currentdomain/my-link');
56+
expect(result).toBe('/my-link');
57+
});
58+
59+
it('should return unchanged link for external link', () => {
60+
const result = service.getRelativePath('https://externalDomain/my-link');
61+
expect(result).toBe('https://externalDomain/my-link');
62+
});
63+
64+
it('should return unchanged link for internal link with leading "/"', () => {
65+
const result = service.getRelativePath('/my-link');
66+
expect(result).toBe('/my-link');
67+
});
68+
});
69+
70+
});
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { Inject, Injectable } from '@angular/core';
2+
import { NativeWindowRef, NativeWindowService } from './window.service';
3+
4+
/**
5+
* LinkService provides utility functions for working with links, such as checking if a link is internal
6+
* and transforming internal links based on the current URL.
7+
*/
8+
@Injectable()
9+
export class InternalLinkService {
10+
currentURL = this._window.nativeWindow.location.origin;
11+
12+
constructor(
13+
@Inject(NativeWindowService) protected _window: NativeWindowRef,
14+
) {
15+
16+
}
17+
18+
/**
19+
* Check if the provided link is internal, i.e., it starts with a '/' or matches the current URL.
20+
*
21+
* @param link The link to be checked.
22+
* @returns A boolean indicating whether the link is internal.
23+
*/
24+
public isLinkInternal(link: string): boolean {
25+
// Create a Domain object for the provided link
26+
const currentDomain = new URL(this.currentURL).hostname;
27+
28+
return link.startsWith('/')
29+
|| link.startsWith(this.currentURL)
30+
|| link.startsWith(currentDomain)
31+
|| link === currentDomain
32+
|| !link.includes('://');
33+
}
34+
35+
/**
36+
* Get the relative path for an internal link based on the current URL.
37+
*
38+
* @param link The internal link to be transformed.
39+
* @returns The relative path for the given internal link.
40+
*/
41+
public getRelativePath(link: string): string {
42+
// Create a Domain object for the provided link
43+
const currentDomain = new URL(this.currentURL).hostname;
44+
45+
if (link.startsWith(this.currentURL)) {
46+
const currentSegments = link.substring(this.currentURL.length);
47+
return currentSegments.startsWith('/') ? currentSegments : `/${currentSegments}`;
48+
}
49+
50+
if (link.startsWith(currentDomain)) {
51+
const currentSegments = link.substring(currentDomain.length);
52+
return currentSegments.startsWith('/') ? currentSegments : `/${currentSegments}`;
53+
}
54+
55+
return link;
56+
}
57+
}

0 commit comments

Comments
 (0)