mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
Merge branch 'main-gh4s' into CST-6782-refactor
This commit is contained in:
@@ -7,8 +7,9 @@ const appConfig: AppConfig = buildAppConfig();
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Calls `ng serve` with the following arguments configured for the UI in the app config: host, port, nameSpace, ssl
|
* Calls `ng serve` with the following arguments configured for the UI in the app config: host, port, nameSpace, ssl
|
||||||
|
* Any CLI arguments given to this script are patched through to `ng serve` as well.
|
||||||
*/
|
*/
|
||||||
child.spawn(
|
child.spawn(
|
||||||
`ng serve --host ${appConfig.ui.host} --port ${appConfig.ui.port} --serve-path ${appConfig.ui.nameSpace} --ssl ${appConfig.ui.ssl}`,
|
`ng serve --host ${appConfig.ui.host} --port ${appConfig.ui.port} --serve-path ${appConfig.ui.nameSpace} --ssl ${appConfig.ui.ssl} ${process.argv.slice(2).join(' ')}`,
|
||||||
{ stdio: 'inherit', shell: true }
|
{ stdio: 'inherit', shell: true }
|
||||||
);
|
);
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
<nav @slideHorizontal class="navbar navbar-dark p-0"
|
<nav class="navbar navbar-dark p-0"
|
||||||
[ngClass]="{'active': sidebarOpen, 'inactive': sidebarClosed}"
|
[ngClass]="{'active': sidebarOpen, 'inactive': sidebarClosed}"
|
||||||
[@slideSidebar]="{
|
[@slideSidebar]="{
|
||||||
value: (!(sidebarExpanded | async) ? 'collapsed' : 'expanded'),
|
value: (!(sidebarExpanded | async) ? 'collapsed' : 'expanded'),
|
||||||
|
@@ -2,7 +2,7 @@ import { Component, HostListener, Injector, OnInit } from '@angular/core';
|
|||||||
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
|
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
|
||||||
import { debounceTime, distinctUntilChanged, first, map, withLatestFrom } from 'rxjs/operators';
|
import { debounceTime, distinctUntilChanged, first, map, withLatestFrom } from 'rxjs/operators';
|
||||||
import { AuthService } from '../../core/auth/auth.service';
|
import { AuthService } from '../../core/auth/auth.service';
|
||||||
import { slideHorizontal, slideSidebar } from '../../shared/animations/slide';
|
import { slideSidebar } from '../../shared/animations/slide';
|
||||||
import { MenuComponent } from '../../shared/menu/menu.component';
|
import { MenuComponent } from '../../shared/menu/menu.component';
|
||||||
import { MenuService } from '../../shared/menu/menu.service';
|
import { MenuService } from '../../shared/menu/menu.service';
|
||||||
import { CSSVariableService } from '../../shared/sass-helper/sass-helper.service';
|
import { CSSVariableService } from '../../shared/sass-helper/sass-helper.service';
|
||||||
@@ -18,7 +18,7 @@ import { ThemeService } from '../../shared/theme-support/theme.service';
|
|||||||
selector: 'ds-admin-sidebar',
|
selector: 'ds-admin-sidebar',
|
||||||
templateUrl: './admin-sidebar.component.html',
|
templateUrl: './admin-sidebar.component.html',
|
||||||
styleUrls: ['./admin-sidebar.component.scss'],
|
styleUrls: ['./admin-sidebar.component.scss'],
|
||||||
animations: [slideHorizontal, slideSidebar]
|
animations: [slideSidebar]
|
||||||
})
|
})
|
||||||
export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
||||||
/**
|
/**
|
||||||
|
@@ -7,17 +7,24 @@ import { TestScheduler } from 'rxjs/testing';
|
|||||||
import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils';
|
||||||
import { ShortLivedToken } from './models/short-lived-token.model';
|
import { ShortLivedToken } from './models/short-lived-token.model';
|
||||||
import { RemoteData } from '../data/remote-data';
|
import { RemoteData } from '../data/remote-data';
|
||||||
|
import { HttpOptions } from '../dspace-rest/dspace-rest.service';
|
||||||
|
import objectContaining = jasmine.objectContaining;
|
||||||
|
import { AuthStatus } from './models/auth-status.model';
|
||||||
|
import { RestRequestMethod } from '../data/rest-request-method';
|
||||||
|
|
||||||
describe(`AuthRequestService`, () => {
|
describe(`AuthRequestService`, () => {
|
||||||
let halService: HALEndpointService;
|
let halService: HALEndpointService;
|
||||||
let endpointURL: string;
|
let endpointURL: string;
|
||||||
|
let requestID: string;
|
||||||
let shortLivedToken: ShortLivedToken;
|
let shortLivedToken: ShortLivedToken;
|
||||||
let shortLivedTokenRD: RemoteData<ShortLivedToken>;
|
let shortLivedTokenRD: RemoteData<ShortLivedToken>;
|
||||||
let requestService: RequestService;
|
let requestService: RequestService;
|
||||||
let rdbService: RemoteDataBuildService;
|
let rdbService: RemoteDataBuildService;
|
||||||
let service: AuthRequestService;
|
let service;
|
||||||
let testScheduler;
|
let testScheduler;
|
||||||
|
|
||||||
|
const status = new AuthStatus();
|
||||||
|
|
||||||
class TestAuthRequestService extends AuthRequestService {
|
class TestAuthRequestService extends AuthRequestService {
|
||||||
constructor(
|
constructor(
|
||||||
hes: HALEndpointService,
|
hes: HALEndpointService,
|
||||||
@@ -34,6 +41,7 @@ describe(`AuthRequestService`, () => {
|
|||||||
|
|
||||||
const init = (cold: typeof TestScheduler.prototype.createColdObservable) => {
|
const init = (cold: typeof TestScheduler.prototype.createColdObservable) => {
|
||||||
endpointURL = 'https://rest.api/auth';
|
endpointURL = 'https://rest.api/auth';
|
||||||
|
requestID = 'requestID';
|
||||||
shortLivedToken = Object.assign(new ShortLivedToken(), {
|
shortLivedToken = Object.assign(new ShortLivedToken(), {
|
||||||
value: 'some-token'
|
value: 'some-token'
|
||||||
});
|
});
|
||||||
@@ -43,13 +51,16 @@ describe(`AuthRequestService`, () => {
|
|||||||
'getEndpoint': cold('a', { a: endpointURL })
|
'getEndpoint': cold('a', { a: endpointURL })
|
||||||
});
|
});
|
||||||
requestService = jasmine.createSpyObj('requestService', {
|
requestService = jasmine.createSpyObj('requestService', {
|
||||||
'send': null
|
'generateRequestId': requestID,
|
||||||
|
'send': null,
|
||||||
});
|
});
|
||||||
rdbService = jasmine.createSpyObj('rdbService', {
|
rdbService = jasmine.createSpyObj('rdbService', {
|
||||||
'buildFromRequestUUID': cold('a', { a: shortLivedTokenRD })
|
'buildFromRequestUUID': cold('a', { a: shortLivedTokenRD })
|
||||||
});
|
});
|
||||||
|
|
||||||
service = new TestAuthRequestService(halService, requestService, rdbService);
|
service = new TestAuthRequestService(halService, requestService, rdbService);
|
||||||
|
|
||||||
|
spyOn(service as any, 'fetchRequest').and.returnValue(cold('a', { a: createSuccessfulRemoteDataObject(status) }));
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -58,6 +69,94 @@ describe(`AuthRequestService`, () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('REST request methods', () => {
|
||||||
|
let options: HttpOptions;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
options = Object.create({});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GET', () => {
|
||||||
|
it('should send a GET request to the right endpoint and return the auth status', () => {
|
||||||
|
testScheduler.run(({ cold, expectObservable, flush }) => {
|
||||||
|
init(cold);
|
||||||
|
|
||||||
|
expectObservable(service.getRequest('method', options)).toBe('a', {
|
||||||
|
a: objectContaining({ payload: status }),
|
||||||
|
});
|
||||||
|
flush();
|
||||||
|
|
||||||
|
expect(requestService.send).toHaveBeenCalledWith(objectContaining({
|
||||||
|
uuid: requestID,
|
||||||
|
href: endpointURL + '/method',
|
||||||
|
method: RestRequestMethod.GET,
|
||||||
|
body: undefined,
|
||||||
|
options,
|
||||||
|
}));
|
||||||
|
expect((service as any).fetchRequest).toHaveBeenCalledWith(requestID);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should send the request even if caller doesn\'t subscribe to the response', () => {
|
||||||
|
testScheduler.run(({ cold, flush }) => {
|
||||||
|
init(cold);
|
||||||
|
|
||||||
|
service.getRequest('method', options);
|
||||||
|
flush();
|
||||||
|
|
||||||
|
expect(requestService.send).toHaveBeenCalledWith(objectContaining({
|
||||||
|
uuid: requestID,
|
||||||
|
href: endpointURL + '/method',
|
||||||
|
method: RestRequestMethod.GET,
|
||||||
|
body: undefined,
|
||||||
|
options,
|
||||||
|
}));
|
||||||
|
expect((service as any).fetchRequest).toHaveBeenCalledWith(requestID);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('POST', () => {
|
||||||
|
it('should send a POST request to the right endpoint and return the auth status', () => {
|
||||||
|
testScheduler.run(({ cold, expectObservable, flush }) => {
|
||||||
|
init(cold);
|
||||||
|
|
||||||
|
expectObservable(service.postToEndpoint('method', { content: 'something' }, options)).toBe('a', {
|
||||||
|
a: objectContaining({ payload: status }),
|
||||||
|
});
|
||||||
|
flush();
|
||||||
|
|
||||||
|
expect(requestService.send).toHaveBeenCalledWith(objectContaining({
|
||||||
|
uuid: requestID,
|
||||||
|
href: endpointURL + '/method',
|
||||||
|
method: RestRequestMethod.POST,
|
||||||
|
body: { content: 'something' },
|
||||||
|
options,
|
||||||
|
}));
|
||||||
|
expect((service as any).fetchRequest).toHaveBeenCalledWith(requestID);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should send the request even if caller doesn\'t subscribe to the response', () => {
|
||||||
|
testScheduler.run(({ cold, flush }) => {
|
||||||
|
init(cold);
|
||||||
|
|
||||||
|
service.postToEndpoint('method', { content: 'something' }, options);
|
||||||
|
flush();
|
||||||
|
|
||||||
|
expect(requestService.send).toHaveBeenCalledWith(objectContaining({
|
||||||
|
uuid: requestID,
|
||||||
|
href: endpointURL + '/method',
|
||||||
|
method: RestRequestMethod.POST,
|
||||||
|
body: { content: 'something' },
|
||||||
|
options,
|
||||||
|
}));
|
||||||
|
expect((service as any).fetchRequest).toHaveBeenCalledWith(requestID);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe(`getShortlivedToken`, () => {
|
describe(`getShortlivedToken`, () => {
|
||||||
it(`should call createShortLivedTokenRequest with the url for the endpoint`, () => {
|
it(`should call createShortLivedTokenRequest with the url for the endpoint`, () => {
|
||||||
testScheduler.run(({ cold, expectObservable, flush }) => {
|
testScheduler.run(({ cold, expectObservable, flush }) => {
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { distinctUntilChanged, filter, map, mergeMap, switchMap, tap } from 'rxjs/operators';
|
import { distinctUntilChanged, filter, map, switchMap, tap, take } from 'rxjs/operators';
|
||||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
import { RequestService } from '../data/request.service';
|
import { RequestService } from '../data/request.service';
|
||||||
import { isNotEmpty } from '../../shared/empty.util';
|
import { isNotEmpty } from '../../shared/empty.util';
|
||||||
@@ -27,8 +27,13 @@ export abstract class AuthRequestService {
|
|||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fetchRequest(request: RestRequest, ...linksToFollow: FollowLinkConfig<AuthStatus>[]): Observable<RemoteData<AuthStatus>> {
|
/**
|
||||||
return this.rdbService.buildFromRequestUUID<AuthStatus>(request.uuid, ...linksToFollow).pipe(
|
* Fetch the response to a request from the cache, once it's completed.
|
||||||
|
* @param requestId the UUID of the request for which to retrieve the response
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
protected fetchRequest(requestId: string, ...linksToFollow: FollowLinkConfig<AuthStatus>[]): Observable<RemoteData<AuthStatus>> {
|
||||||
|
return this.rdbService.buildFromRequestUUID<AuthStatus>(requestId, ...linksToFollow).pipe(
|
||||||
getFirstCompletedRemoteData(),
|
getFirstCompletedRemoteData(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -44,28 +49,48 @@ export abstract class AuthRequestService {
|
|||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a POST request to an authentication endpoint
|
||||||
|
* @param method the method to send to (e.g. 'status')
|
||||||
|
* @param body the data to send (optional)
|
||||||
|
* @param options the HTTP options for the request
|
||||||
|
*/
|
||||||
public postToEndpoint(method: string, body?: any, options?: HttpOptions): Observable<RemoteData<AuthStatus>> {
|
public postToEndpoint(method: string, body?: any, options?: HttpOptions): Observable<RemoteData<AuthStatus>> {
|
||||||
return this.halService.getEndpoint(this.linkName).pipe(
|
const requestId = this.requestService.generateRequestId();
|
||||||
|
|
||||||
|
this.halService.getEndpoint(this.linkName).pipe(
|
||||||
filter((href: string) => isNotEmpty(href)),
|
filter((href: string) => isNotEmpty(href)),
|
||||||
map((endpointURL) => this.getEndpointByMethod(endpointURL, method)),
|
map((endpointURL) => this.getEndpointByMethod(endpointURL, method)),
|
||||||
distinctUntilChanged(),
|
distinctUntilChanged(),
|
||||||
map((endpointURL: string) => new PostRequest(this.requestService.generateRequestId(), endpointURL, body, options)),
|
map((endpointURL: string) => new PostRequest(requestId, endpointURL, body, options)),
|
||||||
tap((request: PostRequest) => this.requestService.send(request)),
|
take(1)
|
||||||
mergeMap((request: PostRequest) => this.fetchRequest(request)),
|
).subscribe((request: PostRequest) => {
|
||||||
distinctUntilChanged());
|
this.requestService.send(request);
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.fetchRequest(requestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a GET request to an authentication endpoint
|
||||||
|
* @param method the method to send to (e.g. 'status')
|
||||||
|
* @param options the HTTP options for the request
|
||||||
|
*/
|
||||||
public getRequest(method: string, options?: HttpOptions, ...linksToFollow: FollowLinkConfig<any>[]): Observable<RemoteData<AuthStatus>> {
|
public getRequest(method: string, options?: HttpOptions, ...linksToFollow: FollowLinkConfig<any>[]): Observable<RemoteData<AuthStatus>> {
|
||||||
return this.halService.getEndpoint(this.linkName).pipe(
|
const requestId = this.requestService.generateRequestId();
|
||||||
|
|
||||||
|
this.halService.getEndpoint(this.linkName).pipe(
|
||||||
filter((href: string) => isNotEmpty(href)),
|
filter((href: string) => isNotEmpty(href)),
|
||||||
map((endpointURL) => this.getEndpointByMethod(endpointURL, method, ...linksToFollow)),
|
map((endpointURL) => this.getEndpointByMethod(endpointURL, method, ...linksToFollow)),
|
||||||
distinctUntilChanged(),
|
distinctUntilChanged(),
|
||||||
map((endpointURL: string) => new GetRequest(this.requestService.generateRequestId(), endpointURL, undefined, options)),
|
map((endpointURL: string) => new GetRequest(requestId, endpointURL, undefined, options)),
|
||||||
tap((request: GetRequest) => this.requestService.send(request)),
|
take(1)
|
||||||
mergeMap((request: GetRequest) => this.fetchRequest(request, ...linksToFollow)),
|
).subscribe((request: GetRequest) => {
|
||||||
distinctUntilChanged());
|
this.requestService.send(request);
|
||||||
}
|
});
|
||||||
|
|
||||||
|
return this.fetchRequest(requestId, ...linksToFollow);
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Factory function to create the request object to send. This needs to be a POST client side and
|
* Factory function to create the request object to send. This needs to be a POST client side and
|
||||||
* a GET server side. Due to CSRF validation, the server isn't allowed to send a POST, so we allow
|
* a GET server side. Due to CSRF validation, the server isn't allowed to send a POST, so we allow
|
||||||
|
@@ -17,6 +17,7 @@ import {
|
|||||||
import { AuthTokenInfo } from './models/auth-token-info.model';
|
import { AuthTokenInfo } from './models/auth-token-info.model';
|
||||||
import { AuthMethod } from './models/auth.method';
|
import { AuthMethod } from './models/auth.method';
|
||||||
import { AuthMethodType } from './models/auth.method-type';
|
import { AuthMethodType } from './models/auth.method-type';
|
||||||
|
import { StoreActionTypes } from '../../store.actions';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The auth state.
|
* The auth state.
|
||||||
@@ -251,6 +252,11 @@ export function authReducer(state: any = initialState, action: AuthActions): Aut
|
|||||||
idle: false,
|
idle: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
case StoreActionTypes.REHYDRATE:
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
blocking: true,
|
||||||
|
});
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
@@ -15,10 +15,11 @@ import { RESOURCE_POLICY } from '../resource-policy/models/resource-policy.resou
|
|||||||
import { COMMUNITY } from './community.resource-type';
|
import { COMMUNITY } from './community.resource-type';
|
||||||
import { Community } from './community.model';
|
import { Community } from './community.model';
|
||||||
import { ChildHALResource } from './child-hal-resource.model';
|
import { ChildHALResource } from './child-hal-resource.model';
|
||||||
|
import { HandleObject } from './handle-object.model';
|
||||||
|
|
||||||
@typedObject
|
@typedObject
|
||||||
@inheritSerialization(DSpaceObject)
|
@inheritSerialization(DSpaceObject)
|
||||||
export class Collection extends DSpaceObject implements ChildHALResource {
|
export class Collection extends DSpaceObject implements ChildHALResource, HandleObject {
|
||||||
static type = COLLECTION;
|
static type = COLLECTION;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -11,10 +11,11 @@ import { COMMUNITY } from './community.resource-type';
|
|||||||
import { DSpaceObject } from './dspace-object.model';
|
import { DSpaceObject } from './dspace-object.model';
|
||||||
import { HALLink } from './hal-link.model';
|
import { HALLink } from './hal-link.model';
|
||||||
import { ChildHALResource } from './child-hal-resource.model';
|
import { ChildHALResource } from './child-hal-resource.model';
|
||||||
|
import { HandleObject } from './handle-object.model';
|
||||||
|
|
||||||
@typedObject
|
@typedObject
|
||||||
@inheritSerialization(DSpaceObject)
|
@inheritSerialization(DSpaceObject)
|
||||||
export class Community extends DSpaceObject implements ChildHALResource {
|
export class Community extends DSpaceObject implements ChildHALResource, HandleObject {
|
||||||
static type = COMMUNITY;
|
static type = COMMUNITY;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
8
src/app/core/shared/handle-object.model.ts
Normal file
8
src/app/core/shared/handle-object.model.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* Interface representing an object in DSpace that contains a handle
|
||||||
|
*/
|
||||||
|
export interface HandleObject {
|
||||||
|
|
||||||
|
handle: string;
|
||||||
|
|
||||||
|
}
|
@@ -23,13 +23,14 @@ import { BITSTREAM } from './bitstream.resource-type';
|
|||||||
import { Bitstream } from './bitstream.model';
|
import { Bitstream } from './bitstream.model';
|
||||||
import { ACCESS_STATUS } from 'src/app/shared/object-list/access-status-badge/access-status.resource-type';
|
import { ACCESS_STATUS } from 'src/app/shared/object-list/access-status-badge/access-status.resource-type';
|
||||||
import { AccessStatusObject } from 'src/app/shared/object-list/access-status-badge/access-status.model';
|
import { AccessStatusObject } from 'src/app/shared/object-list/access-status-badge/access-status.model';
|
||||||
|
import { HandleObject } from './handle-object.model';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class representing a DSpace Item
|
* Class representing a DSpace Item
|
||||||
*/
|
*/
|
||||||
@typedObject
|
@typedObject
|
||||||
@inheritSerialization(DSpaceObject)
|
@inheritSerialization(DSpaceObject)
|
||||||
export class Item extends DSpaceObject implements ChildHALResource {
|
export class Item extends DSpaceObject implements ChildHALResource, HandleObject {
|
||||||
static type = ITEM;
|
static type = ITEM;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -5,7 +5,7 @@ import { inject, TestBed, waitForAsync } from '@angular/core/testing';
|
|||||||
import { MetadataService } from './core/metadata/metadata.service';
|
import { MetadataService } from './core/metadata/metadata.service';
|
||||||
import { BreadcrumbsService } from './breadcrumbs/breadcrumbs.service';
|
import { BreadcrumbsService } from './breadcrumbs/breadcrumbs.service';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { StoreModule } from '@ngrx/store';
|
import { Store, StoreModule } from '@ngrx/store';
|
||||||
import { authReducer } from './core/auth/auth.reducer';
|
import { authReducer } from './core/auth/auth.reducer';
|
||||||
import { storeModuleConfig } from './app.reducer';
|
import { storeModuleConfig } from './app.reducer';
|
||||||
import { AngularticsProviderMock } from './shared/mocks/angulartics-provider.service.mock';
|
import { AngularticsProviderMock } from './shared/mocks/angulartics-provider.service.mock';
|
||||||
@@ -31,6 +31,7 @@ import { getMockThemeService } from './shared/mocks/theme-service.mock';
|
|||||||
import objectContaining = jasmine.objectContaining;
|
import objectContaining = jasmine.objectContaining;
|
||||||
import createSpyObj = jasmine.createSpyObj;
|
import createSpyObj = jasmine.createSpyObj;
|
||||||
import SpyObj = jasmine.SpyObj;
|
import SpyObj = jasmine.SpyObj;
|
||||||
|
import { getTestScheduler } from 'jasmine-marbles';
|
||||||
|
|
||||||
let spy: SpyObj<any>;
|
let spy: SpyObj<any>;
|
||||||
|
|
||||||
@@ -124,6 +125,15 @@ describe('InitService', () => {
|
|||||||
let metadataServiceSpy;
|
let metadataServiceSpy;
|
||||||
let breadcrumbsServiceSpy;
|
let breadcrumbsServiceSpy;
|
||||||
|
|
||||||
|
const BLOCKING = {
|
||||||
|
t: { core: { auth: { blocking: true } } },
|
||||||
|
f: { core: { auth: { blocking: false } } },
|
||||||
|
};
|
||||||
|
const BOOLEAN = {
|
||||||
|
t: true,
|
||||||
|
f: false,
|
||||||
|
};
|
||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
correlationIdServiceSpy = jasmine.createSpyObj('correlationIdServiceSpy', [
|
correlationIdServiceSpy = jasmine.createSpyObj('correlationIdServiceSpy', [
|
||||||
'initCorrelationId',
|
'initCorrelationId',
|
||||||
@@ -182,6 +192,18 @@ describe('InitService', () => {
|
|||||||
expect(breadcrumbsServiceSpy.listenForRouteChanges).toHaveBeenCalledTimes(1);
|
expect(breadcrumbsServiceSpy.listenForRouteChanges).toHaveBeenCalledTimes(1);
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('authenticationReady', () => {
|
||||||
|
it('should emit & complete the first time auth is unblocked', () => {
|
||||||
|
getTestScheduler().run(({ cold, expectObservable }) => {
|
||||||
|
TestBed.overrideProvider(Store, { useValue: cold('t--t--f--t--f--', BLOCKING) });
|
||||||
|
const service = TestBed.inject(InitService);
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
expectObservable(service.authenticationReady$()).toBe('------(f|)', BOOLEAN);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
*
|
*
|
||||||
* http://www.dspace.org/license/
|
* http://www.dspace.org/license/
|
||||||
*/
|
*/
|
||||||
import { Store } from '@ngrx/store';
|
import { select, Store } from '@ngrx/store';
|
||||||
import { CheckAuthenticationTokenAction } from './core/auth/auth.actions';
|
import { CheckAuthenticationTokenAction } from './core/auth/auth.actions';
|
||||||
import { CorrelationIdService } from './correlation-id/correlation-id.service';
|
import { CorrelationIdService } from './correlation-id/correlation-id.service';
|
||||||
import { APP_INITIALIZER, Inject, Provider, Type } from '@angular/core';
|
import { APP_INITIALIZER, Inject, Provider, Type } from '@angular/core';
|
||||||
@@ -20,6 +20,9 @@ import { Angulartics2DSpace } from './statistics/angulartics/dspace-provider';
|
|||||||
import { MetadataService } from './core/metadata/metadata.service';
|
import { MetadataService } from './core/metadata/metadata.service';
|
||||||
import { BreadcrumbsService } from './breadcrumbs/breadcrumbs.service';
|
import { BreadcrumbsService } from './breadcrumbs/breadcrumbs.service';
|
||||||
import { ThemeService } from './shared/theme-support/theme.service';
|
import { ThemeService } from './shared/theme-support/theme.service';
|
||||||
|
import { isAuthenticationBlocking } from './core/auth/selectors';
|
||||||
|
import { distinctUntilChanged, find } from 'rxjs/operators';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs the initialization of the app.
|
* Performs the initialization of the app.
|
||||||
@@ -186,4 +189,16 @@ export abstract class InitService {
|
|||||||
this.breadcrumbsService.listenForRouteChanges();
|
this.breadcrumbsService.listenForRouteChanges();
|
||||||
this.themeService.listenForRouteChanges();
|
this.themeService.listenForRouteChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emits once authentication is ready (no longer blocking)
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
protected authenticationReady$(): Observable<boolean> {
|
||||||
|
return this.store.pipe(
|
||||||
|
select(isAuthenticationBlocking),
|
||||||
|
distinctUntilChanged(),
|
||||||
|
find((b: boolean) => b === false)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { map } from 'rxjs/operators';
|
import { map, startWith } from 'rxjs/operators';
|
||||||
import { Component, Inject, Input, OnInit } from '@angular/core';
|
import { Component, Inject, Input, OnInit } from '@angular/core';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
@@ -71,7 +71,8 @@ export class RootComponent implements OnInit {
|
|||||||
const sidebarCollapsed = this.menuService.isMenuCollapsed(MenuID.ADMIN);
|
const sidebarCollapsed = this.menuService.isMenuCollapsed(MenuID.ADMIN);
|
||||||
this.slideSidebarOver = combineLatestObservable([sidebarCollapsed, this.windowService.isXsOrSm()])
|
this.slideSidebarOver = combineLatestObservable([sidebarCollapsed, this.windowService.isXsOrSm()])
|
||||||
.pipe(
|
.pipe(
|
||||||
map(([collapsed, mobile]) => collapsed || mobile)
|
map(([collapsed, mobile]) => collapsed || mobile),
|
||||||
|
startWith(true),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (this.router.url === getPageInternalServerErrorRoute()) {
|
if (this.router.url === getPageInternalServerErrorRoute()) {
|
||||||
|
@@ -10,13 +10,6 @@ export const slide = trigger('slide', [
|
|||||||
transition('expanded <=> collapsed', animate(250))
|
transition('expanded <=> collapsed', animate(250))
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const slideHorizontal = trigger('slideHorizontal', [
|
|
||||||
state('void', style({ width: 0 })),
|
|
||||||
state('*', style({ width: '*' })),
|
|
||||||
transition(':enter', [animate('200ms')]),
|
|
||||||
transition(':leave', [animate('200ms')])
|
|
||||||
]);
|
|
||||||
|
|
||||||
export const slideMobileNav = trigger('slideMobileNav', [
|
export const slideMobileNav = trigger('slideMobileNav', [
|
||||||
|
|
||||||
state('expanded', style({ height: '100vh' })),
|
state('expanded', style({ height: '100vh' })),
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Inject, Injectable } from '@angular/core';
|
import { Injectable, Inject, Injector } from '@angular/core';
|
||||||
import { createFeatureSelector, createSelector, select, Store } from '@ngrx/store';
|
import { Store, createFeatureSelector, createSelector, select } from '@ngrx/store';
|
||||||
import { BehaviorSubject, EMPTY, Observable, of as observableOf } from 'rxjs';
|
import { BehaviorSubject, EMPTY, Observable, of as observableOf } from 'rxjs';
|
||||||
import { ThemeState } from './theme.reducer';
|
import { ThemeState } from './theme.reducer';
|
||||||
import { SetThemeAction, ThemeActionTypes } from './theme.actions';
|
import { SetThemeAction, ThemeActionTypes } from './theme.actions';
|
||||||
@@ -53,12 +53,13 @@ export class ThemeService {
|
|||||||
private store: Store<ThemeState>,
|
private store: Store<ThemeState>,
|
||||||
private linkService: LinkService,
|
private linkService: LinkService,
|
||||||
private dSpaceObjectDataService: DSpaceObjectDataService,
|
private dSpaceObjectDataService: DSpaceObjectDataService,
|
||||||
|
protected injector: Injector,
|
||||||
@Inject(GET_THEME_CONFIG_FOR_FACTORY) private gtcf: (str) => ThemeConfig,
|
@Inject(GET_THEME_CONFIG_FOR_FACTORY) private gtcf: (str) => ThemeConfig,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
@Inject(DOCUMENT) private document: any,
|
@Inject(DOCUMENT) private document: any,
|
||||||
) {
|
) {
|
||||||
// Create objects from the theme configs in the environment file
|
// Create objects from the theme configs in the environment file
|
||||||
this.themes = environment.themes.map((themeConfig: ThemeConfig) => themeFactory(themeConfig));
|
this.themes = environment.themes.map((themeConfig: ThemeConfig) => themeFactory(themeConfig, injector));
|
||||||
this.hasDynamicTheme = environment.themes.some((themeConfig: any) =>
|
this.hasDynamicTheme = environment.themes.some((themeConfig: any) =>
|
||||||
hasValue(themeConfig.regex) ||
|
hasValue(themeConfig.regex) ||
|
||||||
hasValue(themeConfig.handle) ||
|
hasValue(themeConfig.handle) ||
|
||||||
|
@@ -8,6 +8,7 @@ import { Collection } from '../app/core/shared/collection.model';
|
|||||||
import { Item } from '../app/core/shared/item.model';
|
import { Item } from '../app/core/shared/item.model';
|
||||||
import { ITEM } from '../app/core/shared/item.resource-type';
|
import { ITEM } from '../app/core/shared/item.resource-type';
|
||||||
import { getItemModuleRoute } from '../app/item-page/item-page-routing-paths';
|
import { getItemModuleRoute } from '../app/item-page/item-page-routing-paths';
|
||||||
|
import { HandleService } from '../app/shared/handle.service';
|
||||||
|
|
||||||
describe('Theme Models', () => {
|
describe('Theme Models', () => {
|
||||||
let theme: Theme;
|
let theme: Theme;
|
||||||
@@ -67,24 +68,40 @@ describe('Theme Models', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('HandleTheme', () => {
|
describe('HandleTheme', () => {
|
||||||
|
let handleService;
|
||||||
|
beforeEach(() => {
|
||||||
|
handleService = new HandleService();
|
||||||
|
});
|
||||||
it('should return true when the DSO\'s handle matches the theme\'s handle', () => {
|
it('should return true when the DSO\'s handle matches the theme\'s handle', () => {
|
||||||
theme = new HandleTheme({
|
theme = new HandleTheme({
|
||||||
name: 'matching-handle',
|
name: 'matching-handle',
|
||||||
handle: '1234/5678',
|
handle: '1234/5678',
|
||||||
});
|
}, handleService);
|
||||||
const dso = Object.assign(new Item(), {
|
const matchingDso = Object.assign(new Item(), {
|
||||||
type: ITEM.value,
|
type: ITEM.value,
|
||||||
uuid: 'item-uuid',
|
uuid: 'item-uuid',
|
||||||
handle: '1234/5678',
|
handle: '1234/5678',
|
||||||
|
}, handleService);
|
||||||
|
expect(theme.matches('', matchingDso)).toEqual(true);
|
||||||
});
|
});
|
||||||
expect(theme.matches('', dso)).toEqual(true);
|
it('should return false when the DSO\'s handle contains the theme\'s handle as a subpart', () => {
|
||||||
|
theme = new HandleTheme({
|
||||||
|
name: 'matching-handle',
|
||||||
|
handle: '1234/5678',
|
||||||
|
}, handleService);
|
||||||
|
const dso = Object.assign(new Item(), {
|
||||||
|
type: ITEM.value,
|
||||||
|
uuid: 'item-uuid',
|
||||||
|
handle: '1234/567891011',
|
||||||
|
});
|
||||||
|
expect(theme.matches('', dso)).toEqual(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return false when the handles don\'t match', () => {
|
it('should return false when the handles don\'t match', () => {
|
||||||
theme = new HandleTheme({
|
theme = new HandleTheme({
|
||||||
name: 'no-matching-handle',
|
name: 'no-matching-handle',
|
||||||
handle: '1234/5678',
|
handle: '1234/5678',
|
||||||
});
|
}, handleService);
|
||||||
const dso = Object.assign(new Item(), {
|
const dso = Object.assign(new Item(), {
|
||||||
type: ITEM.value,
|
type: ITEM.value,
|
||||||
uuid: 'item-uuid',
|
uuid: 'item-uuid',
|
||||||
|
@@ -3,6 +3,9 @@ import { Config } from './config.interface';
|
|||||||
import { hasValue, hasNoValue, isNotEmpty } from '../app/shared/empty.util';
|
import { hasValue, hasNoValue, isNotEmpty } from '../app/shared/empty.util';
|
||||||
import { DSpaceObject } from '../app/core/shared/dspace-object.model';
|
import { DSpaceObject } from '../app/core/shared/dspace-object.model';
|
||||||
import { getDSORoute } from '../app/app-routing-paths';
|
import { getDSORoute } from '../app/app-routing-paths';
|
||||||
|
import { HandleObject } from '../app/core/shared/handle-object.model';
|
||||||
|
import { Injector } from '@angular/core';
|
||||||
|
import { HandleService } from '../app/shared/handle.service';
|
||||||
|
|
||||||
export interface NamedThemeConfig extends Config {
|
export interface NamedThemeConfig extends Config {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -82,12 +85,20 @@ export class RegExTheme extends Theme {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class HandleTheme extends Theme {
|
export class HandleTheme extends Theme {
|
||||||
constructor(public config: HandleThemeConfig) {
|
|
||||||
|
private normalizedHandle;
|
||||||
|
|
||||||
|
constructor(public config: HandleThemeConfig,
|
||||||
|
protected handleService: HandleService
|
||||||
|
) {
|
||||||
super(config);
|
super(config);
|
||||||
|
this.normalizedHandle = this.handleService.normalizeHandle(this.config.handle);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
matches(url: string, dso: any): boolean {
|
matches<T extends DSpaceObject & HandleObject>(url: string, dso: T): boolean {
|
||||||
return hasValue(dso) && hasValue(dso.handle) && dso.handle.includes(this.config.handle);
|
return hasValue(dso) && hasValue(dso.handle)
|
||||||
|
&& this.handleService.normalizeHandle(dso.handle) === this.normalizedHandle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,11 +112,11 @@ export class UUIDTheme extends Theme {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const themeFactory = (config: ThemeConfig): Theme => {
|
export const themeFactory = (config: ThemeConfig, injector: Injector): Theme => {
|
||||||
if (hasValue((config as RegExThemeConfig).regex)) {
|
if (hasValue((config as RegExThemeConfig).regex)) {
|
||||||
return new RegExTheme(config as RegExThemeConfig);
|
return new RegExTheme(config as RegExThemeConfig);
|
||||||
} else if (hasValue((config as HandleThemeConfig).handle)) {
|
} else if (hasValue((config as HandleThemeConfig).handle)) {
|
||||||
return new HandleTheme(config as HandleThemeConfig);
|
return new HandleTheme(config as HandleThemeConfig, injector.get(HandleService));
|
||||||
} else if (hasValue((config as UUIDThemeConfig).uuid)) {
|
} else if (hasValue((config as UUIDThemeConfig).uuid)) {
|
||||||
return new UUIDTheme(config as UUIDThemeConfig);
|
return new UUIDTheme(config as UUIDThemeConfig);
|
||||||
} else {
|
} else {
|
||||||
|
@@ -1,155 +0,0 @@
|
|||||||
import { InitService } from '../../app/init.service';
|
|
||||||
import { APP_CONFIG } from 'src/config/app-config.interface';
|
|
||||||
import { inject, TestBed, waitForAsync } from '@angular/core/testing';
|
|
||||||
import { GoogleAnalyticsService } from '../../app/statistics/google-analytics.service';
|
|
||||||
import { MetadataService } from '../../app/core/metadata/metadata.service';
|
|
||||||
import { BreadcrumbsService } from '../../app/breadcrumbs/breadcrumbs.service';
|
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
import { Store, StoreModule } from '@ngrx/store';
|
|
||||||
import { authReducer } from '../../app/core/auth/auth.reducer';
|
|
||||||
import { storeModuleConfig } from '../../app/app.reducer';
|
|
||||||
import { AngularticsProviderMock } from '../../app/shared/mocks/angulartics-provider.service.mock';
|
|
||||||
import { Angulartics2DSpace } from '../../app/statistics/angulartics/dspace-provider';
|
|
||||||
import { AuthService } from '../../app/core/auth/auth.service';
|
|
||||||
import { AuthServiceMock } from '../../app/shared/mocks/auth.service.mock';
|
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
|
||||||
import { RouterMock } from '../../app/shared/mocks/router.mock';
|
|
||||||
import { MockActivatedRoute } from '../../app/shared/mocks/active-router.mock';
|
|
||||||
import { MenuService } from '../../app/shared/menu/menu.service';
|
|
||||||
import { LocaleService } from '../../app/core/locale/locale.service';
|
|
||||||
import { environment } from '../../environments/environment';
|
|
||||||
import { provideMockStore } from '@ngrx/store/testing';
|
|
||||||
import { AppComponent } from '../../app/app.component';
|
|
||||||
import { RouteService } from '../../app/core/services/route.service';
|
|
||||||
import { getMockLocaleService } from '../../app/app.component.spec';
|
|
||||||
import { MenuServiceStub } from '../../app/shared/testing/menu-service.stub';
|
|
||||||
import { CorrelationIdService } from '../../app/correlation-id/correlation-id.service';
|
|
||||||
import { KlaroService } from '../../app/shared/cookies/klaro.service';
|
|
||||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
|
||||||
import { TranslateLoaderMock } from '../../app/shared/mocks/translate-loader.mock';
|
|
||||||
import { getTestScheduler } from 'jasmine-marbles';
|
|
||||||
import { ThemeService } from '../../app/shared/theme-support/theme.service';
|
|
||||||
import { getMockThemeService } from '../../app/shared/mocks/theme-service.mock';
|
|
||||||
import { BrowserInitService } from './browser-init.service';
|
|
||||||
import { TransferState } from '@angular/platform-browser';
|
|
||||||
|
|
||||||
const initialState = {
|
|
||||||
core: {
|
|
||||||
auth: {
|
|
||||||
loading: false,
|
|
||||||
blocking: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('BrowserInitService', () => {
|
|
||||||
describe('browser-specific initialization steps', () => {
|
|
||||||
let correlationIdServiceSpy;
|
|
||||||
let dspaceTransferStateSpy;
|
|
||||||
let transferStateSpy;
|
|
||||||
let metadataServiceSpy;
|
|
||||||
let breadcrumbsServiceSpy;
|
|
||||||
let klaroServiceSpy;
|
|
||||||
let googleAnalyticsSpy;
|
|
||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
|
||||||
correlationIdServiceSpy = jasmine.createSpyObj('correlationIdServiceSpy', [
|
|
||||||
'initCorrelationId',
|
|
||||||
]);
|
|
||||||
dspaceTransferStateSpy = jasmine.createSpyObj('dspaceTransferStateSpy', [
|
|
||||||
'transfer',
|
|
||||||
]);
|
|
||||||
transferStateSpy = jasmine.createSpyObj('dspaceTransferStateSpy', [
|
|
||||||
'get', 'hasKey'
|
|
||||||
]);
|
|
||||||
breadcrumbsServiceSpy = jasmine.createSpyObj('breadcrumbsServiceSpy', [
|
|
||||||
'listenForRouteChanges',
|
|
||||||
]);
|
|
||||||
metadataServiceSpy = jasmine.createSpyObj('metadataService', [
|
|
||||||
'listenForRouteChange',
|
|
||||||
]);
|
|
||||||
klaroServiceSpy = jasmine.createSpyObj('klaroServiceSpy', [
|
|
||||||
'initialize',
|
|
||||||
]);
|
|
||||||
googleAnalyticsSpy = jasmine.createSpyObj('googleAnalyticsService', [
|
|
||||||
'addTrackingIdToPage',
|
|
||||||
]);
|
|
||||||
|
|
||||||
|
|
||||||
TestBed.resetTestingModule();
|
|
||||||
TestBed.configureTestingModule({
|
|
||||||
imports: [
|
|
||||||
CommonModule,
|
|
||||||
StoreModule.forRoot(authReducer, storeModuleConfig),
|
|
||||||
TranslateModule.forRoot({
|
|
||||||
loader: {
|
|
||||||
provide: TranslateLoader,
|
|
||||||
useClass: TranslateLoaderMock
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
providers: [
|
|
||||||
{ provide: InitService, useClass: BrowserInitService },
|
|
||||||
{ provide: CorrelationIdService, useValue: correlationIdServiceSpy },
|
|
||||||
{ provide: APP_CONFIG, useValue: environment },
|
|
||||||
{ provide: LocaleService, useValue: getMockLocaleService() },
|
|
||||||
{ provide: Angulartics2DSpace, useValue: new AngularticsProviderMock() },
|
|
||||||
{ provide: MetadataService, useValue: metadataServiceSpy },
|
|
||||||
{ provide: BreadcrumbsService, useValue: breadcrumbsServiceSpy },
|
|
||||||
{ provide: AuthService, useValue: new AuthServiceMock() },
|
|
||||||
{ provide: Router, useValue: new RouterMock() },
|
|
||||||
{ provide: ActivatedRoute, useValue: new MockActivatedRoute() },
|
|
||||||
{ provide: MenuService, useValue: new MenuServiceStub() },
|
|
||||||
{ provide: KlaroService, useValue: klaroServiceSpy },
|
|
||||||
{ provide: GoogleAnalyticsService, useValue: googleAnalyticsSpy },
|
|
||||||
{ provide: ThemeService, useValue: getMockThemeService() },
|
|
||||||
provideMockStore({ initialState }),
|
|
||||||
AppComponent,
|
|
||||||
RouteService,
|
|
||||||
{ provide: TransferState, useValue: undefined },
|
|
||||||
]
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
describe('initGoogleÀnalytics', () => {
|
|
||||||
it('should call googleAnalyticsService.addTrackingIdToPage()', inject([InitService], (service) => {
|
|
||||||
// @ts-ignore
|
|
||||||
service.initGoogleAnalytics();
|
|
||||||
expect(googleAnalyticsSpy.addTrackingIdToPage).toHaveBeenCalledTimes(1);
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('initKlaro', () => {
|
|
||||||
const BLOCKING = {
|
|
||||||
t: { core: { auth: { blocking: true } } },
|
|
||||||
f: { core: { auth: { blocking: false } } },
|
|
||||||
};
|
|
||||||
|
|
||||||
it('should not initialize Klaro while auth is blocking', () => {
|
|
||||||
getTestScheduler().run(({ cold, flush}) => {
|
|
||||||
TestBed.overrideProvider(Store, { useValue: cold('t--t--t--', BLOCKING) });
|
|
||||||
const service = TestBed.inject(InitService);
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
service.initKlaro();
|
|
||||||
flush();
|
|
||||||
expect(klaroServiceSpy.initialize).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should only initialize Klaro the first time auth is unblocked', () => {
|
|
||||||
getTestScheduler().run(({ cold, flush}) => {
|
|
||||||
TestBed.overrideProvider(Store, { useValue: cold('t--t--f--t--f--', BLOCKING) });
|
|
||||||
const service = TestBed.inject(InitService);
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
service.initKlaro();
|
|
||||||
flush();
|
|
||||||
expect(klaroServiceSpy.initialize).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@@ -6,7 +6,7 @@
|
|||||||
* http://www.dspace.org/license/
|
* http://www.dspace.org/license/
|
||||||
*/
|
*/
|
||||||
import { InitService } from '../../app/init.service';
|
import { InitService } from '../../app/init.service';
|
||||||
import { select, Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { AppState } from '../../app/app.reducer';
|
import { AppState } from '../../app/app.reducer';
|
||||||
import { TransferState } from '@angular/platform-browser';
|
import { TransferState } from '@angular/platform-browser';
|
||||||
import { APP_CONFIG, APP_CONFIG_STATE, AppConfig } from '../../config/app-config.interface';
|
import { APP_CONFIG, APP_CONFIG_STATE, AppConfig } from '../../config/app-config.interface';
|
||||||
@@ -26,9 +26,8 @@ import { AuthService } from '../../app/core/auth/auth.service';
|
|||||||
import { ThemeService } from '../../app/shared/theme-support/theme.service';
|
import { ThemeService } from '../../app/shared/theme-support/theme.service';
|
||||||
import { StoreAction, StoreActionTypes } from '../../app/store.actions';
|
import { StoreAction, StoreActionTypes } from '../../app/store.actions';
|
||||||
import { coreSelector } from '../../app/core/core.selectors';
|
import { coreSelector } from '../../app/core/core.selectors';
|
||||||
import { distinctUntilChanged, filter, find, map, take } from 'rxjs/operators';
|
import { find, map } from 'rxjs/operators';
|
||||||
import { isNotEmpty } from '../../app/shared/empty.util';
|
import { isNotEmpty } from '../../app/shared/empty.util';
|
||||||
import { isAuthenticationBlocking } from '../../app/core/auth/selectors';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs client-side initialization.
|
* Performs client-side initialization.
|
||||||
@@ -90,6 +89,8 @@ export class BrowserInitService extends InitService {
|
|||||||
|
|
||||||
this.initKlaro();
|
this.initKlaro();
|
||||||
|
|
||||||
|
await this.authenticationReady$().toPromise();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -116,16 +117,11 @@ export class BrowserInitService extends InitService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize Klaro
|
* Initialize Klaro (once authentication is resolved)
|
||||||
* @protected
|
* @protected
|
||||||
*/
|
*/
|
||||||
protected initKlaro() {
|
protected initKlaro() {
|
||||||
this.store.pipe(
|
this.authenticationReady$().subscribe(() => {
|
||||||
select(isAuthenticationBlocking),
|
|
||||||
distinctUntilChanged(),
|
|
||||||
filter((isBlocking: boolean) => isBlocking === false),
|
|
||||||
take(1)
|
|
||||||
).subscribe(() => {
|
|
||||||
this.klaroService.initialize();
|
this.klaroService.initialize();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -66,6 +66,8 @@ export class ServerInitService extends InitService {
|
|||||||
this.initRouteListeners();
|
this.initRouteListeners();
|
||||||
this.themeService.listenForThemeChanges(false);
|
this.themeService.listenForThemeChanges(false);
|
||||||
|
|
||||||
|
await this.authenticationReady$().toPromise();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user