diff --git a/e2e/app.e2e-spec.ts b/e2e/app.e2e-spec.ts index 79b9f251dd..f5ac9094d0 100644 --- a/e2e/app.e2e-spec.ts +++ b/e2e/app.e2e-spec.ts @@ -7,9 +7,9 @@ describe('protractor App', () => { page = new ProtractorPage(); }); - it('should display title "DSpace"', () => { + it('should display translated title "DSpace Angular :: Home"', () => { page.navigateTo(); - expect(page.getPageTitleText()).toEqual('DSpace'); + expect(page.getPageTitleText()).toEqual('DSpace Angular :: Home'); }); it('should display header "Welcome to DSpace"', () => { diff --git a/package.json b/package.json index d9b283c526..2f74dd6cb8 100644 --- a/package.json +++ b/package.json @@ -12,15 +12,14 @@ }, "scripts": { "global": "npm install -g @angular/cli marked node-gyp nodemon node-nightly npm-check-updates npm-run-all rimraf typescript ts-node typedoc webpack webpack-bundle-analyzer pm2 rollup", - "clean:coverage": "yarn run rimraf coverage", - "clean:dist": "yarn run rimraf dist", - "clean:doc": "yarn run rimraf doc", - "clean:log": "yarn run rimraf *.log*", - "clean:json": "yarn run rimraf *.records.json", - "clean:node": "yarn run rimraf node_modules", + "clean:coverage": "rimraf coverage", + "clean:dist": "rimraf dist", + "clean:doc": "rimraf doc", + "clean:log": "rimraf *.log*", + "clean:json": "rimraf *.records.json", + "clean:node": "rimraf node_modules", "clean:prod": "yarn run clean:coverage && yarn run clean:doc && yarn run clean:dist && yarn run clean:log && yarn run clean:json", "clean": "yarn run clean:prod && yarn run clean:node", - "rimraf": "rimraf", "prebuild": "yarn run clean:dist", "prebuild:aot": "yarn run prebuild", "prebuild:prod": "yarn run prebuild", @@ -69,15 +68,15 @@ "coverage": "http-server -c-1 -o -p 9875 ./coverage" }, "dependencies": { - "@angular/animations": "4.4.4", - "@angular/common": "4.4.4", - "@angular/core": "4.4.4", - "@angular/forms": "4.4.4", - "@angular/http": "4.4.4", - "@angular/platform-browser": "4.4.4", - "@angular/platform-browser-dynamic": "4.4.4", - "@angular/platform-server": "4.4.4", - "@angular/router": "4.4.4", + "@angular/animations": "4.4.5", + "@angular/common": "4.4.5", + "@angular/core": "4.4.5", + "@angular/forms": "4.4.5", + "@angular/http": "4.4.5", + "@angular/platform-browser": "4.4.5", + "@angular/platform-browser-dynamic": "4.4.5", + "@angular/platform-server": "4.4.5", + "@angular/router": "4.4.5", "@angularclass/bootloader": "1.0.1", "@angularclass/idle-preload": "1.0.4", "@ng-bootstrap/ng-bootstrap": "1.0.0-beta.5", @@ -111,10 +110,10 @@ "zone.js": "0.8.18" }, "devDependencies": { - "@angular/compiler": "4.4.4", - "@angular/compiler-cli": "4.4.4", + "@angular/compiler": "4.4.5", + "@angular/compiler-cli": "4.4.5", "@ngrx/store-devtools": "4.0.0", - "@ngtools/webpack": "1.7.2", + "@ngtools/webpack": "1.7.4", "@types/cookie-parser": "1.4.1", "@types/deep-freeze": "0.1.1", "@types/express": "4.0.37", diff --git a/resources/i18n/en.json b/resources/i18n/en.json index 3388190295..2239d605cc 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -65,12 +65,16 @@ } }, "home": { + "title": "DSpace Angular :: Home", + "description": "", "top-level-communities": { "head": "Communities in DSpace", "help": "Select a community to browse its collections." } }, "search": { + "title": "DSpace Angular :: Search", + "description": "", "form": { "search": "Search", "search_dspace": "Search DSpace" diff --git a/src/app/+collection-page/collection-page-routing.module.ts b/src/app/+collection-page/collection-page-routing.module.ts index c62643acc2..c886aa655c 100644 --- a/src/app/+collection-page/collection-page-routing.module.ts +++ b/src/app/+collection-page/collection-page-routing.module.ts @@ -10,4 +10,6 @@ import { CollectionPageComponent } from './collection-page.component'; ]) ] }) -export class CollectionPageRoutingModule { } +export class CollectionPageRoutingModule { + +} diff --git a/src/app/+collection-page/collection-page.component.ts b/src/app/+collection-page/collection-page.component.ts index a02ebf4d07..0bc86af045 100644 --- a/src/app/+collection-page/collection-page.component.ts +++ b/src/app/+collection-page/collection-page.component.ts @@ -12,6 +12,8 @@ import { Bitstream } from '../core/shared/bitstream.model'; import { Collection } from '../core/shared/collection.model'; import { Item } from '../core/shared/item.model'; +import { MetadataService } from '../core/metadata/metadata.service'; + import { fadeIn, fadeInOut } from '../shared/animations/fade'; import { hasValue, isNotEmpty } from '../shared/empty.util'; import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; @@ -35,9 +37,12 @@ export class CollectionPageComponent implements OnInit, OnDestroy { private subs: Subscription[] = []; private collectionId: string; - constructor(private collectionDataService: CollectionDataService, - private itemDataService: ItemDataService, - private route: ActivatedRoute) { + constructor( + private collectionDataService: CollectionDataService, + private itemDataService: ItemDataService, + private metadata: MetadataService, + private route: ActivatedRoute + ) { this.paginationConfig = new PaginationComponentOptions(); this.paginationConfig.id = 'collection-page-pagination'; this.paginationConfig.pageSize = 5; @@ -50,12 +55,13 @@ export class CollectionPageComponent implements OnInit, OnDestroy { Observable.combineLatest( this.route.params, this.route.queryParams, - (params, queryParams,) => { + (params, queryParams, ) => { return Object.assign({}, params, queryParams); }) .subscribe((params) => { this.collectionId = params.id; this.collectionData = this.collectionDataService.findById(this.collectionId); + this.metadata.processRemoteData(this.collectionData); this.subs.push(this.collectionData.payload.subscribe((collection) => this.logoData = collection.logo)); const page = +params.page || this.paginationConfig.currentPage; diff --git a/src/app/+community-page/community-page-routing.module.ts b/src/app/+community-page/community-page-routing.module.ts index e090a93087..6fd5cc8cb5 100644 --- a/src/app/+community-page/community-page-routing.module.ts +++ b/src/app/+community-page/community-page-routing.module.ts @@ -10,4 +10,6 @@ import { CommunityPageComponent } from './community-page.component'; ]) ] }) -export class CommunityPageRoutingModule { } +export class CommunityPageRoutingModule { + +} diff --git a/src/app/+community-page/community-page.component.ts b/src/app/+community-page/community-page.component.ts index 004eca8f9d..c50df3b40c 100644 --- a/src/app/+community-page/community-page.component.ts +++ b/src/app/+community-page/community-page.component.ts @@ -8,6 +8,8 @@ import { Bitstream } from '../core/shared/bitstream.model'; import { Community } from '../core/shared/community.model'; +import { MetadataService } from '../core/metadata/metadata.service'; + import { fadeInOut } from '../shared/animations/fade'; import { hasValue } from '../shared/empty.util'; @@ -25,6 +27,7 @@ export class CommunityPageComponent implements OnInit, OnDestroy { constructor( private communityDataService: CommunityDataService, + private metadata: MetadataService, private route: ActivatedRoute ) { @@ -33,15 +36,13 @@ export class CommunityPageComponent implements OnInit, OnDestroy { ngOnInit(): void { this.route.params.subscribe((params: Params) => { this.communityData = this.communityDataService.findById(params.id); - this.subs.push(this.communityData.payload - .subscribe((community) => this.logoData = community.logo)); + this.metadata.processRemoteData(this.communityData); + this.subs.push(this.communityData.payload.subscribe((community) => this.logoData = community.logo)); }); } ngOnDestroy(): void { - this.subs - .filter((sub) => hasValue(sub)) - .forEach((sub) => sub.unsubscribe()); + this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe()); } } diff --git a/src/app/+home-page/home-page-routing.module.ts b/src/app/+home-page/home-page-routing.module.ts index e68b633a6d..d7dcc18f49 100644 --- a/src/app/+home-page/home-page-routing.module.ts +++ b/src/app/+home-page/home-page-routing.module.ts @@ -6,7 +6,7 @@ import { HomePageComponent } from './home-page.component'; @NgModule({ imports: [ RouterModule.forChild([ - { path: '', component: HomePageComponent, pathMatch: 'full' } + { path: '', component: HomePageComponent, pathMatch: 'full', data: { title: 'home.title' } } ]) ] }) diff --git a/src/app/+item-page/full/full-item-page.component.ts b/src/app/+item-page/full/full-item-page.component.ts index 337c598021..270cf1fcae 100644 --- a/src/app/+item-page/full/full-item-page.component.ts +++ b/src/app/+item-page/full/full-item-page.component.ts @@ -1,15 +1,17 @@ import { Component, OnInit } from '@angular/core'; -import { animate, state, transition, trigger, style, keyframes } from '@angular/animations'; +import { ActivatedRoute } from '@angular/router'; import { Observable } from 'rxjs/Observable'; import { ItemPageComponent } from '../simple/item-page.component'; import { Metadatum } from '../../core/shared/metadatum.model'; import { ItemDataService } from '../../core/data/item-data.service'; -import { ActivatedRoute } from '@angular/router'; + import { RemoteData } from '../../core/data/remote-data'; import { Item } from '../../core/shared/item.model'; +import { MetadataService } from '../../core/metadata/metadata.service'; + import { fadeInOut } from '../../shared/animations/fade'; /** @@ -30,8 +32,8 @@ export class FullItemPageComponent extends ItemPageComponent implements OnInit { metadata: Observable; - constructor(route: ActivatedRoute, items: ItemDataService) { - super(route, items); + constructor(route: ActivatedRoute, items: ItemDataService, metadataService: MetadataService) { + super(route, items, metadataService); } /*** AoT inheritance fix, will hopefully be resolved in the near future **/ diff --git a/src/app/+item-page/simple/item-page.component.ts b/src/app/+item-page/simple/item-page.component.ts index 64c03b3594..50c36fc76e 100644 --- a/src/app/+item-page/simple/item-page.component.ts +++ b/src/app/+item-page/simple/item-page.component.ts @@ -8,6 +8,8 @@ import { Bitstream } from '../../core/shared/bitstream.model'; import { Item } from '../../core/shared/item.model'; +import { MetadataService } from '../../core/metadata/metadata.service'; + import { fadeInOut } from '../../shared/animations/fade'; /** @@ -32,7 +34,11 @@ export class ItemPageComponent implements OnInit { thumbnail: Observable; - constructor(private route: ActivatedRoute, private items: ItemDataService) { + constructor( + private route: ActivatedRoute, + private items: ItemDataService, + private metadataService: MetadataService + ) { } @@ -45,6 +51,7 @@ export class ItemPageComponent implements OnInit { initialize(params) { this.id = +params.id; this.item = this.items.findById(params.id); + this.metadataService.processRemoteData(this.item); this.thumbnail = this.item.payload.flatMap((i) => i.getThumbnail()); } diff --git a/src/app/+search-page/search-page-routing.module.ts b/src/app/+search-page/search-page-routing.module.ts index de2d64c6c9..65cca99a34 100644 --- a/src/app/+search-page/search-page-routing.module.ts +++ b/src/app/+search-page/search-page-routing.module.ts @@ -6,7 +6,7 @@ import { SearchPageComponent } from './search-page.component'; @NgModule({ imports: [ RouterModule.forChild([ - { path: '', component: SearchPageComponent } + { path: '', component: SearchPageComponent, data: { title: 'search.title' } } ]) ] }) diff --git a/src/app/+search-page/search-page.component.spec.ts b/src/app/+search-page/search-page.component.spec.ts index a3e314db69..0b00021ed6 100644 --- a/src/app/+search-page/search-page.component.spec.ts +++ b/src/app/+search-page/search-page.component.spec.ts @@ -104,7 +104,7 @@ describe('SearchPageComponent', () => { (comp as any).updateSearchResults({}); expect(comp.results as any).toBe(mockResults); - }); + }); }); }); diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index 99cf1068e4..0854c5756e 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -1,4 +1,3 @@ -// ... test imports import { async, ComponentFixture, @@ -23,14 +22,18 @@ import { AppComponent } from './app.component'; import { HostWindowState } from './shared/host-window.reducer'; import { HostWindowResizeAction } from './shared/host-window.actions'; -import { MockTranslateLoader } from './shared/testing/mock-translate-loader'; import { BrowserTransferStateModule } from '../modules/transfer-state/browser-transfer-state.module'; import { BrowserTransferStoreModule } from '../modules/transfer-store/browser-transfer-store.module'; +import { MetadataService } from './core/metadata/metadata.service'; + import { GLOBAL_CONFIG, ENV_CONFIG } from '../config'; import { NativeWindowRef, NativeWindowService } from './shared/window.service'; +import { MockTranslateLoader } from './shared/mocks/mock-translate-loader'; +import { MockMetadataService } from './shared/mocks/mock-metadata-service'; + let comp: AppComponent; let fixture: ComponentFixture; let de: DebugElement; @@ -57,6 +60,7 @@ describe('App component', () => { providers: [ { provide: GLOBAL_CONFIG, useValue: ENV_CONFIG }, { provide: NativeWindowService, useValue: new NativeWindowRef() }, + { provide: MetadataService, useValue: new MockMetadataService() }, AppComponent ], schemas: [CUSTOM_ELEMENTS_SCHEMA] diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 9ea58365df..221c1c37d1 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -12,12 +12,10 @@ import { TranslateService } from '@ngx-translate/core'; import { Store } from '@ngrx/store'; import { TransferState } from '../modules/transfer-state/transfer-state'; - import { HostWindowState } from './shared/host-window.reducer'; - import { HostWindowResizeAction } from './shared/host-window.actions'; - import { NativeWindowRef, NativeWindowService } from './shared/window.service'; +import { MetadataService } from './core/metadata/metadata.service'; import { GLOBAL_CONFIG, GlobalConfig } from '../config'; @@ -35,13 +33,16 @@ export class AppComponent implements OnInit { @Inject(NativeWindowService) private _window: NativeWindowRef, private translate: TranslateService, private cache: TransferState, - private store: Store + private store: Store, + private metadata: MetadataService ) { // this language will be used as a fallback when a translation isn't found in the current language translate.setDefaultLang('en'); // the lang to use, if the lang isn't available, it will use the current loader to get them translate.use('en'); + metadata.listenForRouteChange(); + if (config.debug) { console.info(config); } diff --git a/src/app/core/cache/models/normalized-bitstream-format.model.ts b/src/app/core/cache/models/normalized-bitstream-format.model.ts index 2813794011..bb8b049a1c 100644 --- a/src/app/core/cache/models/normalized-bitstream-format.model.ts +++ b/src/app/core/cache/models/normalized-bitstream-format.model.ts @@ -1,13 +1,30 @@ -import { inheritSerialization } from 'cerialize'; +import { inheritSerialization, autoserialize } from 'cerialize'; +import { mapsTo } from '../builders/build-decorators'; + +import { BitstreamFormat } from '../../shared/bitstream-format.model'; import { NormalizedObject } from './normalized-object.model'; +@mapsTo(BitstreamFormat) @inheritSerialization(NormalizedObject) export class NormalizedBitstreamFormat extends NormalizedObject { - // TODO: this class was created as a placeholder when we connected to the live rest api - get uuid(): string { - return this.self; - } + @autoserialize + shortDescription: string; + + @autoserialize + description: string; + + @autoserialize + mimetype: string; + + @autoserialize + supportLevel: number; + + @autoserialize + internal: boolean; + + @autoserialize + extensions: string; } diff --git a/src/app/core/cache/models/normalized-bitstream.model.ts b/src/app/core/cache/models/normalized-bitstream.model.ts index ba5343e252..db8002a874 100644 --- a/src/app/core/cache/models/normalized-bitstream.model.ts +++ b/src/app/core/cache/models/normalized-bitstream.model.ts @@ -21,16 +21,11 @@ export class NormalizedBitstream extends NormalizedDSpaceObject { @autoserialize content: string; - /** - * The mime type of this Bitstream - */ - @autoserialize - mimetype: string; - /** * The format of this Bitstream */ @autoserialize + @relationship(ResourceType.BitstreamFormat, false) format: string; /** diff --git a/src/app/core/cache/models/normalized-object-factory.ts b/src/app/core/cache/models/normalized-object-factory.ts index e9f3b1c9e6..3c67b18b3e 100644 --- a/src/app/core/cache/models/normalized-object-factory.ts +++ b/src/app/core/cache/models/normalized-object-factory.ts @@ -15,11 +15,9 @@ export class NormalizedObjectFactory { case ResourceType.Bitstream: { return NormalizedBitstream } - // commented out for now, bitstreamformats aren't used in the UI yet - // and slow things down noticeably - // case ResourceType.BitstreamFormat: { - // return NormalizedBitstreamFormat - // } + case ResourceType.BitstreamFormat: { + return NormalizedBitstreamFormat + } case ResourceType.Bundle: { return NormalizedBundle } diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 3372e06e47..bfd5cebcb6 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -1,29 +1,35 @@ -import { NgModule, Optional, SkipSelf, ModuleWithProviders } from '@angular/core'; +import { + NgModule, + Optional, + SkipSelf, + ModuleWithProviders +} from '@angular/core'; import { CommonModule } from '@angular/common'; -import { isNotEmpty } from '../shared/empty.util'; -import { DSpaceRESTv2Service } from './dspace-rest-v2/dspace-rest-v2.service'; -import { ObjectCacheService } from './cache/object-cache.service'; -import { ResponseCacheService } from './cache/response-cache.service'; -import { CollectionDataService } from './data/collection-data.service'; -import { ItemDataService } from './data/item-data.service'; -import { RequestService } from './data/request.service'; -import { RemoteDataBuildService } from './cache/builders/remote-data-build.service'; -import { CommunityDataService } from './data/community-data.service'; -import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; -import { coreEffects } from './core.effects'; -import { EffectsModule } from '@ngrx/effects'; import { StoreModule } from '@ngrx/store'; +import { EffectsModule } from '@ngrx/effects'; + +import { coreEffects } from './core.effects'; import { coreReducers } from './core.reducers'; -import { DSOResponseParsingService } from './data/dso-response-parsing.service'; -import { RootResponseParsingService } from './data/root-response-parsing.service'; + +import { isNotEmpty } from '../shared/empty.util'; import { ApiService } from '../shared/api.service'; - +import { CollectionDataService } from './data/collection-data.service'; +import { CommunityDataService } from './data/community-data.service'; +import { DSOResponseParsingService } from './data/dso-response-parsing.service'; +import { DSpaceRESTv2Service } from './dspace-rest-v2/dspace-rest-v2.service'; import { HostWindowService } from '../shared/host-window.service'; -import { NativeWindowFactory, NativeWindowService } from '../shared/window.service'; - +import { ItemDataService } from './data/item-data.service'; +import { MetadataService } from './metadata/metadata.service'; +import { ObjectCacheService } from './cache/object-cache.service'; +import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; +import { RemoteDataBuildService } from './cache/builders/remote-data-build.service'; +import { RequestService } from './data/request.service'; +import { ResponseCacheService } from './cache/response-cache.service'; +import { RootResponseParsingService } from './data/root-response-parsing.service'; import { ServerResponseService } from '../shared/server-response.service'; +import { NativeWindowFactory, NativeWindowService } from '../shared/window.service'; import { BrowseService } from './browse/browse.service'; import { BrowseResponseParsingService } from './data/browse-response-parsing.service'; @@ -49,6 +55,7 @@ const PROVIDERS = [ DSpaceRESTv2Service, HostWindowService, ItemDataService, + MetadataService, ObjectCacheService, PaginationComponentOptions, RemoteDataBuildService, diff --git a/src/app/core/metadata/metadata.service.spec.ts b/src/app/core/metadata/metadata.service.spec.ts new file mode 100644 index 0000000000..1258751f58 --- /dev/null +++ b/src/app/core/metadata/metadata.service.spec.ts @@ -0,0 +1,226 @@ +import { ComponentFixture, TestBed, async, fakeAsync, inject, tick } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { Location, CommonModule } from '@angular/common'; +import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { By, Meta, MetaDefinition, Title } from '@angular/platform-browser'; +import { ActivatedRoute, Router } from '@angular/router'; + +import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; + +import { Store, StoreModule } from '@ngrx/store'; + +import { Observable } from 'rxjs/Observable'; + +import { MetadataService } from './metadata.service'; + +import { CoreState } from '../core.reducers'; + +import { GlobalConfig } from '../../../config/global-config.interface'; +import { ENV_CONFIG, GLOBAL_CONFIG } from '../../../config'; + +import { ItemDataService } from '../data/item-data.service'; +import { ObjectCacheService } from '../cache/object-cache.service'; +import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; +import { RequestService } from '../data/request.service'; +import { ResponseCacheService } from '../cache/response-cache.service'; + +import { RemoteData } from '../../core/data/remote-data'; +import { Item } from '../../core/shared/item.model'; + +import { MockItem } from '../../shared/mocks/mock-item'; +import { MockTranslateLoader } from '../../shared/mocks/mock-translate-loader'; + +/* tslint:disable:max-classes-per-file */ +@Component({ + template: `` +}) +class TestComponent { + constructor(private metadata: MetadataService) { + metadata.listenForRouteChange(); + } +} + +@Component({ template: '' }) class DummyItemComponent { + constructor(private route: ActivatedRoute, private items: ItemDataService, private metadata: MetadataService) { + this.route.params.subscribe((params) => { + this.metadata.processRemoteData(this.items.findById(params.id)); + }); + } +} +/* tslint:enable:max-classes-per-file */ + +describe('MetadataService', () => { + let metadataService: MetadataService; + + let meta: Meta; + + let title: Title; + + let store: Store; + + let objectCacheService: ObjectCacheService; + let responseCacheService: ResponseCacheService; + let requestService: RequestService; + let remoteDataBuildService: RemoteDataBuildService; + let itemDataService: ItemDataService; + + let location: Location; + let router: Router; + let fixture: ComponentFixture; + + let tagStore: Map; + + let envConfig: GlobalConfig; + + beforeEach(() => { + + store = new Store(undefined, undefined, undefined); + spyOn(store, 'dispatch'); + + objectCacheService = new ObjectCacheService(store); + responseCacheService = new ResponseCacheService(store); + requestService = new RequestService(objectCacheService, responseCacheService, store); + remoteDataBuildService = new RemoteDataBuildService(objectCacheService, responseCacheService, requestService); + + TestBed.configureTestingModule({ + imports: [ + CommonModule, + StoreModule.forRoot({}), + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: MockTranslateLoader + } + }), + RouterTestingModule.withRoutes([ + { path: 'items/:id', component: DummyItemComponent, pathMatch: 'full' }, + { path: 'other', component: DummyItemComponent, pathMatch: 'full', data: { title: 'Dummy Title', description: 'This is a dummy item component for testing!' } } + ]) + ], + declarations: [ + TestComponent, + DummyItemComponent + ], + providers: [ + { provide: ObjectCacheService, useValue: objectCacheService }, + { provide: ResponseCacheService, useValue: responseCacheService }, + { provide: RequestService, useValue: requestService }, + { provide: RemoteDataBuildService, useValue: remoteDataBuildService }, + { provide: GLOBAL_CONFIG, useValue: ENV_CONFIG }, + Meta, + Title, + ItemDataService, + MetadataService + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA] + }); + meta = TestBed.get(Meta); + title = TestBed.get(Title); + itemDataService = TestBed.get(ItemDataService); + metadataService = TestBed.get(MetadataService); + + envConfig = TestBed.get(GLOBAL_CONFIG); + + router = TestBed.get(Router); + location = TestBed.get(Location); + + fixture = TestBed.createComponent(TestComponent); + + tagStore = metadataService.getTagStore(); + }); + + it('items page should set meta tags', fakeAsync(() => { + spyOn(itemDataService, 'findById').and.returnValue(mockRemoteData(MockItem)); + router.navigate(['/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357']); + tick(); + expect(title.getTitle()).toEqual('Test PowerPoint Document'); + expect(tagStore.get('citation_title')[0].content).toEqual('Test PowerPoint Document'); + expect(tagStore.get('citation_author')[0].content).toEqual('Doe, Jane'); + expect(tagStore.get('citation_date')[0].content).toEqual('1650-06-26T19:58:25Z'); + expect(tagStore.get('citation_issn')[0].content).toEqual('123456789'); + expect(tagStore.get('citation_language')[0].content).toEqual('en'); + expect(tagStore.get('citation_keywords')[0].content).toEqual('keyword1; keyword2; keyword3'); + })); + + it('items page should set meta tags as published Thesis', fakeAsync(() => { + spyOn(itemDataService, 'findById').and.returnValue(mockRemoteData(mockPublisher(mockType(MockItem, 'Thesis')))); + router.navigate(['/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357']); + tick(); + expect(tagStore.get('citation_dissertation_name')[0].content).toEqual('Test PowerPoint Document'); + expect(tagStore.get('citation_dissertation_institution')[0].content).toEqual('Mock Publisher'); + expect(tagStore.get('citation_abstract_html_url')[0].content).toEqual([envConfig.ui.baseUrl, router.url].join('')); + expect(tagStore.get('citation_pdf_url')[0].content).toEqual('https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/99b00f3c-1cc6-4689-8158-91965bee6b28/content'); + })); + + it('items page should set meta tags as published Technical Report', fakeAsync(() => { + spyOn(itemDataService, 'findById').and.returnValue(mockRemoteData(mockPublisher(mockType(MockItem, 'Technical Report')))); + router.navigate(['/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357']); + tick(); + expect(tagStore.get('citation_technical_report_institution')[0].content).toEqual('Mock Publisher'); + })); + + it('other navigation should title and description', fakeAsync(() => { + spyOn(itemDataService, 'findById').and.returnValue(mockRemoteData(MockItem)); + router.navigate(['/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357']); + tick(); + expect(tagStore.size).toBeGreaterThan(0) + router.navigate(['/other']); + tick(); + expect(tagStore.size).toEqual(2); + expect(title.getTitle()).toEqual('Dummy Title'); + expect(tagStore.get('title')[0].content).toEqual('Dummy Title'); + expect(tagStore.get('description')[0].content).toEqual('This is a dummy item component for testing!'); + })); + + const mockRemoteData = (mockItem: Item): RemoteData => { + return new RemoteData( + Observable.create((observer) => { + observer.next(''); + }), + Observable.create((observer) => { + observer.next(false); + }), + Observable.create((observer) => { + observer.next(false); + }), + Observable.create((observer) => { + observer.next(true); + }), + Observable.create((observer) => { + observer.next(''); + }), + Observable.create((observer) => { + observer.next(200); + }), + Observable.create((observer) => { + observer.next({}); + }), + Observable.create((observer) => { + observer.next(MockItem); + }) + ); + } + + const mockType = (mockItem: Item, type: string): Item => { + const typedMockItem = Object.assign(new Item(), mockItem) as Item; + for (const metadatum of typedMockItem.metadata) { + if (metadatum.key === 'dc.type') { + metadatum.value = type; + break; + } + } + return typedMockItem; + } + + const mockPublisher = (mockItem: Item): Item => { + const publishedMockItem = Object.assign(new Item(), mockItem) as Item; + publishedMockItem.metadata.push({ + key: 'dc.publisher', + language: 'en_US', + value: 'Mock Publisher' + }); + return publishedMockItem; + } + +}); diff --git a/src/app/core/metadata/metadata.service.ts b/src/app/core/metadata/metadata.service.ts new file mode 100644 index 0000000000..32b002e721 --- /dev/null +++ b/src/app/core/metadata/metadata.service.ts @@ -0,0 +1,400 @@ +import 'rxjs/add/operator/first' +import 'rxjs/add/operator/take' + +import { Inject, Injectable } from '@angular/core'; +import { + ActivatedRoute, + Event, + NavigationEnd, + Params, + Router +} from '@angular/router'; + +import { Meta, MetaDefinition, Title } from '@angular/platform-browser'; + +import { TranslateService } from '@ngx-translate/core'; + +import { BehaviorSubject } from 'rxjs/BehaviorSubject'; +import { Observable } from 'rxjs/Observable'; + +import { RemoteData } from '../data/remote-data'; +import { Bitstream } from '../shared/bitstream.model'; +import { CacheableObject } from '../cache/object-cache.reducer'; +import { DSpaceObject } from '../shared/dspace-object.model'; +import { Item } from '../shared/item.model'; +import { Metadatum } from '../shared/metadatum.model'; + +import { GLOBAL_CONFIG, GlobalConfig } from '../../../config'; + +@Injectable() +export class MetadataService { + + private initialized: boolean; + + private tagStore: Map; + + private currentObject: BehaviorSubject; + + constructor( + private router: Router, + private translate: TranslateService, + private meta: Meta, + private title: Title, + @Inject(GLOBAL_CONFIG) private envConfig: GlobalConfig + ) { + // TODO: determine what open graph meta tags are needed and whether + // the differ per route. potentially add image based on DSpaceObject + this.meta.addTags([ + { property: 'og:title', content: 'DSpace Angular Universal' }, + { property: 'og:description', content: 'The modern front-end for DSpace 7.' } + ]); + this.initialized = false; + this.tagStore = new Map(); + } + + public listenForRouteChange(): void { + this.router.events + .filter((event) => event instanceof NavigationEnd) + .map(() => this.router.routerState.root) + .map((route: ActivatedRoute) => { + route = this.getCurrentRoute(route); + return { params: route.params, data: route.data }; + }).subscribe((routeInfo: any) => { + this.processRouteChange(routeInfo); + }); + } + + public processRemoteData(remoteData: RemoteData): void { + remoteData.payload.take(1).subscribe((dspaceObject: DSpaceObject) => { + if (!this.initialized) { + this.initialize(dspaceObject); + } + this.currentObject.next(dspaceObject); + }); + } + + private processRouteChange(routeInfo: any): void { + if (routeInfo.params.value.id === undefined) { + this.clearMetaTags(); + } + if (routeInfo.data.value.title) { + this.translate.get(routeInfo.data.value.title).take(1).subscribe((translatedTitle: string) => { + this.addMetaTag('title', translatedTitle); + this.title.setTitle(translatedTitle); + }); + } + if (routeInfo.data.value.description) { + this.translate.get(routeInfo.data.value.description).take(1).subscribe((translatedDescription: string) => { + this.addMetaTag('description', translatedDescription); + }); + } + } + + private initialize(dspaceObject: DSpaceObject): void { + this.currentObject = new BehaviorSubject(dspaceObject); + this.currentObject.asObservable().distinctUntilKeyChanged('uuid').subscribe(() => { + this.setMetaTags(); + }); + this.initialized = true; + } + + private getCurrentRoute(route: ActivatedRoute): ActivatedRoute { + while (route.firstChild) { + route = route.firstChild; + } + return route; + } + + private setMetaTags(): void { + + this.clearMetaTags(); + + this.setTitleTag(); + this.setDescriptionTag(); + + this.setCitationTitleTag(); + this.setCitationAuthorTags(); + this.setCitationDateTag(); + this.setCitationISSNTag(); + this.setCitationISBNTag(); + + this.setCitationLanguageTag(); + this.setCitationKeywordsTag(); + + this.setCitationAbstractUrlTag(); + this.setCitationPdfUrlTag(); + + if (this.isDissertation()) { + this.setCitationDissertationNameTag(); + this.setCitationDissertationInstitutionTag(); + } + + if (this.isTechReport()) { + this.setCitationTechReportInstitutionTag(); + } + + // this.setCitationJournalTitleTag(); + // this.setCitationVolumeTag(); + // this.setCitationIssueTag(); + // this.setCitationFirstPageTag(); + // this.setCitationLastPageTag(); + // this.setCitationDOITag(); + // this.setCitationPMIDTag(); + + // this.setCitationFullTextTag(); + + // this.setCitationConferenceTag(); + + // this.setCitationPatentCountryTag(); + // this.setCitationPatentNumberTag(); + + } + + /** + * Add to the + */ + private setTitleTag(): void { + const value = this.getMetaTagValue('dc.title'); + this.addMetaTag('title', value); + this.title.setTitle(value); + } + + /** + * Add to the + */ + private setDescriptionTag(): void { + // TODO: truncate abstract + const value = this.getMetaTagValue('dc.description.abstract'); + this.addMetaTag('desciption', value); + } + + /** + * Add to the + */ + private setCitationTitleTag(): void { + const value = this.getMetaTagValue('dc.title'); + this.addMetaTag('citation_title', value); + } + + /** + * Add to the + */ + private setCitationAuthorTags(): void { + const values: string[] = this.getMetaTagValues(['dc.author', 'dc.contributor.author', 'dc.creator']); + this.addMetaTags('citation_author', values); + } + + /** + * Add to the + */ + private setCitationDateTag(): void { + const value = this.getFirstMetaTagValue(['dc.date.copyright', 'dc.date.issued', 'dc.date.available', 'dc.date.accessioned']); + this.addMetaTag('citation_date', value); + } + + /** + * Add to the + */ + private setCitationISSNTag(): void { + const value = this.getMetaTagValue('dc.identifier.issn'); + this.addMetaTag('citation_issn', value); + } + + /** + * Add to the + */ + private setCitationISBNTag(): void { + const value = this.getMetaTagValue('dc.identifier.isbn'); + this.addMetaTag('citation_isbn', value); + } + + /** + * Add to the + */ + private setCitationLanguageTag(): void { + const value = this.getFirstMetaTagValue(['dc.language', 'dc.language.iso']); + this.addMetaTag('citation_language', value); + } + + /** + * Add to the + */ + private setCitationDissertationNameTag(): void { + const value = this.getMetaTagValue('dc.title'); + this.addMetaTag('citation_dissertation_name', value); + } + + /** + * Add to the + */ + private setCitationDissertationInstitutionTag(): void { + const value = this.getMetaTagValue('dc.publisher'); + this.addMetaTag('citation_dissertation_institution', value); + } + + /** + * Add to the + */ + private setCitationTechReportInstitutionTag(): void { + const value = this.getMetaTagValue('dc.publisher'); + this.addMetaTag('citation_technical_report_institution', value); + } + + /** + * Add to the + */ + private setCitationKeywordsTag(): void { + const value = this.getMetaTagValuesAndCombine('dc.subject'); + this.addMetaTag('citation_keywords', value); + } + + /** + * Add to the + */ + private setCitationAbstractUrlTag(): void { + if (this.currentObject.value instanceof Item) { + const value = [this.envConfig.ui.baseUrl, this.router.url].join(''); + this.addMetaTag('citation_abstract_html_url', value); + } + } + + /** + * Add to the + */ + private setCitationPdfUrlTag(): void { + if (this.currentObject.value instanceof Item) { + const item = this.currentObject.value as Item; + // NOTE: Observable resolves many times with same data + // taking only two, fist one is empty array + item.getFiles().take(2).subscribe((bitstreams: Bitstream[]) => { + for (const bitstream of bitstreams) { + bitstream.format.payload.take(1).subscribe((format) => { + if (format.mimetype === 'application/pdf') { + this.addMetaTag('citation_pdf_url', bitstream.content); + } + }); + } + }); + } + } + + /** + * Returns true if this._item is a dissertation + * + * @returns {boolean} + * true if this._item has a dc.type equal to 'Thesis' + */ + private isDissertation(): boolean { + let isDissertation = false; + for (const metadatum of this.currentObject.value.metadata) { + if (metadatum.key === 'dc.type') { + isDissertation = metadatum.value.toLowerCase() === 'thesis'; + break; + } + } + return isDissertation; + } + + /** + * Returns true if this._item is a technical report + * + * @returns {boolean} + * true if this._item has a dc.type equal to 'Technical Report' + */ + private isTechReport(): boolean { + let isTechReport = false; + for (const metadatum of this.currentObject.value.metadata) { + if (metadatum.key === 'dc.type') { + isTechReport = metadatum.value.toLowerCase() === 'technical report'; + break; + } + } + return isTechReport; + } + + private getMetaTagValue(key: string): string { + let value: string; + for (const metadatum of this.currentObject.value.metadata) { + if (metadatum.key === key) { + value = metadatum.value; + } + } + return value; + } + + private getFirstMetaTagValue(keys: string[]): string { + let value: string; + for (const metadatum of this.currentObject.value.metadata) { + for (const key of keys) { + if (key === metadatum.key) { + value = metadatum.value; + break; + } + } + if (value !== undefined) { + break; + } + } + return value; + } + + private getMetaTagValuesAndCombine(key: string): string { + return this.getMetaTagValues([key]).join('; '); + } + + private getMetaTagValues(keys: string[]): string[] { + const values: string[] = []; + for (const metadatum of this.currentObject.value.metadata) { + for (const key of keys) { + if (key === metadatum.key) { + values.push(metadatum.value); + } + } + } + return values; + } + + private addMetaTag(property: string, content: string): void { + if (content) { + const tag = { property, content } as MetaDefinition; + this.meta.addTag(tag); + this.storeTag(property, tag); + } + } + + private addMetaTags(property: string, content: string[]): void { + for (const value of content) { + this.addMetaTag(property, value); + } + } + + private storeTag(key: string, tag: MetaDefinition): void { + const tags: MetaDefinition[] = this.getTags(key); + tags.push(tag); + this.setTags(key, tags); + } + + private getTags(key: string): MetaDefinition[] { + let tags: MetaDefinition[] = this.tagStore.get(key); + if (tags === undefined) { + tags = []; + } + return tags; + } + + private setTags(key: string, tags: MetaDefinition[]): void { + this.tagStore.set(key, tags); + } + + public clearMetaTags() { + this.tagStore.forEach((tags: MetaDefinition[], property: string) => { + this.meta.removeTag("property='" + property + "'"); + }); + this.tagStore.clear(); + } + + public getTagStore(): Map { + return this.tagStore; + } + +} diff --git a/src/app/core/shared/bitstream-format.model.ts b/src/app/core/shared/bitstream-format.model.ts new file mode 100644 index 0000000000..c0f6be29c9 --- /dev/null +++ b/src/app/core/shared/bitstream-format.model.ts @@ -0,0 +1,17 @@ +import { DSpaceObject } from './dspace-object.model'; + +export class BitstreamFormat extends DSpaceObject { + + shortDescription: string; + + description: string; + + mimetype: string; + + supportLevel: number; + + internal: boolean; + + extensions: string; + +} diff --git a/src/app/core/shared/bitstream.model.ts b/src/app/core/shared/bitstream.model.ts index 5e4ee929d4..0b77a7b032 100644 --- a/src/app/core/shared/bitstream.model.ts +++ b/src/app/core/shared/bitstream.model.ts @@ -1,6 +1,7 @@ import { DSpaceObject } from './dspace-object.model'; import { RemoteData } from '../data/remote-data'; import { Item } from './item.model'; +import { BitstreamFormat } from './bitstream-format.model'; export class Bitstream extends DSpaceObject { @@ -9,11 +10,6 @@ export class Bitstream extends DSpaceObject { */ sizeBytes: number; - /** - * The mime type of this Bitstream - */ - mimetype: string; - /** * The description of this Bitstream */ @@ -24,6 +20,11 @@ export class Bitstream extends DSpaceObject { */ bundleName: string; + /** + * An array of Bitstream Format of this Bitstream + */ + format: RemoteData; + /** * An array of Items that are direct parents of this Bitstream */ diff --git a/src/app/footer/footer.component.spec.ts b/src/app/footer/footer.component.spec.ts index 6d56726764..dde432e1ef 100644 --- a/src/app/footer/footer.component.spec.ts +++ b/src/app/footer/footer.component.spec.ts @@ -21,7 +21,7 @@ import { Store, StoreModule } from '@ngrx/store'; // Load the implementations that should be tested import { FooterComponent } from './footer.component'; -import { MockTranslateLoader } from '../shared/testing/mock-translate-loader'; +import { MockTranslateLoader } from '../shared/mocks/mock-translate-loader'; let comp: FooterComponent; let fixture: ComponentFixture; diff --git a/src/app/shared/error/error.component.spec.ts b/src/app/shared/error/error.component.spec.ts index a0226f7f86..7335f93aed 100644 --- a/src/app/shared/error/error.component.spec.ts +++ b/src/app/shared/error/error.component.spec.ts @@ -4,7 +4,7 @@ import { DebugElement } from '@angular/core'; import { TranslateModule, TranslateLoader, TranslateService } from '@ngx-translate/core'; -import { MockTranslateLoader } from '../testing/mock-translate-loader'; +import { MockTranslateLoader } from '../mocks/mock-translate-loader'; import { ErrorComponent } from './error.component'; @@ -25,8 +25,8 @@ describe('ErrorComponent (inline template)', () => { } }), ], - declarations: [ ErrorComponent ], // declare the test component - providers: [ TranslateService ] + declarations: [ErrorComponent], // declare the test component + providers: [TranslateService] }).compileComponents(); // compile template and css })); diff --git a/src/app/shared/loading/loading.component.spec.ts b/src/app/shared/loading/loading.component.spec.ts index 0b758b8218..aca9673282 100644 --- a/src/app/shared/loading/loading.component.spec.ts +++ b/src/app/shared/loading/loading.component.spec.ts @@ -4,7 +4,7 @@ import { DebugElement } from '@angular/core'; import { TranslateModule, TranslateLoader, TranslateService } from '@ngx-translate/core'; -import { MockTranslateLoader } from '../testing/mock-translate-loader'; +import { MockTranslateLoader } from '../mocks/mock-translate-loader'; import { LoadingComponent } from './loading.component'; @@ -25,8 +25,8 @@ describe('LoadingComponent (inline template)', () => { } }), ], - declarations: [ LoadingComponent ], // declare the test component - providers: [ TranslateService ] + declarations: [LoadingComponent], // declare the test component + providers: [TranslateService] }).compileComponents(); // compile template and css })); diff --git a/src/app/shared/mocks/mock-action.ts b/src/app/shared/mocks/mock-action.ts new file mode 100644 index 0000000000..0f619c8aff --- /dev/null +++ b/src/app/shared/mocks/mock-action.ts @@ -0,0 +1,6 @@ +import { Action } from '@ngrx/store'; + +export class MockAction implements Action { + type = null; + payload: {}; +} diff --git a/src/app/shared/mocks/mock-active-router.ts b/src/app/shared/mocks/mock-active-router.ts new file mode 100644 index 0000000000..391b9c3426 --- /dev/null +++ b/src/app/shared/mocks/mock-active-router.ts @@ -0,0 +1,34 @@ +import { Params } from '@angular/router'; + +import { BehaviorSubject } from 'rxjs/BehaviorSubject'; + +export class MockActivatedRoute { + + private _testParams?: any; + + // ActivatedRoute.params is Observable + private subject?: BehaviorSubject = new BehaviorSubject(this.testParams); + + params = this.subject.asObservable(); + queryParams = this.subject.asObservable(); + + constructor(params?: Params) { + if (params) { + this.testParams = params; + } else { + this.testParams = {}; + } + } + + // Test parameters + get testParams() { return this._testParams; } + set testParams(params: {}) { + this._testParams = params; + this.subject.next(params); + } + + // ActivatedRoute.snapshot.params + get snapshot() { + return { params: this.testParams }; + } +} diff --git a/src/app/shared/mocks/mock-host-window-service.ts b/src/app/shared/mocks/mock-host-window-service.ts new file mode 100644 index 0000000000..104e712682 --- /dev/null +++ b/src/app/shared/mocks/mock-host-window-service.ts @@ -0,0 +1,19 @@ +import { Observable } from 'rxjs/Observable'; + +// declare a stub service +export class MockHostWindowService { + + private width: number; + + constructor(width) { + this.setWidth(width); + } + + setWidth(width) { + this.width = width; + } + + isXs(): Observable { + return Observable.of(this.width < 576); + } +} diff --git a/src/app/shared/mocks/mock-item.ts b/src/app/shared/mocks/mock-item.ts new file mode 100644 index 0000000000..0331491aa0 --- /dev/null +++ b/src/app/shared/mocks/mock-item.ts @@ -0,0 +1,273 @@ +import { Observable } from 'rxjs/Observable'; + +import { Item } from '../../core/shared/item.model'; + +/* tslint:disable:no-shadowed-variable */ +export const MockItem: Item = Object.assign(new Item(), { + handle: '10673/6', + lastModified: '2017-04-24T19:44:08.178+0000', + isArchived: true, + isDiscoverable: true, + isWithdrawn: false, + bitstreams: { + self: { + _isScalar: true, + value: '1507836003548', + scheduler: null + }, + requestPending: Observable.create((observer) => { + observer.next(false); + }), + responsePending: Observable.create((observer) => { + observer.next(false); + }), + isSuccessFul: Observable.create((observer) => { + observer.next(true); + }), + errorMessage: Observable.create((observer) => { + observer.next(''); + }), + statusCode: Observable.create((observer) => { + observer.next(202); + }), + pageInfo: Observable.create((observer) => { + observer.next({}); + }), + payload: Observable.create((observer) => { + observer.next([ + { + sizeBytes: 10201, + content: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/cf9b0c8e-a1eb-4b65-afd0-567366448713/content', + format: { + self: { + _isScalar: true, + value: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreamformats/10', + scheduler: null + }, + requestPending: Observable.create((observer) => { + observer.next(false); + }), + responsePending: Observable.create((observer) => { + observer.next(false); + }), + isSuccessFul: Observable.create((observer) => { + observer.next(true); + }), + errorMessage: Observable.create((observer) => { + observer.next(''); + }), + statusCode: Observable.create((observer) => { + observer.next(202); + }), + pageInfo: Observable.create((observer) => { + observer.next({}); + }), + payload: Observable.create((observer) => { + observer.next({ + shortDescription: 'Microsoft Word XML', + description: 'Microsoft Word XML', + mimetype: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + supportLevel: 0, + internal: false, + extensions: null, + self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreamformats/10' + }); + }) + }, + bundleName: 'ORIGINAL', + self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/cf9b0c8e-a1eb-4b65-afd0-567366448713', + id: 'cf9b0c8e-a1eb-4b65-afd0-567366448713', + uuid: 'cf9b0c8e-a1eb-4b65-afd0-567366448713', + type: 'bitstream', + name: 'test_word.docx', + metadata: [ + { + key: 'dc.title', + language: null, + value: 'test_word.docx' + } + ] + }, + { + sizeBytes: 31302, + content: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/99b00f3c-1cc6-4689-8158-91965bee6b28/content', + format: { + self: { + _isScalar: true, + value: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreamformats/4', + scheduler: null + }, + requestPending: Observable.create((observer) => { + observer.next(false); + }), + responsePending: Observable.create((observer) => { + observer.next(false); + }), + isSuccessFul: Observable.create((observer) => { + observer.next(true); + }), + errorMessage: Observable.create((observer) => { + observer.next(''); + }), + statusCode: Observable.create((observer) => { + observer.next(202); + }), + pageInfo: Observable.create((observer) => { + observer.next({}); + }), + payload: Observable.create((observer) => { + observer.next({ + shortDescription: 'Adobe PDF', + description: 'Adobe Portable Document Format', + mimetype: 'application/pdf', + supportLevel: 0, + internal: false, + extensions: null, + self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreamformats/4' + }); + }) + }, + bundleName: 'ORIGINAL', + self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/99b00f3c-1cc6-4689-8158-91965bee6b28', + id: '99b00f3c-1cc6-4689-8158-91965bee6b28', + uuid: '99b00f3c-1cc6-4689-8158-91965bee6b28', + type: 'bitstream', + name: 'test_pdf.pdf', + metadata: [ + { + key: 'dc.title', + language: null, + value: 'test_pdf.pdf' + } + ] + } + ]); + }) + }, + self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357', + id: '0ec7ff22-f211-40ab-a69e-c819b0b1f357', + uuid: '0ec7ff22-f211-40ab-a69e-c819b0b1f357', + type: 'item', + name: 'Test PowerPoint Document', + metadata: [ + { + key: 'dc.creator', + language: 'en_US', + value: 'Doe, Jane' + }, + { + key: 'dc.date.accessioned', + language: null, + value: '1650-06-26T19:58:25Z' + }, + { + key: 'dc.date.available', + language: null, + value: '1650-06-26T19:58:25Z' + }, + { + key: 'dc.date.issued', + language: null, + value: '1650-06-26' + }, + { + key: 'dc.identifier.issn', + language: 'en_US', + value: '123456789' + }, + { + key: 'dc.identifier.uri', + language: null, + value: 'http://dspace7.4science.it/xmlui/handle/10673/6' + }, + { + key: 'dc.description.abstract', + language: 'en_US', + value: 'This is really just a sample abstract. If it was a real abstract it would contain useful information about this test document. Sorry though, nothing useful in this paragraph. You probably shouldn\'t have even bothered to read it!' + }, + { + key: 'dc.description.provenance', + language: 'en', + value: 'Made available in DSpace on 2012-06-26T19:58:25Z (GMT). No. of bitstreams: 2\r\ntest_ppt.ppt: 12707328 bytes, checksum: a353fc7d29b3c558c986f7463a41efd3 (MD5)\r\ntest_ppt.pptx: 12468572 bytes, checksum: 599305edb4ebee329667f2c35b14d1d6 (MD5)' + }, + { + key: 'dc.description.provenance', + language: 'en', + value: 'Restored into DSpace on 2013-06-13T09:17:34Z (GMT).' + }, + { + key: 'dc.description.provenance', + language: 'en', + value: 'Restored into DSpace on 2013-06-13T11:04:16Z (GMT).' + }, + { + key: 'dc.description.provenance', + language: 'en', + value: 'Restored into DSpace on 2017-04-24T19:44:08Z (GMT).' + }, + { + key: 'dc.language', + language: 'en_US', + value: 'en' + }, + { + key: 'dc.rights', + language: 'en_US', + value: '© Jane Doe' + }, + { + key: 'dc.subject', + language: 'en_US', + value: 'keyword1' + }, + { + key: 'dc.subject', + language: 'en_US', + value: 'keyword2' + }, + { + key: 'dc.subject', + language: 'en_US', + value: 'keyword3' + }, + { + key: 'dc.title', + language: 'en_US', + value: 'Test PowerPoint Document' + }, + { + key: 'dc.type', + language: 'en_US', + value: 'text' + } + ], + owningCollection: { + self: { + _isScalar: true, + value: 'https://dspace7.4science.it/dspace-spring-rest/api/core/collections/1c11f3f1-ba1f-4f36-908a-3f1ea9a557eb', + scheduler: null + }, + requestPending: Observable.create((observer) => { + observer.next(false); + }), + responsePending: Observable.create((observer) => { + observer.next(false); + }), + isSuccessFul: Observable.create((observer) => { + observer.next(true); + }), + errorMessage: Observable.create((observer) => { + observer.next(''); + }), + statusCode: Observable.create((observer) => { + observer.next(202); + }), + pageInfo: Observable.create((observer) => { + observer.next({}); + }), + payload: Observable.create((observer) => { + observer.next([]); + }) + } +}) +/* tslint:enable:no-shadowed-variable */ diff --git a/src/app/shared/mocks/mock-metadata-service.ts b/src/app/shared/mocks/mock-metadata-service.ts new file mode 100644 index 0000000000..8456e92b92 --- /dev/null +++ b/src/app/shared/mocks/mock-metadata-service.ts @@ -0,0 +1,9 @@ + +export class MockMetadataService { + + // tslint:disable-next-line:no-empty + public listenForRouteChange(): void { + + } + +} diff --git a/src/app/shared/mocks/mock-router.ts b/src/app/shared/mocks/mock-router.ts new file mode 100644 index 0000000000..054c63d4c0 --- /dev/null +++ b/src/app/shared/mocks/mock-router.ts @@ -0,0 +1,4 @@ +export class MockRouter { + // noinspection TypeScriptUnresolvedFunction + navigate = jasmine.createSpy('navigate'); +} diff --git a/src/app/shared/mocks/mock-store.ts b/src/app/shared/mocks/mock-store.ts new file mode 100644 index 0000000000..c619b5aa77 --- /dev/null +++ b/src/app/shared/mocks/mock-store.ts @@ -0,0 +1,23 @@ +import { Action } from '@ngrx/store'; +import { Observable } from 'rxjs/Observable'; +import { BehaviorSubject } from 'rxjs/BehaviorSubject'; + +export class MockStore extends BehaviorSubject { + + constructor(private _initialState: T) { + super(_initialState); + } + + dispatch = (action: Action): void => { + console.info(); + } + + select = (pathOrMapFn: any): Observable => { + return Observable.of(this.getValue()); + } + + nextState(_newState: T) { + this.next(_newState); + } + +} diff --git a/src/app/shared/mocks/mock-translate-loader.ts b/src/app/shared/mocks/mock-translate-loader.ts new file mode 100644 index 0000000000..6e22066f8a --- /dev/null +++ b/src/app/shared/mocks/mock-translate-loader.ts @@ -0,0 +1,8 @@ +import { TranslateLoader } from '@ngx-translate/core'; +import { Observable } from 'rxjs/Observable'; + +export class MockTranslateLoader implements TranslateLoader { + getTranslation(lang: string): Observable { + return Observable.of({}); + } +} diff --git a/src/app/shared/pagination/pagination.component.spec.ts b/src/app/shared/pagination/pagination.component.spec.ts index 92d7ba693f..a4b9e5fcea 100644 --- a/src/app/shared/pagination/pagination.component.spec.ts +++ b/src/app/shared/pagination/pagination.component.spec.ts @@ -34,10 +34,10 @@ import Spy = jasmine.Spy; import { PaginationComponent } from './pagination.component'; import { PaginationComponentOptions } from './pagination-component-options.model'; -import { MockTranslateLoader } from '../testing/mock-translate-loader'; -import { HostWindowServiceStub } from '../testing/host-window-service-stub'; -import { ActivatedRouteStub } from '../testing/active-router-stub'; -import { RouterStub } from '../testing/router-stub'; +import { MockTranslateLoader } from '../mocks/mock-translate-loader'; +import { MockHostWindowService } from '../mocks/mock-host-window-service'; +import { MockActivatedRoute } from '../mocks/mock-active-router'; +import { MockRouter } from '../mocks/mock-router'; import { HostWindowService } from '../host-window.service'; import { EnumKeysPipe } from '../utils/enum-keys-pipe'; @@ -45,7 +45,7 @@ import { SortOptions } from '../../core/cache/models/sort-options.model'; import { GLOBAL_CONFIG, ENV_CONFIG } from '../../../config'; -function createTestComponent(html: string, type: { new (...args: any[]): T }): ComponentFixture { +function createTestComponent(html: string, type: { new(...args: any[]): T }): ComponentFixture { TestBed.overrideComponent(type, { set: { template: html } }); @@ -123,19 +123,19 @@ describe('Pagination component', () => { let testFixture: ComponentFixture; let de: DebugElement; let html; - let hostWindowServiceStub: HostWindowServiceStub; + let hostWindowServiceStub: MockHostWindowService; - let activatedRouteStub: ActivatedRouteStub; - let routerStub: RouterStub; + let activatedRouteStub: MockActivatedRoute; + let routerStub: MockRouter; // Define initial state and test state const _initialState = { width: 1600, height: 770 }; // async beforeEach beforeEach(async(() => { - activatedRouteStub = new ActivatedRouteStub(); - routerStub = new RouterStub(); - hostWindowServiceStub = new HostWindowServiceStub(_initialState.width); + activatedRouteStub = new MockActivatedRoute(); + routerStub = new MockRouter(); + hostWindowServiceStub = new MockHostWindowService(_initialState.width); TestBed.configureTestingModule({ imports: [ diff --git a/yarn.lock b/yarn.lock index 139c8b7107..91b2a787e2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,79 +2,79 @@ # yarn lockfile v1 -"@angular/animations@4.4.4": - version "4.4.4" - resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-4.4.4.tgz#a2f9353604347abe15df98292058842f52f08bc2" +"@angular/animations@4.4.5": + version "4.4.5" + resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-4.4.5.tgz#5a5a551d757e5a5560098f6f8535c102d93954d7" dependencies: tslib "^1.7.1" -"@angular/common@4.4.4": - version "4.4.4" - resolved "https://registry.yarnpkg.com/@angular/common/-/common-4.4.4.tgz#ae0a818aaa0c6a3f0901e7b80bd94e1c22eb9365" +"@angular/common@4.4.5": + version "4.4.5" + resolved "https://registry.yarnpkg.com/@angular/common/-/common-4.4.5.tgz#bd5179dc922adbf4c3ea6dfb19e73cb849ffdc37" dependencies: tslib "^1.7.1" -"@angular/compiler-cli@4.4.4": - version "4.4.4" - resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-4.4.4.tgz#063080a497d9175396825050222c717da184f6cf" +"@angular/compiler-cli@4.4.5": + version "4.4.5" + resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-4.4.5.tgz#61fa0336acd1a208c5f1c5c6d4df679e99953248" dependencies: - "@angular/tsc-wrapped" "4.4.4" + "@angular/tsc-wrapped" "4.4.5" minimist "^1.2.0" reflect-metadata "^0.1.2" -"@angular/compiler@4.4.4": - version "4.4.4" - resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-4.4.4.tgz#326eb0029d9a3541aaca124def9adc51c36f2b41" +"@angular/compiler@4.4.5": + version "4.4.5" + resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-4.4.5.tgz#8721a5910f2bb52f09e2d404cad264f35ede5902" dependencies: tslib "^1.7.1" -"@angular/core@4.4.4": - version "4.4.4" - resolved "https://registry.yarnpkg.com/@angular/core/-/core-4.4.4.tgz#bd37ecf54158f97489996c9386bd222f80a32f5c" +"@angular/core@4.4.5": + version "4.4.5" + resolved "https://registry.yarnpkg.com/@angular/core/-/core-4.4.5.tgz#54acbcbda11719f883c786a906974abeb132f1a0" dependencies: tslib "^1.7.1" -"@angular/forms@4.4.4": - version "4.4.4" - resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-4.4.4.tgz#4db3790509b6b10f1db8a7c1b7f52187cf64cfd4" +"@angular/forms@4.4.5": + version "4.4.5" + resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-4.4.5.tgz#e9552086232aab2ce1d08ef198b62204ea13c43b" dependencies: tslib "^1.7.1" -"@angular/http@4.4.4": - version "4.4.4" - resolved "https://registry.yarnpkg.com/@angular/http/-/http-4.4.4.tgz#667faf616bb624168eafae6ee92e5eba23a9d1f2" +"@angular/http@4.4.5": + version "4.4.5" + resolved "https://registry.yarnpkg.com/@angular/http/-/http-4.4.5.tgz#2c735ed842401fc2356419268e288dcf2396e84f" dependencies: tslib "^1.7.1" -"@angular/platform-browser-dynamic@4.4.4": - version "4.4.4" - resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-4.4.4.tgz#c3c9eb854a528556a07054127932e527fa932e14" +"@angular/platform-browser-dynamic@4.4.5": + version "4.4.5" + resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-4.4.5.tgz#774dbdc1d90f775dbf1e319f6ed42b260623b61f" dependencies: tslib "^1.7.1" -"@angular/platform-browser@4.4.4": - version "4.4.4" - resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-4.4.4.tgz#a3898e2e7ba9d84ffa0d47144c6971179c75aee6" +"@angular/platform-browser@4.4.5": + version "4.4.5" + resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-4.4.5.tgz#74eb91c0b758126f26d53ee56c7cf4668bd9cac5" dependencies: tslib "^1.7.1" -"@angular/platform-server@4.4.4": - version "4.4.4" - resolved "https://registry.yarnpkg.com/@angular/platform-server/-/platform-server-4.4.4.tgz#73ee41fa1cec8628fcc03174727b27cb0031b22a" +"@angular/platform-server@4.4.5": + version "4.4.5" + resolved "https://registry.yarnpkg.com/@angular/platform-server/-/platform-server-4.4.5.tgz#76f23b2c384ed7395dc1793cf85978883ba2cb50" dependencies: parse5 "^3.0.1" tslib "^1.7.1" xhr2 "^0.1.4" -"@angular/router@4.4.4": - version "4.4.4" - resolved "https://registry.yarnpkg.com/@angular/router/-/router-4.4.4.tgz#7be391096e843cb3e04f9f05d1d65a88df9bc7cf" +"@angular/router@4.4.5": + version "4.4.5" + resolved "https://registry.yarnpkg.com/@angular/router/-/router-4.4.5.tgz#f73130cf487d9a32cc1988afda59665f44a28a89" dependencies: tslib "^1.7.1" -"@angular/tsc-wrapped@4.4.4": - version "4.4.4" - resolved "https://registry.yarnpkg.com/@angular/tsc-wrapped/-/tsc-wrapped-4.4.4.tgz#9841821e55616b826ca160250fe85e15fc74ffc3" +"@angular/tsc-wrapped@4.4.5": + version "4.4.5" + resolved "https://registry.yarnpkg.com/@angular/tsc-wrapped/-/tsc-wrapped-4.4.5.tgz#30a0cbb43a663aa75dca984894be4813778ddc9c" dependencies: tsickle "^0.21.0" @@ -106,9 +106,9 @@ version "4.0.3" resolved "https://registry.yarnpkg.com/@ngrx/store/-/store-4.0.3.tgz#36abacdfa19bfb8506e40de80bae06050a1e15e9" -"@ngtools/webpack@1.7.2": - version "1.7.2" - resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-1.7.2.tgz#3fc4de01786dcc2f50d8cbaaa117311e56799977" +"@ngtools/webpack@1.7.4": + version "1.7.4" + resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-1.7.4.tgz#5015c47ebd339045dd89a1bef0497f4524d2c8ed" dependencies: enhanced-resolve "^3.1.0" loader-utils "^1.0.2" @@ -192,11 +192,7 @@ version "2.0.29" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-2.0.29.tgz#5002e14f75e2d71e564281df0431c8c1b4a2a36a" -"@types/node@*": - version "8.0.31" - resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.31.tgz#d9af61093cf4bfc9f066ca34de0175012cfb0ce9" - -"@types/node@8.0.34": +"@types/node@*", "@types/node@8.0.34": version "8.0.34" resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.34.tgz#55f801fa2ddb2a40dd6dfc15ecfe1dde9c129fe9" @@ -233,11 +229,7 @@ version "1.6.29" resolved "https://registry.yarnpkg.com/@types/webfontloader/-/webfontloader-1.6.29.tgz#c6b5f6eb8ca31d0aae6b02b6c1300349dd93ea8e" -abbrev@1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" - -abbrev@1.0.x: +abbrev@1, abbrev@1.0.x: version "1.0.9" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135" @@ -554,7 +546,7 @@ async@2.0.1: dependencies: lodash "^4.8.0" -async@2.4.1: +async@2.4.1, async@^2.0.0, async@^2.1.2, async@^2.1.5: version "2.4.1" resolved "https://registry.yarnpkg.com/async/-/async-2.4.1.tgz#62a56b279c98a11d0987096a01cc3eeb8eb7bbd7" dependencies: @@ -564,12 +556,6 @@ async@^0.9.0, async@~0.9.0: version "0.9.2" resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d" -async@^2.0.0, async@^2.1.2, async@^2.1.5: - version "2.5.0" - resolved "https://registry.yarnpkg.com/async/-/async-2.5.0.tgz#843190fd6b7357a0b9e1c956edddd5ec8462b54d" - dependencies: - lodash "^4.14.0" - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -582,7 +568,7 @@ atob@~1.1.0: version "1.1.3" resolved "https://registry.yarnpkg.com/atob/-/atob-1.1.3.tgz#95f13629b12c3a51a5d215abdce2aa9f32f80773" -autoprefixer@7.1.5: +autoprefixer@7.1.5, autoprefixer@^7.1.1: version "7.1.5" resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-7.1.5.tgz#d65d14b83c7cd1dd7bc801daa00557addf5a06b2" dependencies: @@ -604,17 +590,6 @@ autoprefixer@^6.3.1: postcss "^5.2.16" postcss-value-parser "^3.2.3" -autoprefixer@^7.1.1: - version "7.1.4" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-7.1.4.tgz#960847dbaa4016bc8e8e52ec891cbf8f1257a748" - dependencies: - browserslist "^2.4.0" - caniuse-lite "^1.0.30000726" - normalize-range "^0.1.2" - num2fraction "^1.2.2" - postcss "^6.0.11" - postcss-value-parser "^3.2.3" - awesome-typescript-loader@3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/awesome-typescript-loader/-/awesome-typescript-loader-3.2.3.tgz#aa2119b7c808a031e2b28945b031450a8975367f" @@ -807,11 +782,7 @@ blocking-proxy@0.0.5: dependencies: minimist "^1.2.0" -bluebird@^3.3.0, bluebird@^3.4.7: - version "3.5.0" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c" - -bluebird@^3.5.1: +bluebird@^3.3.0, bluebird@^3.4.7, bluebird@^3.5.1: version "3.5.1" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9" @@ -989,14 +960,7 @@ browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6: caniuse-db "^1.0.30000639" electron-to-chromium "^1.2.7" -browserslist@^2.0.0, browserslist@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-2.4.0.tgz#693ee93d01e66468a6348da5498e011f578f87f8" - dependencies: - caniuse-lite "^1.0.30000718" - electron-to-chromium "^1.3.18" - -browserslist@^2.5.0: +browserslist@^2.0.0, browserslist@^2.5.0: version "2.5.1" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-2.5.1.tgz#68e4bc536bbcc6086d62843a2ffccea8396821c6" dependencies: @@ -1109,13 +1073,13 @@ caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639: version "1.0.30000740" resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000740.tgz#03fcaaa176e3ed075895f72d46c1a12149bbeac9" -caniuse-lite@1.0.30000746, caniuse-lite@^1.0.30000744: +caniuse-lite@1.0.30000746: version "1.0.30000746" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000746.tgz#c64f95a3925cfd30207a308ed76c1ae96ea09ea0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000718, caniuse-lite@^1.0.30000726: - version "1.0.30000740" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000740.tgz#f2c4c04d6564eb812e61006841700ad557f6f973" +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000744: + version "1.0.30000745" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000745.tgz#20d6fede1157a4935133502946fc7e0e6b880da5" capture-stack-trace@^1.0.0: version "1.0.0" @@ -1550,14 +1514,10 @@ crc32-stream@^2.0.0: crc "^3.4.4" readable-stream "^2.0.0" -crc@3.4.4: +crc@3.4.4, crc@^3.4.4: version "3.4.4" resolved "https://registry.yarnpkg.com/crc/-/crc-3.4.4.tgz#9da1e980e3bd44fc5c93bf5ab3da3378d85e466b" -crc@^3.4.4: - version "3.5.0" - resolved "https://registry.yarnpkg.com/crc/-/crc-3.5.0.tgz#98b8ba7d489665ba3979f59b21381374101a1964" - create-ecdh@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.0.tgz#888c723596cdf7612f6498233eebd7a35301737d" @@ -2056,14 +2016,10 @@ ejs@^2.5.6: version "2.5.7" resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.7.tgz#cc872c168880ae3c7189762fd5ffc00896c9518a" -electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.18: +electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.24: version "1.3.24" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.24.tgz#9b7b88bb05ceb9fa016a177833cc2dde388f21b6" -electron-to-chromium@^1.3.24: - version "1.3.26" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.26.tgz#996427294861a74d9c7c82b9260ea301e8c02d66" - elliptic@^6.0.0: version "6.4.0" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.0.tgz#cac9af8762c85836187003c8dfe193e5e2eae5df" @@ -2432,7 +2388,7 @@ express-session@1.15.6: uid-safe "~2.1.5" utils-merge "1.0.1" -express@4.16.2: +express@4.16.2, express@^4.13.3, express@^4.15.2: version "4.16.2" resolved "https://registry.yarnpkg.com/express/-/express-4.16.2.tgz#e35c6dfe2d64b7dca0a5cd4f21781be3299e076c" dependencies: @@ -2467,41 +2423,6 @@ express@4.16.2: utils-merge "1.0.1" vary "~1.1.2" -express@^4.13.3, express@^4.15.2: - version "4.16.0" - resolved "https://registry.yarnpkg.com/express/-/express-4.16.0.tgz#b519638e4eb58e7178c81b498ef22f798cb2e255" - dependencies: - accepts "~1.3.4" - array-flatten "1.1.1" - body-parser "1.18.2" - content-disposition "0.5.2" - content-type "~1.0.4" - cookie "0.3.1" - cookie-signature "1.0.6" - debug "2.6.9" - depd "~1.1.1" - encodeurl "~1.0.1" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "1.1.0" - fresh "0.5.2" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "~2.3.0" - parseurl "~1.3.2" - path-to-regexp "0.1.7" - proxy-addr "~2.0.2" - qs "6.5.1" - range-parser "~1.2.0" - safe-buffer "5.1.1" - send "0.16.0" - serve-static "1.13.0" - setprototypeof "1.1.0" - statuses "~1.3.1" - type-is "~1.6.15" - utils-merge "1.0.1" - vary "~1.1.2" - extend-shallow@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" @@ -5009,7 +4930,7 @@ os-locale@^2.0.0: lcid "^1.0.0" mem "^1.1.0" -os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.1, os-tmpdir@~1.0.2: +os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" @@ -5808,7 +5729,7 @@ postcss-zindex@^2.0.1: postcss "^5.0.4" uniqs "^2.0.0" -postcss@6.0.13, postcss@^6.0.13: +postcss@6.0.13, postcss@^6.0.0, postcss@^6.0.1, postcss@^6.0.11, postcss@^6.0.13, postcss@^6.0.3, postcss@^6.0.5, postcss@^6.0.6, postcss@^6.0.8: version "6.0.13" resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.13.tgz#b9ecab4ee00c89db3ec931145bd9590bbf3f125f" dependencies: @@ -5825,14 +5746,6 @@ postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0 source-map "^0.5.6" supports-color "^3.2.3" -postcss@^6.0.0, postcss@^6.0.1, postcss@^6.0.11, postcss@^6.0.3, postcss@^6.0.5, postcss@^6.0.6, postcss@^6.0.8: - version "6.0.12" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.12.tgz#6b0155089d2d212f7bd6a0cecd4c58c007403535" - dependencies: - chalk "^2.1.0" - source-map "^0.5.7" - supports-color "^4.4.0" - prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" @@ -5950,14 +5863,10 @@ punycode@^1.2.4, punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" -q@1.4.1: +q@1.4.1, q@^1.1.2, q@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/q/-/q-1.4.1.tgz#55705bcd93c5f3673530c2c2cbc0c2b3addc286e" -q@^1.1.2, q@^1.4.1: - version "1.5.0" - resolved "https://registry.yarnpkg.com/q/-/q-1.5.0.tgz#dd01bac9d06d30e6f219aecb8253ee9ebdc308f1" - qjobs@^1.1.4: version "1.1.5" resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.1.5.tgz#659de9f2cf8dcc27a1481276f205377272382e73" @@ -6089,7 +5998,7 @@ readable-stream@1.0, readable-stream@~1.0.2: isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.2.6, readable-stream@^2.2.9: +readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.2.2, readable-stream@^2.2.6, readable-stream@^2.2.9: version "2.3.3" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" dependencies: @@ -6639,24 +6548,6 @@ semver@~5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" -send@0.16.0: - version "0.16.0" - resolved "https://registry.yarnpkg.com/send/-/send-0.16.0.tgz#16338dbb9a2ede4ad57b48420ec3b82d8e80a57b" - dependencies: - debug "2.6.9" - depd "~1.1.1" - destroy "~1.0.4" - encodeurl "~1.0.1" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "~1.6.2" - mime "1.4.1" - ms "2.0.0" - on-finished "~2.3.0" - range-parser "~1.2.0" - statuses "~1.3.1" - send@0.16.1: version "0.16.1" resolved "https://registry.yarnpkg.com/send/-/send-0.16.1.tgz#a70e1ca21d1382c11d0d9f6231deb281080d7ab3" @@ -6687,15 +6578,6 @@ serve-index@^1.7.2: mime-types "~2.1.17" parseurl "~1.3.2" -serve-static@1.13.0: - version "1.13.0" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.0.tgz#810c91db800e94ba287eae6b4e06caab9fdc16f1" - dependencies: - encodeurl "~1.0.1" - escape-html "~1.0.3" - parseurl "~1.3.2" - send "0.16.0" - serve-static@1.13.1: version "1.13.1" resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.1.tgz#4c57d53404a761d8f2e7c1e8a18a47dbf278a719" @@ -6953,7 +6835,7 @@ source-map-url@~0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.3.0.tgz#7ecaf13b57bcd09da8a40c5d269db33799d4aaf9" -source-map@0.5.x, source-map@^0.5.1, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.1, source-map@~0.5.3: +source-map@0.5.x, source-map@^0.5.1, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1, source-map@~0.5.3: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" @@ -7038,11 +6920,7 @@ split@0.3: dependencies: through "2" -sprintf-js@^1.0.3: - version "1.1.1" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.1.tgz#36be78320afe5801f6cea3ee78b6e5aab940ea0c" - -sprintf-js@~1.0.2: +sprintf-js@^1.0.3, sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -7266,20 +7144,13 @@ throttleit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-1.0.0.tgz#9e785836daf46743145a5984b6268d828528ac6c" -through2@2.0.1: +through2@2.0.1, through2@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.1.tgz#384e75314d49f32de12eebb8136b8eb6b5d59da9" dependencies: readable-stream "~2.0.0" xtend "~4.0.0" -through2@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be" - dependencies: - readable-stream "^2.1.5" - xtend "~4.0.1" - through@2, through@X.X.X, through@~2.3, through@~2.3.1: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" @@ -7320,18 +7191,12 @@ tmp@0.0.30: dependencies: os-tmpdir "~1.0.1" -tmp@0.0.31: +tmp@0.0.31, tmp@0.0.x: version "0.0.31" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.31.tgz#8f38ab9438e17315e5dbd8b3657e8bfb277ae4a7" dependencies: os-tmpdir "~1.0.1" -tmp@0.0.x: - version "0.0.33" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" - dependencies: - os-tmpdir "~1.0.2" - to-array@0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890" @@ -8045,20 +7910,13 @@ write-file-atomic@^2.0.0: imurmurhash "^0.1.4" signal-exit "^3.0.2" -ws@1.1.2: +ws@1.1.2, ws@^1.0.1: version "1.1.2" resolved "https://registry.yarnpkg.com/ws/-/ws-1.1.2.tgz#8a244fa052401e08c9886cf44a85189e1fd4067f" dependencies: options ">=0.0.5" ultron "1.0.x" -ws@^1.0.1: - version "1.1.4" - resolved "https://registry.yarnpkg.com/ws/-/ws-1.1.4.tgz#57f40d036832e5f5055662a397c4de76ed66bf61" - dependencies: - options ">=0.0.5" - ultron "1.0.x" - ws@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/ws/-/ws-2.3.1.tgz#6b94b3e447cb6a363f785eaf94af6359e8e81c80" @@ -8104,7 +7962,7 @@ xmlhttprequest-ssl@1.5.3: version "1.5.3" resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.3.tgz#185a888c04eca46c3e4070d99f7b49de3528992d" -xtend@^4.0.0, xtend@~4.0.0, xtend@~4.0.1: +xtend@^4.0.0, xtend@~4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"