Merge branch 'master' into w2p-71809_Add-server-side-X-Forwarded-For-interceptor

Conflicts:
	src/modules/app/server-app.module.ts
This commit is contained in:
Kristof De Langhe
2020-07-14 13:35:11 +02:00
16 changed files with 593 additions and 85 deletions

View File

@@ -1,35 +1,22 @@
import {
async,
ComponentFixture,
inject,
TestBed
} from '@angular/core/testing';
import {
CUSTOM_ELEMENTS_SCHEMA,
DebugElement
} from '@angular/core';
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
import { CUSTOM_ELEMENTS_SCHEMA, DebugElement } from '@angular/core';
import { CommonModule } from '@angular/common';
import { By } from '@angular/platform-browser';
import { ActivatedRoute, Router } from '@angular/router';
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { Store, StoreModule } from '@ngrx/store';
import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
// Load the implementations that should be tested
import { AppComponent } from './app.component';
import { HostWindowState } from './shared/search/host-window.reducer';
import { HostWindowResizeAction } from './shared/host-window.actions';
import { MetadataService } from './core/metadata/metadata.service';
import { NativeWindowRef, NativeWindowService } from './core/services/window.service';
import { TranslateLoaderMock } from './shared/mocks/translate-loader.mock';
import { MetadataServiceMock } from './shared/mocks/metadata-service.mock';
import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
import { AngularticsMock } from './shared/mocks/angulartics.service.mock';
import { AuthServiceMock } from './shared/mocks/auth.service.mock';
import { AuthService } from './core/auth/auth.service';
@@ -39,14 +26,12 @@ import { CSSVariableServiceStub } from './shared/testing/css-variable-service.st
import { MenuServiceStub } from './shared/testing/menu-service.stub';
import { HostWindowService } from './shared/host-window.service';
import { HostWindowServiceStub } from './shared/testing/host-window-service.stub';
import { ActivatedRoute, Router } from '@angular/router';
import { RouteService } from './core/services/route.service';
import { MockActivatedRoute } from './shared/mocks/active-router.mock';
import { RouterMock } from './shared/mocks/router.mock';
import { CookieServiceMock } from './shared/mocks/cookie.service.mock';
import { CookieService } from './core/services/cookie.service';
import { Angulartics2DSpace } from './statistics/angulartics/dspace-provider';
import { storeModuleConfig } from './app.reducer';
import { LocaleService } from './core/locale/locale.service';
let comp: AppComponent;
let fixture: ComponentFixture<AppComponent>;
@@ -56,6 +41,12 @@ const menuService = new MenuServiceStub();
describe('App component', () => {
function getMockLocaleService(): LocaleService {
return jasmine.createSpyObj('LocaleService', {
setCurrentLanguageCode: jasmine.createSpy('setCurrentLanguageCode')
})
}
// async beforeEach
beforeEach(async(() => {
return TestBed.configureTestingModule({
@@ -81,7 +72,7 @@ describe('App component', () => {
{ provide: MenuService, useValue: menuService },
{ provide: CSSVariableService, useClass: CSSVariableServiceStub },
{ provide: HostWindowService, useValue: new HostWindowServiceStub(800) },
{ provide: CookieService, useValue: new CookieServiceMock()},
{ provide: LocaleService, useValue: getMockLocaleService() },
AppComponent,
RouteService
],

View File

@@ -1,10 +1,19 @@
import { delay, filter, map, take } from 'rxjs/operators';
import { AfterViewInit, ChangeDetectionStrategy, Component, HostListener, Inject, OnInit, ViewEncapsulation } from '@angular/core';
import {
AfterViewInit,
ChangeDetectionStrategy,
Component,
HostListener,
Inject,
OnInit,
ViewEncapsulation
} from '@angular/core';
import { NavigationCancel, NavigationEnd, NavigationStart, Router } from '@angular/router';
import { BehaviorSubject, combineLatest as combineLatestObservable, Observable, of } from 'rxjs';
import { select, Store } from '@ngrx/store';
import { TranslateService } from '@ngx-translate/core';
import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
import { MetadataService } from './core/metadata/metadata.service';
import { HostWindowResizeAction } from './shared/host-window.actions';
@@ -12,20 +21,17 @@ import { HostWindowState } from './shared/search/host-window.reducer';
import { NativeWindowRef, NativeWindowService } from './core/services/window.service';
import { isAuthenticated } from './core/auth/selectors';
import { AuthService } from './core/auth/auth.service';
import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
import variables from '../styles/_exposed_variables.scss';
import { CSSVariableService } from './shared/sass-helper/sass-helper.service';
import { MenuService } from './shared/menu/menu.service';
import { MenuID } from './shared/menu/initial-menus-state';
import { BehaviorSubject, combineLatest as combineLatestObservable, Observable, of } from 'rxjs';
import { slideSidebarPadding } from './shared/animations/slide';
import { HostWindowService } from './shared/host-window.service';
import { Theme } from '../config/theme.inferface';
import { isNotEmpty } from './shared/empty.util';
import { CookieService } from './core/services/cookie.service';
import { Angulartics2DSpace } from './statistics/angulartics/dspace-provider';
import { environment } from '../environments/environment';
import { models } from './core/core.module';
import { LocaleService } from './core/locale/locale.service';
export const LANG_COOKIE = 'language_cookie';
@@ -59,7 +65,7 @@ export class AppComponent implements OnInit, AfterViewInit {
private cssService: CSSVariableService,
private menuService: MenuService,
private windowService: HostWindowService,
private cookie: CookieService
private localeService: LocaleService
) {
/* Use models object so all decorators are actually called */
this.models = models;
@@ -67,23 +73,10 @@ export class AppComponent implements OnInit, AfterViewInit {
translate.addLangs(environment.languages.filter((LangConfig) => LangConfig.active === true).map((a) => a.code));
// Load the default language from the config file
translate.setDefaultLang(environment.defaultLanguage);
// translate.setDefaultLang(environment.defaultLanguage);
// Attempt to get the language from a cookie
const lang = cookie.get(LANG_COOKIE);
if (isNotEmpty(lang)) {
// Cookie found
// Use the language from the cookie
translate.use(lang);
} else {
// Cookie not found
// Attempt to get the browser language from the user
if (translate.getLangs().includes(translate.getBrowserLang())) {
translate.use(translate.getBrowserLang());
} else {
translate.use(environment.defaultLanguage);
}
}
// set the current language code
this.localeService.setCurrentLanguageCode();
angulartics2GoogleAnalytics.startTracking();
angulartics2DSpace.startTracking();
@@ -140,15 +133,15 @@ export class AppComponent implements OnInit, AfterViewInit {
// More information on this bug-fix: https://blog.angular-university.io/angular-debugging/
delay(0)
).subscribe((event) => {
if (event instanceof NavigationStart) {
this.isLoading$.next(true);
} else if (
event instanceof NavigationEnd ||
event instanceof NavigationCancel
) {
this.isLoading$.next(false);
}
});
if (event instanceof NavigationStart) {
this.isLoading$.next(true);
} else if (
event instanceof NavigationEnd ||
event instanceof NavigationCancel
) {
this.isLoading$.next(false);
}
});
}
@HostListener('window:resize', ['$event'])

View File

@@ -241,6 +241,12 @@ describe('AuthService test', () => {
expect(result).toBe(false);
});
it('should return true when authentication is loaded', () => {
authService.isAuthenticationLoaded().subscribe((status: boolean) => {
expect(status).toBe(true);
});
});
});
describe('', () => {

View File

@@ -21,7 +21,8 @@ import {
getAuthenticationToken,
getRedirectUrl,
isAuthenticated,
isTokenRefreshing
isTokenRefreshing,
isAuthenticatedLoaded
} from './selectors';
import { AppState, routerStateSelector } from '../../app.reducer';
import {
@@ -148,6 +149,14 @@ export class AuthService {
return this.store.pipe(select(isAuthenticated));
}
/**
* Determines if authentication is loaded
* @returns {Observable<boolean>}
*/
public isAuthenticationLoaded(): Observable<boolean> {
return this.store.pipe(select(isAuthenticatedLoaded));
}
/**
* Returns the href link to authenticated user
* @returns {string}
@@ -197,7 +206,7 @@ export class AuthService {
return this.store.pipe(
select(getAuthenticatedUserId),
hasValueOperator(),
switchMap((id: string) => this.epersonService.findById(id)),
switchMap((id: string) => { console.log('ID: ', id); return this.epersonService.findById(id) }),
getAllSucceededRemoteDataPayload()
)
}

View File

@@ -144,6 +144,7 @@ import { ScriptDataService } from './data/processes/script-data.service';
import { ProcessFilesResponseParsingService } from './data/process-files-response-parsing.service';
import { WorkflowActionDataService } from './data/workflow-action-data.service';
import { WorkflowAction } from './tasks/models/workflow-action-object.model';
import { LocaleInterceptor } from './locale/locale.interceptor';
import { ItemTemplateDataService } from './data/item-template-data.service';
import { TemplateItem } from './shared/template-item.model';
import { Registration } from './shared/registration.model';
@@ -283,6 +284,12 @@ const PROVIDERS = [
useClass: AuthInterceptor,
multi: true
},
// register LocaleInterceptor as HttpInterceptor
{
provide: HTTP_INTERCEPTORS,
useClass: LocaleInterceptor,
multi: true
},
NotificationsService,
FilteredDiscoveryPageResponseParsingService,
{ provide: NativeWindowService, useFactory: NativeWindowFactory }

View File

@@ -0,0 +1,74 @@
import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController, } from '@angular/common/http/testing';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { DSpaceRESTv2Service } from '../dspace-rest-v2/dspace-rest-v2.service';
import { RestRequestMethod } from '../data/rest-request-method';
import { LocaleService } from './locale.service';
import { LocaleInterceptor } from './locale.interceptor';
import { of } from 'rxjs';
describe(`LocaleInterceptor`, () => {
let service: DSpaceRESTv2Service;
let httpMock: HttpTestingController;
let localeService: any;
const languageList = ['en;q=1', 'it;q=0.9', 'de;q=0.8', 'fr;q=0.7'];
const mockLocaleService = jasmine.createSpyObj('LocaleService', {
getCurrentLanguageCode: jasmine.createSpy('getCurrentLanguageCode'),
getLanguageCodeList: of(languageList)
})
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [
DSpaceRESTv2Service,
{
provide: HTTP_INTERCEPTORS,
useClass: LocaleInterceptor,
multi: true,
},
{provide: LocaleService, useValue: mockLocaleService},
],
});
service = TestBed.get(DSpaceRESTv2Service);
httpMock = TestBed.get(HttpTestingController);
localeService = TestBed.get(LocaleService);
localeService.getCurrentLanguageCode.and.returnValue('en')
});
describe('', () => {
it('should add an Accept-Language header when were sending an HTTP POST request', () => {
service.request(RestRequestMethod.POST, 'server/api/submission/workspaceitems', 'test').subscribe((response) => {
expect(response).toBeTruthy();
});
const httpRequest = httpMock.expectOne(`server/api/submission/workspaceitems`);
expect(httpRequest.request.headers.has('Accept-Language'));
const lang = httpRequest.request.headers.get('Accept-Language');
expect(lang).toBeDefined();
expect(lang).toBe(languageList.toString());
});
it('should add an Accept-Language header when were sending an HTTP GET request', () => {
service.request(RestRequestMethod.GET, 'server/api/submission/workspaceitems/123').subscribe((response) => {
expect(response).toBeTruthy();
});
const httpRequest = httpMock.expectOne(`server/api/submission/workspaceitems/123`);
expect(httpRequest.request.headers.has('Accept-Language'));
const lang = httpRequest.request.headers.get('Accept-Language');
expect(lang).toBeDefined();
expect(lang).toBe(languageList.toString());
});
});
});

View File

@@ -0,0 +1,35 @@
import { Injectable } from '@angular/core';
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Observable } from 'rxjs';
import { LocaleService } from './locale.service';
import { mergeMap, scan } from 'rxjs/operators';
@Injectable()
export class LocaleInterceptor implements HttpInterceptor {
constructor(private localeService: LocaleService) {
}
/**
* Intercept method
* @param req
* @param next
*/
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
let newReq: HttpRequest<any>;
return this.localeService.getLanguageCodeList()
.pipe(
scan((acc: any, value: any) => [...acc, ...value], []),
mergeMap((languages) => {
// Clone the request to add the new header.
newReq = req.clone({
headers: req.headers
.set('Accept-Language', languages.toString())
});
// Pass on the new request instead of the original request.
return next.handle(newReq);
}))
}
}

View File

@@ -0,0 +1,125 @@
import { async, TestBed } from '@angular/core/testing';
import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core';
import { CookieService } from '../services/cookie.service';
import { CookieServiceMock } from '../../shared/mocks/cookie.service.mock';
import { TranslateLoaderMock } from '../../shared/mocks/translate-loader.mock';
import { LANG_COOKIE, LocaleService, LANG_ORIGIN } from './locale.service';
import { AuthService } from '../auth/auth.service';
import { AuthServiceMock } from 'src/app/shared/mocks/auth.service.mock';
import { NativeWindowRef } from '../services/window.service';
describe('LocaleService test suite', () => {
let service: LocaleService;
let serviceAsAny: any;
let cookieService: CookieService;
let translateService: TranslateService;
let authService: AuthService;
let window;
let spyOnGet;
let spyOnSet;
const langList = ['en', 'it', 'de'];
beforeEach(async(() => {
return TestBed.configureTestingModule({
imports: [
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useClass: TranslateLoaderMock
}
}),
],
providers: [
{ provide: CookieService, useValue: new CookieServiceMock() },
{ provide: AuthService, userValue: AuthServiceMock }
]
});
}));
beforeEach(() => {
cookieService = TestBed.get(CookieService);
translateService = TestBed.get(TranslateService);
authService = TestBed.get(TranslateService);
window = new NativeWindowRef();
service = new LocaleService(window, cookieService, translateService, authService);
serviceAsAny = service;
spyOnGet = spyOn(cookieService, 'get');
spyOnSet = spyOn(cookieService, 'set');
});
describe('getCurrentLanguageCode', () => {
it('should return language saved on cookie', () => {
spyOnGet.and.returnValue('de');
expect(service.getCurrentLanguageCode()).toBe('de');
});
describe('', () => {
beforeEach(() => {
spyOn(translateService, 'getLangs').and.returnValue(langList);
});
it('should return language from browser setting', () => {
spyOn(translateService, 'getBrowserLang').and.returnValue('it');
expect(service.getCurrentLanguageCode()).toBe('it');
});
it('should return default language from config', () => {
spyOn(translateService, 'getBrowserLang').and.returnValue('fr');
expect(service.getCurrentLanguageCode()).toBe('en');
});
});
});
describe('getLanguageCodeFromCookie', () => {
it('should return language from cookie', () => {
spyOnGet.and.returnValue('de');
expect(service.getLanguageCodeFromCookie()).toBe('de');
});
});
describe('saveLanguageCodeToCookie', () => {
it('should save language to cookie', () => {
service.saveLanguageCodeToCookie('en');
expect(spyOnSet).toHaveBeenCalledWith(LANG_COOKIE, 'en');
});
});
describe('setCurrentLanguageCode', () => {
beforeEach(() => {
spyOn(service, 'saveLanguageCodeToCookie');
spyOn(translateService, 'use');
});
it('should set the given language', () => {
service.setCurrentLanguageCode('it');
expect(translateService.use).toHaveBeenCalledWith( 'it');
expect(service.saveLanguageCodeToCookie).toHaveBeenCalledWith('it');
});
it('should set the current language', () => {
spyOn(service, 'getCurrentLanguageCode').and.returnValue('es');
service.setCurrentLanguageCode();
expect(translateService.use).toHaveBeenCalledWith( 'es');
expect(service.saveLanguageCodeToCookie).toHaveBeenCalledWith('es');
});
});
describe('', () => {
it('should set quality to current language list', () => {
const langListWithQuality = ['en;q=1', 'it;q=0.9', 'de;q=0.8'];
spyOn(service, 'setQuality').and.returnValue(langListWithQuality);
service.setQuality(langList, LANG_ORIGIN.BROWSER, false);
expect(service.setQuality).toHaveBeenCalledWith(langList, LANG_ORIGIN.BROWSER, false);
});
it('should return the list of language with quality factor', () => {
spyOn(service, 'getLanguageCodeList');
service.getLanguageCodeList();
expect(service.getLanguageCodeList).toHaveBeenCalled();
});
});
});

View File

@@ -0,0 +1,192 @@
import { Injectable, Inject } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { isEmpty, isNotEmpty } from '../../shared/empty.util';
import { CookieService } from '../services/cookie.service';
import { environment } from '../../../environments/environment';
import { AuthService } from '../auth/auth.service';
import { Observable, of as observableOf, combineLatest } from 'rxjs';
import { map, take, flatMap } from 'rxjs/operators';
import { NativeWindowService, NativeWindowRef } from '../services/window.service';
export const LANG_COOKIE = 'language_cookie';
/**
* This enum defines the possible origin of the languages
*/
export enum LANG_ORIGIN {
UI,
EPERSON,
BROWSER
};
/**
* Service to provide localization handler
*/
@Injectable({
providedIn: 'root'
})
export class LocaleService {
/**
* Eperson language metadata
*/
EPERSON_LANG_METADATA = 'eperson.language';
constructor(
@Inject(NativeWindowService) protected _window: NativeWindowRef,
protected cookie: CookieService,
protected translate: TranslateService,
protected authService: AuthService) {
}
/**
* Get the language currently used
*
* @returns {string} The language code
*/
getCurrentLanguageCode(): string {
// Attempt to get the language from a cookie
let lang = this.getLanguageCodeFromCookie();
if (isEmpty(lang)) {
// Cookie not found
// Attempt to get the browser language from the user
if (this.translate.getLangs().includes(this.translate.getBrowserLang())) {
lang = this.translate.getBrowserLang();
} else {
lang = environment.defaultLanguage;
}
}
return lang;
}
/**
* Get the languages list of the user in Accept-Language format
*
* @returns {Observable<string[]>}
*/
getLanguageCodeList(): Observable<string[]> {
const obs$ = combineLatest([
this.authService.isAuthenticated(),
this.authService.isAuthenticationLoaded()
]);
return obs$.pipe(
take(1),
flatMap(([isAuthenticated, isLoaded]) => {
let epersonLang$: Observable<string[]> = observableOf([]);
if (isAuthenticated && isLoaded) {
epersonLang$ = this.authService.getAuthenticatedUserFromStore().pipe(
take(1),
map((eperson) => {
const languages: string[] = [];
const ePersonLang = eperson.firstMetadataValue(this.EPERSON_LANG_METADATA);
if (ePersonLang) {
languages.push(...this.setQuality(
[ePersonLang],
LANG_ORIGIN.EPERSON,
!isEmpty(this.translate.currentLang)));
}
return languages;
})
);
}
return epersonLang$.pipe(
map((epersonLang: string[]) => {
const languages: string[] = [];
if (this.translate.currentLang) {
languages.push(...this.setQuality(
[this.translate.currentLang],
LANG_ORIGIN.UI,
false));
}
if (isNotEmpty(epersonLang)) {
languages.push(...epersonLang);
}
if (navigator.languages) {
languages.push(...this.setQuality(
Object.assign([], navigator.languages),
LANG_ORIGIN.BROWSER,
!isEmpty(this.translate.currentLang))
);
}
return languages;
})
)
})
);
}
/**
* Retrieve the language from a cookie
*/
getLanguageCodeFromCookie(): string {
return this.cookie.get(LANG_COOKIE);
}
/**
* Set the language currently used
*
* @param lang
* The language to save
*/
saveLanguageCodeToCookie(lang: string): void {
this.cookie.set(LANG_COOKIE, lang);
}
/**
* Set the language currently used
*
* @param lang
* The language to set, if it's not provided retrieve default one
*/
setCurrentLanguageCode(lang?: string): void {
if (isEmpty(lang)) {
lang = this.getCurrentLanguageCode()
}
this.translate.use(lang);
this.saveLanguageCodeToCookie(lang);
}
/**
* Set the quality factor for all element of input array.
* Returns a new array that contains the languages list with the quality value.
* The quality factor indicate the relative degree of preference for the language
* @param languages the languages list
* @param origin origin of language list (UI, EPERSON, BROWSER)
* @param hasOther true if contains other language, false otherwise
*/
setQuality(languages: string[], origin: LANG_ORIGIN, hasOther: boolean): string[] {
const langWithPrior = [];
let idx = 0;
const v = languages.length > 10 ? languages.length : 10;
let divisor: number;
switch (origin) {
case LANG_ORIGIN.EPERSON:
divisor = 2; break;
case LANG_ORIGIN.BROWSER:
divisor = (hasOther ? 10 : 1); break;
default:
divisor = 1;
}
languages.forEach( (lang) => {
let value = lang + ';q=';
let quality = (v - idx++) / v;
quality = ((languages.length > 10) ? quality.toFixed(2) : quality) as number;
value += quality / divisor;
langWithPrior.push(value);
});
return langWithPrior;
}
/**
* Refresh route navigated
*/
public refreshAfterChangeLanguage() {
// Hard redirect to the reload page with a unique number behind it
// so that all state is definitely lost
this._window.nativeWindow.location.href = `/reload/${new Date().getTime()}`;
}
}

View File

@@ -0,0 +1,60 @@
import { LocaleService, LANG_ORIGIN } from './locale.service';
import { Injectable } from '@angular/core';
import { Observable, combineLatest, of as observableOf } from 'rxjs';
import { take, flatMap, map } from 'rxjs/operators';
import { isNotEmpty, isEmpty } from 'src/app/shared/empty.util';
@Injectable()
export class ServerLocaleService extends LocaleService {
/**
* Get the languages list of the user in Accept-Language format
*
* @returns {Observable<string[]>}
*/
getLanguageCodeList(): Observable<string[]> {
const obs$ = combineLatest([
this.authService.isAuthenticated(),
this.authService.isAuthenticationLoaded()
]);
return obs$.pipe(
take(1),
flatMap(([isAuthenticated, isLoaded]) => {
let epersonLang$: Observable<string[]> = observableOf([]);
if (isAuthenticated && isLoaded) {
epersonLang$ = this.authService.getAuthenticatedUserFromStore().pipe(
take(1),
map((eperson) => {
const languages: string[] = [];
const ePersonLang = eperson.firstMetadataValue(this.EPERSON_LANG_METADATA);
if (ePersonLang) {
languages.push(...this.setQuality(
[ePersonLang],
LANG_ORIGIN.EPERSON,
!isEmpty(this.translate.currentLang)));
}
return languages;
})
);
}
return epersonLang$.pipe(
map((epersonLang: string[]) => {
const languages: string[] = [];
if (this.translate.currentLang) {
languages.push(...this.setQuality(
[this.translate.currentLang],
LANG_ORIGIN.UI,
false));
}
if (isNotEmpty(epersonLang)) {
languages.push(...epersonLang);
}
return languages;
})
)
})
);
}
}

View File

@@ -1,13 +1,14 @@
import { LangSwitchComponent } from './lang-switch.component';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
import { By } from '@angular/platform-browser';
import { Observable, of } from 'rxjs';
import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { LangSwitchComponent } from './lang-switch.component';
import { LangConfig } from '../../../config/lang-config.interface';
import { Observable, of } from 'rxjs';
import { By } from '@angular/platform-browser';
import { CookieServiceMock } from '../mocks/cookie.service.mock';
import { CookieService } from '../../core/services/cookie.service';
import { LocaleService } from '../../core/locale/locale.service';
// This test is completely independent from any message catalogs or keys in the codebase
// The translation module is instantiated with these bogus messages that we aren't using anyway.
@@ -31,13 +32,16 @@ class CustomLoader implements TranslateLoader {
/* tslint:enable:quotemark */
/* tslint:enable:object-literal-key-quotes */
let cookie: CookieService;
let localService: any;
describe('LangSwitchComponent', () => {
beforeEach(() => {
cookie = Object.assign(new CookieServiceMock());
});
function getMockLocaleService(): LocaleService {
return jasmine.createSpyObj('LocaleService', {
setCurrentLanguageCode: jasmine.createSpy('setCurrentLanguageCode'),
refreshAfterChangeLanguage: jasmine.createSpy('refreshAfterChangeLanguage')
})
}
describe('with English and Deutsch activated, English as default', () => {
let component: LangSwitchComponent;
@@ -72,7 +76,7 @@ describe('LangSwitchComponent', () => {
schemas: [NO_ERRORS_SCHEMA],
providers: [
TranslateService,
{ provide: CookieService, useValue: cookie }
{ provide: LocaleService, useValue: getMockLocaleService() },
]
}).compileComponents()
.then(() => {
@@ -82,6 +86,7 @@ describe('LangSwitchComponent', () => {
translate.use('en');
http = TestBed.get(HttpTestingController);
fixture = TestBed.createComponent(LangSwitchComponent);
localService = TestBed.get(LocaleService);
component = fixture.componentInstance;
de = fixture.debugElement;
langSwitchElement = de.nativeElement;
@@ -110,19 +115,16 @@ describe('LangSwitchComponent', () => {
describe('when selecting a language', () => {
beforeEach(() => {
spyOn(translate, 'use');
spyOn(cookie, 'set');
const langItem = fixture.debugElement.query(By.css('.dropdown-item')).nativeElement;
langItem.click();
fixture.detectChanges();
});
it('should translate the app', () => {
expect(translate.use).toHaveBeenCalled();
it('should translate the app and set the client\'s language cookie', () => {
expect(localService.setCurrentLanguageCode).toHaveBeenCalled();
expect(localService.refreshAfterChangeLanguage).toHaveBeenCalled();
});
it('should set the client\'s language cookie', () => {
expect(cookie.set).toHaveBeenCalled();
});
});
});
@@ -160,7 +162,7 @@ describe('LangSwitchComponent', () => {
schemas: [NO_ERRORS_SCHEMA],
providers: [
TranslateService,
{ provide: CookieService, useValue: cookie }
{ provide: LocaleService, useValue: getMockLocaleService() }
]
}).compileComponents();
translate = TestBed.get(TranslateService);

View File

@@ -1,9 +1,10 @@
import {Component, Inject, OnInit} from '@angular/core';
import {TranslateService} from '@ngx-translate/core';
import {LangConfig} from '../../../config/lang-config.interface';
import { LANG_COOKIE } from '../../app.component';
import { CookieService } from '../../core/services/cookie.service';
import { Component, Inject, OnInit } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { LangConfig } from '../../../config/lang-config.interface';
import { environment } from '../../../environments/environment';
import { LocaleService } from '../../core/locale/locale.service';
@Component({
selector: 'ds-lang-switch',
@@ -25,7 +26,7 @@ export class LangSwitchComponent implements OnInit {
constructor(
public translate: TranslateService,
public cookie: CookieService
private localeService: LocaleService
) {
}
@@ -53,8 +54,8 @@ export class LangSwitchComponent implements OnInit {
* @param lang The language to switch to
*/
useLang(lang: string) {
this.translate.use(lang);
this.cookie.set(LANG_COOKIE, lang);
this.localeService.setCurrentLanguageCode(lang);
this.localeService.refreshAfterChangeLanguage();
}
}

View File

@@ -44,6 +44,7 @@ export function getRequest(transferState: TransferState): any {
RouterModule.forRoot([], {
// enableTracing: true,
useHash: false,
scrollPositionRestoration: 'enabled',
preloadingStrategy:
IdlePreload
}),

View File

@@ -25,6 +25,8 @@ import { ServerSubmissionService } from '../../app/submission/server-submission.
import { Angulartics2DSpace } from '../../app/statistics/angulartics/dspace-provider';
import { Angulartics2RouterlessModule } from 'angulartics2/routerlessmodule';
import { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader';
import { ServerLocaleService } from 'src/app/core/locale/server-locale.service';
import { LocaleService } from 'src/app/core/locale/locale.service';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { ForwardClientIpInterceptor } from '../../app/core/forward-client-ip/forward-client-ip.interceptor';
@@ -76,6 +78,10 @@ export function createTranslateLoader() {
provide: SubmissionService,
useClass: ServerSubmissionService
},
{
provide: LocaleService,
useClass: ServerLocaleService
},
// register ForwardClientIpInterceptor as HttpInterceptor
{
provide: HTTP_INTERCEPTORS,