mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
Merge remote-tracking branch 'remotes/github4science/master' into DS-4515_submit-external-source
This commit is contained in:
22
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
22
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug, needs triage
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is. Include the version(s) of DSpace where you've seen this problem & what *web browser* you were using. Link to examples if they are public.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Do this
|
||||
2. Then this...
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Related work**
|
||||
Link to any related tickets or PRs here.
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest a new feature for this project
|
||||
title: ''
|
||||
labels: new feature, needs triage
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives or workarounds you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
@@ -25,4 +25,4 @@ _This checklist provides a reminder of what we are going to look for when review
|
||||
* Include tests for different user types (if behavior differs), including: (1) Anonymous user, (2) Logged in user (non-admin), and (3) Administrator.
|
||||
* Include tests for error scenarios, e.g. when errors/warnings should appear (or buttons should be disabled).
|
||||
* For bug fixes, include a test that reproduces the bug and proves it is fixed. For clarity, it may be useful to provide the test in a separate commit from the bug fix.
|
||||
- [ ] If my PR includes new, third-party dependencies (in `package.json`), I've made sure their licenses align with the [DSpace BSD License](https://github.com/DSpace/DSpace/blob/master/LICENSE) based on the [Licensing of Contributions](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines#CodeContributionGuidelines-LicensingofContributions) documentation.
|
||||
- [ ] If my PR includes new, third-party dependencies (in `package.json`), I've made sure their licenses align with the [DSpace BSD License](https://github.com/DSpace/DSpace/blob/main/LICENSE) based on the [Licensing of Contributions](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines#CodeContributionGuidelines-LicensingofContributions) documentation.
|
||||
|
29
.github/workflows/issue_opened.yml
vendored
Normal file
29
.github/workflows/issue_opened.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
# This workflow runs whenever a new issue is created
|
||||
name: Issue opened
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
automation:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# Add the new issue to a project board, if it needs triage
|
||||
# See https://github.com/marketplace/actions/create-project-card-action
|
||||
- name: Add issue to project board
|
||||
# Only add to project board if issue is flagged as "needs triage" or has no labels
|
||||
# NOTE: By default we flag new issues as "needs triage" in our issue template
|
||||
if: (contains(github.event.issue.labels.*.name, 'needs triage') || join(github.event.issue.labels.*.name) == '')
|
||||
uses: technote-space/create-project-card-action@v1
|
||||
# Note, the authentication token below is an ORG level Secret.
|
||||
# It must be created/recreated manually via a personal access token with "public_repo" and "admin:org" permissions
|
||||
# See: https://docs.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token#permissions-for-the-github_token
|
||||
# This is necessary because the "DSpace Backlog" project is an org level project (i.e. not repo specific)
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.ORG_PROJECT_TOKEN }}
|
||||
PROJECT: DSpace Backlog
|
||||
COLUMN: Triage
|
||||
CHECK_ORG_PROJECT: true
|
||||
# Ignore errors
|
||||
continue-on-error: true
|
24
.github/workflows/pull_request_opened.yml
vendored
Normal file
24
.github/workflows/pull_request_opened.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# This workflow runs whenever a new pull request is created
|
||||
name: Pull Request opened
|
||||
|
||||
# Only run for newly opened PRs against the "main" branch
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened]
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
automation:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# Assign the PR to whomever created it. This is useful for visualizing assignments on project boards
|
||||
# See https://github.com/marketplace/actions/pull-request-assigner
|
||||
- name: Assign PR to creator
|
||||
uses: thomaseizinger/assign-pr-creator-action@v1.0.0
|
||||
# Note, this authentication token is created automatically
|
||||
# See: https://docs.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# Ignore errors. It is possible the PR was created by someone who cannot be assigned
|
||||
continue-on-error: true
|
17
.travis.yml
17
.travis.yml
@@ -1,4 +1,4 @@
|
||||
sudo: required
|
||||
os: linux
|
||||
dist: bionic
|
||||
language: node_js
|
||||
|
||||
@@ -35,10 +35,11 @@ before_install:
|
||||
- google-chrome-stable --version
|
||||
|
||||
install:
|
||||
# Start up DSpace 7 using the entities database dump
|
||||
# Start up a test DSpace 7 REST backend using the entities database dump
|
||||
- docker-compose -f ./docker/docker-compose-travis.yml up -d
|
||||
# Use the dspace-cli image to populate the assetstore. Triggers a discovery and oai update
|
||||
- docker-compose -f ./docker/cli.yml -f ./docker/cli.assetstore.yml run --rm dspace-cli
|
||||
# Install all local dependencies (retry if initially fails)
|
||||
- travis_retry yarn install
|
||||
|
||||
before_script:
|
||||
@@ -49,9 +50,17 @@ before_script:
|
||||
#- curl http://localhost:8080/server/
|
||||
|
||||
script:
|
||||
- yarn run ci
|
||||
- cat coverage/dspace-angular-cli/lcov.info | ./node_modules/coveralls/bin/coveralls.js
|
||||
# build app and run all tests
|
||||
- ng lint
|
||||
- travis_wait yarn run build:prod
|
||||
- yarn test:headless
|
||||
- yarn run e2e:ci
|
||||
|
||||
after_script:
|
||||
# Shutdown docker after everything runs
|
||||
- docker-compose -f ./docker/docker-compose-travis.yml down
|
||||
|
||||
# After a successful build and test (see 'script'), send code coverage reports to coveralls.io
|
||||
# These code coverage reports are generated by the coveralls node module in our package.json
|
||||
after_success:
|
||||
- cat coverage/dspace-angular/lcov.info | ./node_modules/coveralls/bin/coveralls.js
|
||||
|
@@ -1,4 +1,4 @@
|
||||
[](https://travis-ci.org/DSpace/dspace-angular) [](https://coveralls.io/github/DSpace/dspace-angular?branch=master) [](https://github.com/angular/universal)
|
||||
[](https://travis-ci.com/DSpace/dspace-angular) [](https://coveralls.io/github/DSpace/dspace-angular?branch=main) [](https://github.com/angular/universal)
|
||||
|
||||
dspace-angular
|
||||
==============
|
||||
|
16
angular.json
16
angular.json
@@ -3,7 +3,7 @@
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"dspace-angular-cli": {
|
||||
"dspace-angular": {
|
||||
"projectType": "application",
|
||||
"schematics": {
|
||||
"@schematics/angular:component": {
|
||||
@@ -21,7 +21,7 @@
|
||||
"path": "./webpack/webpack.common.ts",
|
||||
"mergeStrategies": {
|
||||
"loaders": "prepend"
|
||||
},
|
||||
}
|
||||
},
|
||||
"outputPath": "dist/browser",
|
||||
"index": "src/index.html",
|
||||
@@ -65,19 +65,19 @@
|
||||
"serve": {
|
||||
"builder": "@angular-builders/custom-webpack:dev-server",
|
||||
"options": {
|
||||
"browserTarget": "dspace-angular-cli:build",
|
||||
"browserTarget": "dspace-angular:build",
|
||||
"port": 4000
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "dspace-angular-cli:build:production"
|
||||
"browserTarget": "dspace-angular:build:production"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "dspace-angular-cli:build"
|
||||
"browserTarget": "dspace-angular:build"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
@@ -119,11 +119,11 @@
|
||||
"builder": "@angular-devkit/build-angular:protractor",
|
||||
"options": {
|
||||
"protractorConfig": "e2e/protractor.conf.js",
|
||||
"devServerTarget": "dspace-angular-cli:serve"
|
||||
"devServerTarget": "dspace-angular:serve"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"devServerTarget": "dspace-angular-cli:serve:production"
|
||||
"devServerTarget": "dspace-angular:serve:production"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -153,5 +153,5 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaultProject": "dspace-angular-cli"
|
||||
"defaultProject": "dspace-angular"
|
||||
}
|
||||
|
@@ -11,7 +11,7 @@ version: "3.7"
|
||||
services:
|
||||
dspace-cli:
|
||||
environment:
|
||||
- AIPZIP=https://github.com/DSpace-Labs/AIP-Files/raw/master/dogAndReport.zip
|
||||
- AIPZIP=https://github.com/DSpace-Labs/AIP-Files/raw/main/dogAndReport.zip
|
||||
- ADMIN_EMAIL=test@test.edu
|
||||
- AIPDIR=/tmp/aip-dir
|
||||
entrypoint:
|
||||
|
@@ -17,7 +17,7 @@ module.exports = function (config) {
|
||||
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||
},
|
||||
coverageIstanbulReporter: {
|
||||
dir: require('path').join(__dirname, './coverage/dspace-angular-cli'),
|
||||
dir: require('path').join(__dirname, './coverage/dspace-angular'),
|
||||
reports: ['html', 'lcovonly', 'text-summary'],
|
||||
fixWebpackSourcePaths: true
|
||||
},
|
||||
|
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "dspace-angular-cli",
|
||||
"name": "dspace-angular",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
@@ -23,7 +23,7 @@
|
||||
"build": "ng build",
|
||||
"build:prod": "yarn run build:ssr",
|
||||
"build:ssr": "yarn run build:client-and-server-bundles && yarn run compile:server",
|
||||
"build:client-and-server-bundles": "node --max_old_space_size=8192 node_modules/@angular/cli/bin/ng build --prod && ng run dspace-angular-cli:server:production --bundleDependencies all",
|
||||
"build:client-and-server-bundles": "node --max_old_space_size=8192 node_modules/@angular/cli/bin/ng build --prod && ng run dspace-angular:server:production --bundleDependencies all",
|
||||
"test:watch": "npm-run-all --parallel config:test:watch test",
|
||||
"test": "node --max_old_space_size=8192 node_modules/@angular/cli/bin/ng test --sourceMap=true --watch=true",
|
||||
"test:headless": "node --max_old_space_size=8192 node_modules/@angular/cli/bin/ng test --watch=false --sourceMap=true --browsers=ChromeHeadless --code-coverage",
|
||||
@@ -32,7 +32,6 @@
|
||||
"e2e:ci": "ng e2e --protractor-config=./e2e/protractor-ci.conf.js",
|
||||
"compile:server": "webpack --config webpack.server.config.js --progress --colors",
|
||||
"serve:ssr": "node dist/server",
|
||||
"ci": "ng lint && yarn run build:prod && yarn test:headless && yarn run e2e:ci",
|
||||
"clean:coverage": "rimraf coverage",
|
||||
"clean:dist": "rimraf dist",
|
||||
"clean:doc": "rimraf doc",
|
||||
@@ -140,7 +139,7 @@
|
||||
"codelyzer": "^5.0.0",
|
||||
"compression-webpack-plugin": "^3.0.1",
|
||||
"copy-webpack-plugin": "^5.1.1",
|
||||
"coveralls": "3.0.0",
|
||||
"coveralls": "^3.0.0",
|
||||
"css-loader": "3.4.0",
|
||||
"cssnano": "^4.1.10",
|
||||
"deep-freeze": "0.0.1",
|
||||
|
@@ -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
|
||||
],
|
||||
|
@@ -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'])
|
||||
|
@@ -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('', () => {
|
||||
|
@@ -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}
|
||||
|
@@ -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 }
|
||||
|
74
src/app/core/locale/locale.interceptor.spec.ts
Normal file
74
src/app/core/locale/locale.interceptor.spec.ts
Normal 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 we’re 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 we’re 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());
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
35
src/app/core/locale/locale.interceptor.ts
Normal file
35
src/app/core/locale/locale.interceptor.ts
Normal 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);
|
||||
}))
|
||||
}
|
||||
}
|
125
src/app/core/locale/locale.service.spec.ts
Normal file
125
src/app/core/locale/locale.service.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
});
|
192
src/app/core/locale/locale.service.ts
Normal file
192
src/app/core/locale/locale.service.ts
Normal 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()}`;
|
||||
}
|
||||
|
||||
}
|
60
src/app/core/locale/server-locale.service.ts
Normal file
60
src/app/core/locale/server-locale.service.ts
Normal 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;
|
||||
})
|
||||
)
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@@ -59,8 +59,8 @@ describe('ProcessFormComponent', () => {
|
||||
providers: [
|
||||
{ provide: ScriptDataService, useValue: scriptService },
|
||||
{ provide: NotificationsService, useClass: NotificationsServiceStub },
|
||||
{ provide: RequestService, useValue: jasmine.createSpyObj('requestService', ['removeBySubstring']) },
|
||||
{ provide: Router, useValue: {} },
|
||||
{ provide: RequestService, useValue: jasmine.createSpyObj('requestService', ['removeBySubstring', 'removeByHrefSubstring']) },
|
||||
{ provide: Router, useValue: jasmine.createSpyObj('router', ['navigateByUrl']) },
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
})
|
||||
|
@@ -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);
|
||||
|
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -44,6 +44,7 @@ export function getRequest(transferState: TransferState): any {
|
||||
RouterModule.forRoot([], {
|
||||
// enableTracing: true,
|
||||
useHash: false,
|
||||
scrollPositionRestoration: 'enabled',
|
||||
preloadingStrategy:
|
||||
IdlePreload
|
||||
}),
|
||||
|
@@ -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';
|
||||
|
||||
export function createTranslateLoader() {
|
||||
return new TranslateJson5UniversalLoader('dist/server/assets/i18n/', '.json5');
|
||||
@@ -73,6 +75,10 @@ export function createTranslateLoader() {
|
||||
{
|
||||
provide: SubmissionService,
|
||||
useClass: ServerSubmissionService
|
||||
},
|
||||
{
|
||||
provide: LocaleService,
|
||||
useClass: ServerLocaleService
|
||||
}
|
||||
]
|
||||
})
|
||||
|
90
yarn.lock
90
yarn.lock
@@ -2264,7 +2264,28 @@ cacache@12.0.2:
|
||||
unique-filename "^1.1.1"
|
||||
y18n "^4.0.0"
|
||||
|
||||
cacache@^12.0.0, cacache@^12.0.2, cacache@^12.0.3:
|
||||
cacache@^12.0.0:
|
||||
version "12.0.4"
|
||||
resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.4.tgz#668bcbd105aeb5f1d92fe25570ec9525c8faa40c"
|
||||
integrity sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==
|
||||
dependencies:
|
||||
bluebird "^3.5.5"
|
||||
chownr "^1.1.1"
|
||||
figgy-pudding "^3.5.1"
|
||||
glob "^7.1.4"
|
||||
graceful-fs "^4.1.15"
|
||||
infer-owner "^1.0.3"
|
||||
lru-cache "^5.1.1"
|
||||
mississippi "^3.0.0"
|
||||
mkdirp "^0.5.1"
|
||||
move-concurrently "^1.0.1"
|
||||
promise-inflight "^1.0.1"
|
||||
rimraf "^2.6.3"
|
||||
ssri "^6.0.1"
|
||||
unique-filename "^1.1.1"
|
||||
y18n "^4.0.0"
|
||||
|
||||
cacache@^12.0.2, cacache@^12.0.3:
|
||||
version "12.0.3"
|
||||
resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.3.tgz#be99abba4e1bf5df461cd5a2c1071fc432573390"
|
||||
integrity sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw==
|
||||
@@ -3040,16 +3061,16 @@ coverage-istanbul-loader@2.0.3:
|
||||
merge-source-map "^1.1.0"
|
||||
schema-utils "^2.6.1"
|
||||
|
||||
coveralls@3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/coveralls/-/coveralls-3.0.0.tgz#22ef730330538080d29b8c151dc9146afde88a99"
|
||||
integrity sha512-ZppXR9y5PraUOrf/DzHJY6gzNUhXYE3b9D43xEXs4QYZ7/Oe0Gy0CS+IPKWFfvQFXB3RG9QduaQUFehzSpGAFw==
|
||||
coveralls@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/coveralls/-/coveralls-3.1.0.tgz#13c754d5e7a2dd8b44fe5269e21ca394fb4d615b"
|
||||
integrity sha512-sHxOu2ELzW8/NC1UP5XVLbZDzO4S3VxfFye3XYCznopHy02YjNkHcj5bKaVw2O7hVaBdBjEdQGpie4II1mWhuQ==
|
||||
dependencies:
|
||||
js-yaml "^3.6.1"
|
||||
lcov-parse "^0.0.10"
|
||||
log-driver "^1.2.5"
|
||||
minimist "^1.2.0"
|
||||
request "^2.79.0"
|
||||
js-yaml "^3.13.1"
|
||||
lcov-parse "^1.0.0"
|
||||
log-driver "^1.2.7"
|
||||
minimist "^1.2.5"
|
||||
request "^2.88.2"
|
||||
|
||||
create-ecdh@^4.0.0:
|
||||
version "4.0.3"
|
||||
@@ -4260,9 +4281,9 @@ faye-websocket@~0.11.1:
|
||||
websocket-driver ">=0.5.1"
|
||||
|
||||
figgy-pudding@^3.4.1, figgy-pudding@^3.5.1:
|
||||
version "3.5.1"
|
||||
resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790"
|
||||
integrity sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==
|
||||
version "3.5.2"
|
||||
resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e"
|
||||
integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==
|
||||
|
||||
figures@^2.0.0:
|
||||
version "2.0.0"
|
||||
@@ -4797,12 +4818,12 @@ got@^6.7.1:
|
||||
unzip-response "^2.0.1"
|
||||
url-parse-lax "^1.0.0"
|
||||
|
||||
graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.2:
|
||||
graceful-fs@^4.1.11, graceful-fs@^4.1.6, graceful-fs@^4.2.2:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423"
|
||||
integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==
|
||||
|
||||
graceful-fs@^4.2.0, graceful-fs@^4.2.4:
|
||||
graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.2.0, graceful-fs@^4.2.4:
|
||||
version "4.2.4"
|
||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
|
||||
integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==
|
||||
@@ -4952,11 +4973,16 @@ hoopy@^0.1.4:
|
||||
resolved "https://registry.yarnpkg.com/hoopy/-/hoopy-0.1.4.tgz#609207d661100033a9a9402ad3dea677381c1b1d"
|
||||
integrity sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==
|
||||
|
||||
hosted-git-info@^2.1.4, hosted-git-info@^2.6.0, hosted-git-info@^2.7.1:
|
||||
hosted-git-info@^2.1.4, hosted-git-info@^2.6.0:
|
||||
version "2.8.7"
|
||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.7.tgz#4d2e0d5248e1cfabc984b0f6a6d75fe36e679511"
|
||||
integrity sha512-ChkjQtKJ3GI6SsI4O5jwr8q8EPrWCnxuc4Tbx+vRI5x6mDOpjKKltNo1lRlszw3xwgTOSns1ZRBiMmmwpcvLxg==
|
||||
|
||||
hosted-git-info@^2.7.1:
|
||||
version "2.8.8"
|
||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
|
||||
integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==
|
||||
|
||||
hpack.js@^2.1.6:
|
||||
version "2.1.6"
|
||||
resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2"
|
||||
@@ -5902,7 +5928,7 @@ js-tokens@^3.0.2:
|
||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
|
||||
integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls=
|
||||
|
||||
js-yaml@^3.13.0, js-yaml@^3.13.1, js-yaml@^3.6.1:
|
||||
js-yaml@^3.13.0, js-yaml@^3.13.1:
|
||||
version "3.13.1"
|
||||
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847"
|
||||
integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==
|
||||
@@ -6143,10 +6169,10 @@ lcid@^2.0.0:
|
||||
dependencies:
|
||||
invert-kv "^2.0.0"
|
||||
|
||||
lcov-parse@^0.0.10:
|
||||
version "0.0.10"
|
||||
resolved "https://registry.yarnpkg.com/lcov-parse/-/lcov-parse-0.0.10.tgz#1b0b8ff9ac9c7889250582b70b71315d9da6d9a3"
|
||||
integrity sha1-GwuP+ayceIklBYK3C3ExXZ2m2aM=
|
||||
lcov-parse@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/lcov-parse/-/lcov-parse-1.0.0.tgz#eb0d46b54111ebc561acb4c408ef9363bdc8f7e0"
|
||||
integrity sha1-6w1GtUER68VhrLTECO+TY73I9+A=
|
||||
|
||||
less-loader@5.0.0:
|
||||
version "5.0.0"
|
||||
@@ -6298,7 +6324,7 @@ lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
|
||||
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
|
||||
|
||||
log-driver@^1.2.5:
|
||||
log-driver@^1.2.7:
|
||||
version "1.2.7"
|
||||
resolved "https://registry.yarnpkg.com/log-driver/-/log-driver-1.2.7.tgz#63b95021f0702fedfa2c9bb0a24e7797d71871d8"
|
||||
integrity sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==
|
||||
@@ -6869,9 +6895,9 @@ no-case@^2.2.0:
|
||||
lower-case "^1.1.1"
|
||||
|
||||
node-fetch-npm@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch-npm/-/node-fetch-npm-2.0.2.tgz#7258c9046182dca345b4208eda918daf33697ff7"
|
||||
integrity sha512-nJIxm1QmAj4v3nfCvEeCrYSoVwXyxLnaPBK5W1W5DGEJwjlKuC2VEUycGw5oxk+4zZahRrB84PUJJgEmhFTDFw==
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch-npm/-/node-fetch-npm-2.0.4.tgz#6507d0e17a9ec0be3bec516958a497cec54bf5a4"
|
||||
integrity sha512-iOuIQDWDyjhv9qSDrj9aq/klt6F9z1p2otB3AV7v3zBDcL/x+OfGsvGQZZCcMZbUf4Ujw1xGNQkjvGnVT22cKg==
|
||||
dependencies:
|
||||
encoding "^0.1.11"
|
||||
json-parse-better-errors "^1.0.0"
|
||||
@@ -7056,9 +7082,9 @@ npm-pick-manifest@^2.2.3:
|
||||
semver "^5.4.1"
|
||||
|
||||
npm-registry-fetch@^4.0.0:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-4.0.3.tgz#3c2179e39e04f9348b1c2979545951d36bee8766"
|
||||
integrity sha512-WGvUx0lkKFhu9MbiGFuT9nG2NpfQ+4dCJwRwwtK2HK5izJEvwDxMeUyqbuMS7N/OkpVCqDorV6rO5E4V9F8lJw==
|
||||
version "4.0.5"
|
||||
resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-4.0.5.tgz#cb87cf7f25bfb048d6c3ee19d115bebf93ea5bfa"
|
||||
integrity sha512-yQ0/U4fYpCCqmueB2g8sc+89ckQ3eXpmU4+Yi2j5o/r0WkKvE2+Y0tK3DEILAtn2UaQTkjTHxIXe2/CSdit+/Q==
|
||||
dependencies:
|
||||
JSONStream "^1.3.4"
|
||||
bluebird "^3.5.1"
|
||||
@@ -8897,7 +8923,7 @@ repeat-string@^1.6.1:
|
||||
resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
|
||||
integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc=
|
||||
|
||||
request@^2.79.0, request@^2.83.0, request@^2.87.0, request@^2.88.0:
|
||||
request@^2.83.0, request@^2.87.0, request@^2.88.0, request@^2.88.2:
|
||||
version "2.88.2"
|
||||
resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
|
||||
integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
|
||||
@@ -9115,9 +9141,9 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
|
||||
|
||||
safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519"
|
||||
integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
||||
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
||||
|
||||
safe-regex@^1.1.0:
|
||||
version "1.1.0"
|
||||
|
Reference in New Issue
Block a user