Merge branch 'master' into w2p-64574_Item-page-entities

This commit is contained in:
Kristof De Langhe
2019-09-06 11:38:12 +02:00
15 changed files with 142 additions and 24 deletions

View File

@@ -1,3 +1,5 @@
module.exports = {
theme: {
name: 'default',
}
};

View File

@@ -44,6 +44,8 @@ import { ActivatedRoute, Router } from '@angular/router';
import { RouteService } from './shared/services/route.service';
import { MockActivatedRoute } from './shared/mocks/mock-active-router';
import { MockRouter } from './shared/mocks/mock-router';
import { CookieService } from './shared/services/cookie.service';
import { MockCookieService } from './shared/mocks/mock-cookie.service';
let comp: AppComponent;
let fixture: ComponentFixture<AppComponent>;
@@ -78,6 +80,7 @@ describe('App component', () => {
{ provide: MenuService, useValue: menuService },
{ provide: CSSVariableService, useClass: CSSVariableServiceStub },
{ provide: HostWindowService, useValue: new HostWindowServiceStub(800) },
{ provide: CookieService, useValue: new MockCookieService()},
AppComponent,
RouteService
],

View File

@@ -32,6 +32,11 @@ import { 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 { ClientCookieService } from './shared/services/client-cookie.service';
import { isNotEmpty } from './shared/empty.util';
import { CookieService } from './shared/services/cookie.service';
export const LANG_COOKIE = 'language_cookie';
@Component({
selector: 'ds-app',
@@ -61,6 +66,7 @@ export class AppComponent implements OnInit, AfterViewInit {
private cssService: CSSVariableService,
private menuService: MenuService,
private windowService: HostWindowService,
private cookie: CookieService
) {
// Load all the languages that are defined as active from the config file
translate.addLangs(config.languages.filter((LangConfig) => LangConfig.active === true).map((a) => a.code));
@@ -68,12 +74,21 @@ export class AppComponent implements OnInit, AfterViewInit {
// Load the default language from the config file
translate.setDefaultLang(config.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(config.defaultLanguage);
}
}
metadata.listenForRouteChange();

View File

@@ -39,6 +39,7 @@ import { ExpandableAdminSidebarSectionComponent } from './+admin/admin-sidebar/e
import { NavbarModule } from './navbar/navbar.module';
import { JournalEntitiesModule } from './entity-groups/journal-entities/journal-entities.module';
import { ResearchEntitiesModule } from './entity-groups/research-entities/research-entities.module';
import { ClientCookieService } from './shared/services/client-cookie.service';
export function getConfig() {
return ENV_CONFIG;
@@ -97,7 +98,8 @@ const PROVIDERS = [
{
provide: RouterStateSerializer,
useClass: DSpaceRouterStateSerializer
}
},
ClientCookieService
];
const DECLARATIONS = [

View File

@@ -3,11 +3,14 @@ import { Item } from '../../../../core/shared/item.model';
import { of as observableOf } from 'rxjs/internal/observable/of';
import { getEntityGridElementTestComponent } from '../../../../shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec';
import { JournalIssueGridElementComponent } from './journal-issue-grid-element.component';
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils';
import { PaginatedList } from '../../../../core/data/paginated-list';
import { PageInfo } from '../../../../core/shared/page-info.model';
const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithMetadata.hitHighlights = {};
mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
metadata: {
'dc.title': [
{
@@ -33,7 +36,7 @@ mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithoutMetadata.hitHighlights = {};
mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
metadata: {
'dc.title': [
{

View File

@@ -3,11 +3,14 @@ import { Item } from '../../../../core/shared/item.model';
import { of as observableOf } from 'rxjs/internal/observable/of';
import { getEntityGridElementTestComponent } from '../../../../shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec';
import { JournalVolumeGridElementComponent } from './journal-volume-grid-element.component';
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils';
import { PaginatedList } from '../../../../core/data/paginated-list';
import { PageInfo } from '../../../../core/shared/page-info.model';
const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithMetadata.hitHighlights = {};
mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
metadata: {
'dc.title': [
{
@@ -33,7 +36,7 @@ mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithoutMetadata.hitHighlights = {};
mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
metadata: {
'dc.title': [
{

View File

@@ -3,11 +3,14 @@ import { Item } from '../../../../core/shared/item.model';
import { of as observableOf } from 'rxjs/internal/observable/of';
import { getEntityGridElementTestComponent } from '../../../../shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec';
import { JournalGridElementComponent } from './journal-grid-element.component';
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils';
import { PaginatedList } from '../../../../core/data/paginated-list';
import { PageInfo } from '../../../../core/shared/page-info.model';
const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithMetadata.hitHighlights = {};
mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
metadata: {
'dc.title': [
{
@@ -39,7 +42,7 @@ mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithoutMetadata.hitHighlights = {};
mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
metadata: {
'dc.title': [
{

View File

@@ -3,11 +3,14 @@ import { Item } from '../../../../core/shared/item.model';
import { of as observableOf } from 'rxjs/internal/observable/of';
import { getEntityGridElementTestComponent } from '../../../../shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec';
import { OrgunitGridElementComponent } from './orgunit-grid-element.component';
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils';
import { PaginatedList } from '../../../../core/data/paginated-list';
import { PageInfo } from '../../../../core/shared/page-info.model';
const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithMetadata.hitHighlights = {};
mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
metadata: {
'dc.title': [
{
@@ -39,7 +42,7 @@ mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithoutMetadata.hitHighlights = {};
mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
metadata: {
'dc.title': [
{

View File

@@ -3,11 +3,14 @@ import { Item } from '../../../../core/shared/item.model';
import { of as observableOf } from 'rxjs/internal/observable/of';
import { getEntityGridElementTestComponent } from '../../../../shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec';
import { PersonGridElementComponent } from './person-grid-element.component';
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils';
import { PaginatedList } from '../../../../core/data/paginated-list';
import { PageInfo } from '../../../../core/shared/page-info.model';
const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithMetadata.hitHighlights = {};
mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
metadata: {
'dc.title': [
{
@@ -33,7 +36,7 @@ mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithoutMetadata.hitHighlights = {};
mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
metadata: {
'dc.title': [
{

View File

@@ -3,11 +3,14 @@ import { Item } from '../../../../core/shared/item.model';
import { of as observableOf } from 'rxjs/internal/observable/of';
import { getEntityGridElementTestComponent } from '../../../../shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec';
import { ProjectGridElementComponent } from './project-grid-element.component';
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils';
import { PaginatedList } from '../../../../core/data/paginated-list';
import { PageInfo } from '../../../../core/shared/page-info.model';
const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithMetadata.hitHighlights = {};
mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
metadata: {
'dc.title': [
{
@@ -27,7 +30,7 @@ mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithoutMetadata.hitHighlights = {};
mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
metadata: {
'dc.title': [
{

View File

@@ -4,7 +4,7 @@
</a>
<ul ngbDropdownMenu class="dropdown-menu" aria-labelledby="dropdownLang">
<li class="dropdown-item" #langSelect *ngFor="let lang of translate.getLangs()"
(click)="translate.use(lang)"
(click)="useLang(lang)"
[class.active]="lang === translate.currentLang">
{{ langLabel(lang) }}
</li>

View File

@@ -6,6 +6,9 @@ import {HttpClientTestingModule, HttpTestingController} from '@angular/common/ht
import { GLOBAL_CONFIG } from '../../../config';
import {LangConfig} from '../../../config/lang-config.interface';
import {Observable, of} from 'rxjs';
import { By } from '@angular/platform-browser';
import { CookieService } from '../services/cookie.service';
import { MockCookieService } from '../mocks/mock-cookie.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.
@@ -28,8 +31,14 @@ class CustomLoader implements TranslateLoader {
/* tslint:enable:quotemark */
/* tslint:enable:object-literal-key-quotes */
let cookie: CookieService;
describe('LangSwitchComponent', () => {
beforeEach(() => {
cookie = Object.assign(new MockCookieService());
});
describe('with English and Deutsch activated, English as default', () => {
let component: LangSwitchComponent;
let fixture: ComponentFixture<LangSwitchComponent>;
@@ -61,7 +70,11 @@ describe('LangSwitchComponent', () => {
)],
declarations: [LangSwitchComponent],
schemas: [NO_ERRORS_SCHEMA],
providers: [TranslateService, {provide: GLOBAL_CONFIG, useValue: mockConfig}]
providers: [
TranslateService,
{ provide: GLOBAL_CONFIG, useValue: mockConfig },
{ provide: CookieService, useValue: cookie }
]
}).compileComponents()
.then(() => {
translate = TestBed.get(TranslateService);
@@ -73,6 +86,7 @@ describe('LangSwitchComponent', () => {
component = fixture.componentInstance;
de = fixture.debugElement;
langSwitchElement = de.nativeElement;
fixture.detectChanges();
});
}));
@@ -93,6 +107,24 @@ describe('LangSwitchComponent', () => {
it('should define the main A HREF in the UI', (() => {
expect(langSwitchElement.querySelector('a')).toBeDefined();
}));
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 set the client\'s language cookie', () => {
expect(cookie.set).toHaveBeenCalled();
});
});
});
describe('with English as the only active and also default language', () => {
@@ -127,7 +159,11 @@ describe('LangSwitchComponent', () => {
)],
declarations: [LangSwitchComponent],
schemas: [NO_ERRORS_SCHEMA],
providers: [TranslateService, {provide: GLOBAL_CONFIG, useValue: mockConfig}]
providers: [
TranslateService,
{ provide: GLOBAL_CONFIG, useValue: mockConfig },
{ provide: CookieService, useValue: cookie }
]
}).compileComponents();
translate = TestBed.get(TranslateService);
translate.addLangs(mockConfig.languages.filter((MyLangConfig) => MyLangConfig.active === true).map((a) => a.code));

View File

@@ -2,6 +2,9 @@ import {Component, Inject, OnInit} from '@angular/core';
import { GLOBAL_CONFIG, GlobalConfig } from '../../../config';
import {TranslateService} from '@ngx-translate/core';
import {LangConfig} from '../../../config/lang-config.interface';
import { ClientCookieService } from '../services/client-cookie.service';
import { LANG_COOKIE } from '../../app.component';
import { CookieService } from '../services/cookie.service';
@Component({
selector: 'ds-lang-switch',
@@ -23,7 +26,8 @@ export class LangSwitchComponent implements OnInit {
constructor(
@Inject(GLOBAL_CONFIG) public config: GlobalConfig,
public translate: TranslateService
public translate: TranslateService,
public cookie: CookieService
) {
}
@@ -46,4 +50,13 @@ export class LangSwitchComponent implements OnInit {
return this.activeLangs.find((MyLangConfig) => MyLangConfig.code === langcode).label;
}
/**
* Switch to a language and store it in a cookie
* @param lang The language to switch to
*/
useLang(lang: string) {
this.translate.use(lang);
this.cookie.set(LANG_COOKIE, lang);
}
}

View File

@@ -0,0 +1,26 @@
/**
* Mock for [[CookieService]]
*/
export class MockCookieService {
cookies: Map<string, string>;
constructor(cookies: Map<string, string> = new Map()) {
this.cookies = cookies;
}
set(name, value) {
this.cookies.set(name, value);
}
get(name) {
return this.cookies.get(name);
}
remove() {
return jasmine.createSpy('remove');
}
getAll() {
return jasmine.createSpy('getAll');
}
}

View File

@@ -9,11 +9,14 @@ import { of as observableOf } from 'rxjs/internal/observable/of';
import { ItemSearchResult } from '../../../../object-collection/shared/item-search-result.model';
import { Item } from '../../../../../core/shared/item.model';
import { ITEM } from '../../../../items/switcher/item-type-switcher.component';
import { createSuccessfulRemoteDataObject$ } from '../../../../testing/utils';
import { PaginatedList } from '../../../../../core/data/paginated-list';
import { PageInfo } from '../../../../../core/shared/page-info.model';
const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithMetadata.hitHighlights = {};
mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
metadata: {
'dc.title': [
{
@@ -45,7 +48,7 @@ mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithoutMetadata.hitHighlights = {};
mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
metadata: {
'dc.title': [
{