diff --git a/e2e/search-page/search-page.e2e-spec.ts b/e2e/search-page/search-page.e2e-spec.ts new file mode 100644 index 0000000000..cb9c92a87b --- /dev/null +++ b/e2e/search-page/search-page.e2e-spec.ts @@ -0,0 +1,53 @@ +import { ProtractorPage } from './search-page.po'; +import { browser } from 'protractor'; +import { promise } from 'selenium-webdriver'; + +describe('protractor SearchPage', () => { + let page: ProtractorPage; + + beforeEach(() => { + page = new ProtractorPage(); + }); + + it('should contain query value when navigating to page with query parameter', () => { + const queryString = 'Interesting query string'; + page.navigateToSearchWithQueryParameter(queryString); + page.getCurrentQuery().then((query: string) => { + expect(query).toEqual(queryString); + }); + }); + + it('should have right scope selected when navigating to page with scope parameter', () => { + const scope: promise.Promise = page.getRandomScopeOption(); + scope.then((scopeString: string) => { + page.navigateToSearchWithScopeParameter(scopeString); + page.getCurrentScope().then((s: string) => { + expect(s).toEqual(scopeString); + }); + }); + }); + + it('should redirect to the correct url when scope was set and submit button was triggered', () => { + const scope: promise.Promise = page.getRandomScopeOption(); + scope.then((scopeString: string) => { + page.setCurrentScope(scopeString); + page.submitSearchForm(); + browser.wait(() => { + return browser.getCurrentUrl().then((url: string) => { + return url.indexOf('scope=' + encodeURI(scopeString)) !== -1; + }); + }); + }); + }); + + it('should redirect to the correct url when query was set and submit button was triggered', () => { + const queryString = 'Another interesting query string'; + page.setCurrentQuery(queryString); + page.submitSearchForm(); + browser.wait(() => { + return browser.getCurrentUrl().then((url: string) => { + return url.indexOf('query=' + encodeURI(queryString)) !== -1; + }); + }); + }); +}); diff --git a/e2e/search-page/search-page.po.ts b/e2e/search-page/search-page.po.ts new file mode 100644 index 0000000000..b1a17ee150 --- /dev/null +++ b/e2e/search-page/search-page.po.ts @@ -0,0 +1,47 @@ +import { browser, element, by } from 'protractor'; +import { promise } from 'selenium-webdriver'; + +export class ProtractorPage { + SEARCH = '/search'; + + navigateToSearch() { + return browser.get(this.SEARCH); + } + + navigateToSearchWithQueryParameter(query: string) { + return browser.get(this.SEARCH + '?query=' + query); + } + + navigateToSearchWithScopeParameter(scope: string) { + return browser.get(this.SEARCH + '?scope=' + scope); + } + + getCurrentScope(): promise.Promise { + return element(by.tagName('select')).getAttribute('value'); + } + + getCurrentQuery(): promise.Promise { + return element(by.tagName('input')).getAttribute('value'); + } + + setCurrentScope(scope: string) { + element(by.css('option[value="' + scope + '"]')).click(); + } + + setCurrentQuery(query: string) { + element(by.css('input[name="query"]')).sendKeys(query); + } + + submitSearchForm() { + element(by.css('button.search-button')).click(); + } + + getRandomScopeOption(): promise.Promise { + const options = element(by.css('select[name="scope"]')).all(by.tagName('option')); + return options.count().then((c: number) => { + const index: number = Math.floor(Math.random() * (c - 1)); + return options.get(index + 1).getAttribute('value'); + }); + } + +} diff --git a/karma.conf.js b/karma.conf.js index 8f51e61344..e43191d8ee 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -27,18 +27,18 @@ module.exports = function (config) { frameworks: ['jasmine'], plugins: [ - require('karma-webpack'), - require('karma-jasmine'), + require("istanbul-instrumenter-loader"), require('karma-chrome-launcher'), - require('karma-phantomjs-launcher'), - require('karma-webdriver-launcher'), require('karma-coverage'), - require('karma-remap-coverage'), + require("karma-istanbul-preprocessor"), + require('karma-jasmine'), require('karma-mocha-reporter'), + require('karma-phantomjs-launcher'), + require('karma-remap-coverage'), require('karma-remap-istanbul'), require('karma-sourcemap-loader'), - require("istanbul-instrumenter-loader"), - require("karma-istanbul-preprocessor") + require('karma-webdriver-launcher'), + require('karma-webpack') ], // list of files to exclude @@ -59,7 +59,11 @@ module.exports = function (config) { * available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor */ preprocessors: { - './spec-bundle.js': ['istanbul', 'webpack', 'sourcemap'] + './spec-bundle.js': [ + 'istanbul', + 'webpack', + 'sourcemap' + ] }, // Webpack Config at ./webpack.test.js @@ -79,9 +83,9 @@ module.exports = function (config) { remapIstanbulReporter: { remapOptions: {}, //additional remap options reports: { - json: 'coverage/coverage.json', - lcovonly: 'coverage/lcov.info', - html: 'coverage/html/', + json: './coverage/coverage.json', + lcovonly: './coverage/lcov.info', + html: './coverage/html/', } }, @@ -111,7 +115,12 @@ module.exports = function (config) { * possible values: 'dots', 'progress' * available reporters: https://npmjs.org/browse/keyword/karma-reporter */ - reporters: ['mocha', 'coverage', 'remap-coverage', 'karma-remap-istanbul'], + reporters: [ + 'mocha', + 'coverage', + 'remap-coverage', + 'karma-remap-istanbul' + ], // Karma web server port port: 9876, diff --git a/package.json b/package.json index 2f0d024f89..d9b283c526 100644 --- a/package.json +++ b/package.json @@ -12,14 +12,15 @@ }, "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": "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: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: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", @@ -113,7 +114,7 @@ "@angular/compiler": "4.4.4", "@angular/compiler-cli": "4.4.4", "@ngrx/store-devtools": "4.0.0", - "@ngtools/webpack": "1.7.3", + "@ngtools/webpack": "1.7.2", "@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 c605d76961..3388190295 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -78,5 +78,25 @@ "results": { "title": "Search Results" } + }, + "loading": { + "default": "Loading...", + "top-level-communities": "Loading top level communities...", + "community": "Loading community...", + "collection": "Loading collection...", + "sub-collections": "Loading sub-collections...", + "items": "Loading items...", + "item": "Loading item...", + "objects": "Loading..." + }, + "error": { + "default": "Error", + "top-level-communities": "Error fetching top level communities", + "community": "Error fetching community", + "collection": "Error fetching collection", + "sub-collections": "Error fetching sub-collections", + "items": "Error fetching items", + "item": "Error fetching item", + "objects": "Error fetching objects" } } diff --git a/rollup.config.js b/rollup.config.js index a47b46dd37..caa63ac03e 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,20 +1,22 @@ -import nodeResolve from 'rollup-plugin-node-resolve' -import commonjs from 'rollup-plugin-commonjs'; -import uglify from 'rollup-plugin-uglify' - -export default { - entry: 'dist/client.js', - dest: 'dist/client.js', - sourceMap: false, - format: 'iife', - plugins: [ - nodeResolve({ - jsnext: true, - module: true - }), - commonjs({ - include: 'node_modules/rxjs/**' - }), - uglify() - ] -} +import nodeResolve from 'rollup-plugin-node-resolve' +import commonjs from 'rollup-plugin-commonjs'; +import uglify from 'rollup-plugin-uglify' + +export default { + input: 'dist/client.js', + output: { + file: 'dist/client.js', + format: 'iife', + }, + sourceMap: false, + plugins: [ + nodeResolve({ + jsnext: true, + module: true + }), + commonjs({ + include: 'node_modules/rxjs/**' + }), + uglify() + ] +} diff --git a/spec-bundle.js b/spec-bundle.js index 36026d530f..b9df9bec5e 100644 --- a/spec-bundle.js +++ b/spec-bundle.js @@ -38,25 +38,11 @@ testing.TestBed.initTestEnvironment( browser.platformBrowserDynamicTesting() ); -/* - * Ok, this is kinda crazy. We can use the context method on - * require that webpack created in order to tell webpack - * what files we actually want to require or import. - * Below, context will be a function/object with file names as keys. - * Using that regex we are saying look in ../src then find - * any file that ends with spec.ts and get its path. By passing in true - * we say do this recursively - */ -var testContext = require.context('./src', true, /\.spec\.ts/); +var tests = require.context('./src', true, /\.spec\.ts$/); -/* - * get all the files, for each file, call the context function - * that will require the file and load it up here. Context will - * loop and require those spec files here - */ -function requireAll(requireContext) { - return requireContext.keys().map(requireContext); -} +tests.keys().forEach(tests); -// requires and returns all modules that match -var modules = requireAll(testContext); +// includes all modules into test coverage +const modules = require.context('./src/app', true, /\.module\.ts$/); + +modules.keys().forEach(modules); diff --git a/src/app/+collection-page/collection-page.component.html b/src/app/+collection-page/collection-page.component.html index b1aae17f7c..e385de08b4 100644 --- a/src/app/+collection-page/collection-page.component.html +++ b/src/app/+collection-page/collection-page.component.html @@ -1,40 +1,50 @@
-
- - - - - - - - - - - - - - - - - - +
+
+ + + + + + + + + + + + + + + + + + +
+ +
-
+

{{'collection.page.browse.recent.head' | translate}}

- + +
+ +
diff --git a/src/app/+collection-page/collection-page.component.ts b/src/app/+collection-page/collection-page.component.ts index e0043f2845..a02ebf4d07 100644 --- a/src/app/+collection-page/collection-page.component.ts +++ b/src/app/+collection-page/collection-page.component.ts @@ -1,28 +1,30 @@ -import { - ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, - OnInit -} from '@angular/core'; -import { ActivatedRoute, Params } from '@angular/router'; +import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { Observable } from 'rxjs/Observable'; import { Subscription } from 'rxjs/Subscription'; - -import { Collection } from '../core/shared/collection.model'; -import { Bitstream } from '../core/shared/bitstream.model'; -import { RemoteData } from '../core/data/remote-data'; +import { SortOptions } from '../core/cache/models/sort-options.model'; import { CollectionDataService } from '../core/data/collection-data.service'; import { ItemDataService } from '../core/data/item-data.service'; +import { RemoteData } from '../core/data/remote-data'; +import { Bitstream } from '../core/shared/bitstream.model'; + +import { Collection } from '../core/shared/collection.model'; import { Item } from '../core/shared/item.model'; -import { SortOptions, SortDirection } from '../core/cache/models/sort-options.model'; + +import { fadeIn, fadeInOut } from '../shared/animations/fade'; +import { hasValue, isNotEmpty } from '../shared/empty.util'; import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; -import { hasValue, isNotEmpty, isUndefined } from '../shared/empty.util'; -import { PageInfo } from '../core/shared/page-info.model'; -import { Observable } from 'rxjs/Observable'; @Component({ selector: 'ds-collection-page', styleUrls: ['./collection-page.component.scss'], templateUrl: './collection-page.component.html', - changeDetection: ChangeDetectionStrategy.OnPush + changeDetection: ChangeDetectionStrategy.OnPush, + animations: [ + fadeIn, + fadeInOut + ] }) export class CollectionPageComponent implements OnInit, OnDestroy { collectionData: RemoteData; diff --git a/src/app/+community-page/community-page.component.html b/src/app/+community-page/community-page.component.html index 4f02c885e6..3935ffccae 100644 --- a/src/app/+community-page/community-page.component.html +++ b/src/app/+community-page/community-page.component.html @@ -1,26 +1,30 @@ -
- - - - +
+ + + + - - - + + - - - + + - - - + + - - + + +
+ + diff --git a/src/app/+community-page/community-page.component.ts b/src/app/+community-page/community-page.component.ts index 4ed516637d..004eca8f9d 100644 --- a/src/app/+community-page/community-page.component.ts +++ b/src/app/+community-page/community-page.component.ts @@ -2,18 +2,21 @@ import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/ import { ActivatedRoute, Params } from '@angular/router'; import { Subscription } from 'rxjs/Subscription'; +import { CommunityDataService } from '../core/data/community-data.service'; +import { RemoteData } from '../core/data/remote-data'; +import { Bitstream } from '../core/shared/bitstream.model'; import { Community } from '../core/shared/community.model'; -import { Bitstream } from '../core/shared/bitstream.model'; -import { RemoteData } from '../core/data/remote-data'; -import { CommunityDataService } from '../core/data/community-data.service'; + +import { fadeInOut } from '../shared/animations/fade'; import { hasValue } from '../shared/empty.util'; @Component({ selector: 'ds-community-page', styleUrls: ['./community-page.component.scss'], templateUrl: './community-page.component.html', - changeDetection: ChangeDetectionStrategy.OnPush + changeDetection: ChangeDetectionStrategy.OnPush, + animations: [fadeInOut] }) export class CommunityPageComponent implements OnInit, OnDestroy { communityData: RemoteData; diff --git a/src/app/+community-page/sub-collection-list/community-page-sub-collection-list.component.html b/src/app/+community-page/sub-collection-list/community-page-sub-collection-list.component.html index f1f05a0467..31dc2cd326 100644 --- a/src/app/+community-page/sub-collection-list/community-page-sub-collection-list.component.html +++ b/src/app/+community-page/sub-collection-list/community-page-sub-collection-list.component.html @@ -1,4 +1,4 @@ -
+

{{'community.sub-collection-list.head' | translate}}

  • @@ -9,3 +9,5 @@
+ + diff --git a/src/app/+community-page/sub-collection-list/community-page-sub-collection-list.component.ts b/src/app/+community-page/sub-collection-list/community-page-sub-collection-list.component.ts index caa764c39f..618890a60c 100644 --- a/src/app/+community-page/sub-collection-list/community-page-sub-collection-list.component.ts +++ b/src/app/+community-page/sub-collection-list/community-page-sub-collection-list.component.ts @@ -4,10 +4,13 @@ import { CollectionDataService } from '../../core/data/collection-data.service'; import { RemoteData } from '../../core/data/remote-data'; import { Collection } from '../../core/shared/collection.model'; +import { fadeIn } from '../../shared/animations/fade'; + @Component({ selector: 'ds-community-page-sub-collection-list', styleUrls: ['./community-page-sub-collection-list.component.scss'], templateUrl: './community-page-sub-collection-list.component.html', + animations:[fadeIn] }) export class CommunityPageSubCollectionListComponent implements OnInit { subCollections: RemoteData; diff --git a/src/app/+home/home-news/home-news.component.html b/src/app/+home-page/home-news/home-news.component.html similarity index 100% rename from src/app/+home/home-news/home-news.component.html rename to src/app/+home-page/home-news/home-news.component.html diff --git a/src/app/+home/home-news/home-news.component.scss b/src/app/+home-page/home-news/home-news.component.scss similarity index 100% rename from src/app/+home/home-news/home-news.component.scss rename to src/app/+home-page/home-news/home-news.component.scss diff --git a/src/app/+home/home-news/home-news.component.ts b/src/app/+home-page/home-news/home-news.component.ts similarity index 100% rename from src/app/+home/home-news/home-news.component.ts rename to src/app/+home-page/home-news/home-news.component.ts diff --git a/src/app/+home-page/home-page-routing.module.ts b/src/app/+home-page/home-page-routing.module.ts new file mode 100644 index 0000000000..e68b633a6d --- /dev/null +++ b/src/app/+home-page/home-page-routing.module.ts @@ -0,0 +1,13 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +import { HomePageComponent } from './home-page.component'; + +@NgModule({ + imports: [ + RouterModule.forChild([ + { path: '', component: HomePageComponent, pathMatch: 'full' } + ]) + ] +}) +export class HomePageRoutingModule { } diff --git a/src/app/+home/home.component.html b/src/app/+home-page/home-page.component.html similarity index 100% rename from src/app/+home/home.component.html rename to src/app/+home-page/home-page.component.html diff --git a/src/app/+home/home.component.scss b/src/app/+home-page/home-page.component.scss similarity index 100% rename from src/app/+home/home.component.scss rename to src/app/+home-page/home-page.component.scss diff --git a/src/app/+home-page/home-page.component.ts b/src/app/+home-page/home-page.component.ts new file mode 100644 index 0000000000..ad25ec0155 --- /dev/null +++ b/src/app/+home-page/home-page.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ds-home-page', + styleUrls: ['./home-page.component.scss'], + templateUrl: './home-page.component.html' +}) +export class HomePageComponent { + +} diff --git a/src/app/+home/home.module.ts b/src/app/+home-page/home-page.module.ts similarity index 70% rename from src/app/+home/home.module.ts rename to src/app/+home-page/home-page.module.ts index 7a68629deb..0a513260cd 100644 --- a/src/app/+home/home.module.ts +++ b/src/app/+home-page/home-page.module.ts @@ -1,24 +1,24 @@ -import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; import { SharedModule } from '../shared/shared.module'; - -import { HomeComponent } from './home.component'; -import { HomeRoutingModule } from './home-routing.module'; -import { TopLevelCommunityListComponent } from './top-level-community-list/top-level-community-list.component'; import { HomeNewsComponent } from './home-news/home-news.component'; +import { HomePageRoutingModule } from './home-page-routing.module'; + +import { HomePageComponent } from './home-page.component'; +import { TopLevelCommunityListComponent } from './top-level-community-list/top-level-community-list.component'; @NgModule({ imports: [ CommonModule, SharedModule, - HomeRoutingModule + HomePageRoutingModule ], declarations: [ - HomeComponent, + HomePageComponent, TopLevelCommunityListComponent, HomeNewsComponent ] }) -export class HomeModule { +export class HomePageModule { } diff --git a/src/app/+home-page/top-level-community-list/top-level-community-list.component.html b/src/app/+home-page/top-level-community-list/top-level-community-list.component.html new file mode 100644 index 0000000000..a5bd6c5c5d --- /dev/null +++ b/src/app/+home-page/top-level-community-list/top-level-community-list.component.html @@ -0,0 +1,13 @@ +
+

{{'home.top-level-communities.head' | translate}}

+

{{'home.top-level-communities.help' | translate}}

+ + +
+ + \ No newline at end of file diff --git a/src/app/+home/top-level-community-list/top-level-community-list.component.scss b/src/app/+home-page/top-level-community-list/top-level-community-list.component.scss similarity index 100% rename from src/app/+home/top-level-community-list/top-level-community-list.component.scss rename to src/app/+home-page/top-level-community-list/top-level-community-list.component.scss diff --git a/src/app/+home/top-level-community-list/top-level-community-list.component.ts b/src/app/+home-page/top-level-community-list/top-level-community-list.component.ts similarity index 82% rename from src/app/+home/top-level-community-list/top-level-community-list.component.ts rename to src/app/+home-page/top-level-community-list/top-level-community-list.component.ts index 783265d110..4986ff9ec9 100644 --- a/src/app/+home/top-level-community-list/top-level-community-list.component.ts +++ b/src/app/+home-page/top-level-community-list/top-level-community-list.component.ts @@ -1,18 +1,20 @@ -import { Component, OnInit, ChangeDetectorRef, ChangeDetectionStrategy } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { SortOptions } from '../../core/cache/models/sort-options.model'; +import { CommunityDataService } from '../../core/data/community-data.service'; import { RemoteData } from '../../core/data/remote-data'; -import { CommunityDataService } from '../../core/data/community-data.service'; import { Community } from '../../core/shared/community.model'; + +import { fadeInOut } from '../../shared/animations/fade'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; -import { SortOptions, SortDirection } from '../../core/cache/models/sort-options.model'; -import { ActivatedRoute } from '@angular/router'; @Component({ selector: 'ds-top-level-community-list', styleUrls: ['./top-level-community-list.component.scss'], templateUrl: './top-level-community-list.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + animations: [fadeInOut] }) - export class TopLevelCommunityListComponent { topLevelCommunities: RemoteData; config: PaginationComponentOptions; @@ -21,8 +23,7 @@ export class TopLevelCommunityListComponent { constructor(private cds: CommunityDataService) { this.config = new PaginationComponentOptions(); this.config.id = 'top-level-pagination'; - this.config.pageSizeOptions = [4]; - this.config.pageSize = 4; + this.config.pageSize = 5; this.config.currentPage = 1; this.sortConfig = new SortOptions(); diff --git a/src/app/+home/home-routing.module.ts b/src/app/+home/home-routing.module.ts deleted file mode 100644 index 9bc4619f74..0000000000 --- a/src/app/+home/home-routing.module.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { NgModule } from '@angular/core'; -import { RouterModule } from '@angular/router'; - -import { HomeComponent } from './home.component'; - -@NgModule({ - imports: [ - RouterModule.forChild([ - { path: '', component: HomeComponent, pathMatch: 'full' } - ]) - ] -}) -export class HomeRoutingModule { } diff --git a/src/app/+home/home.component.ts b/src/app/+home/home.component.ts deleted file mode 100644 index d9127a13a6..0000000000 --- a/src/app/+home/home.component.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core'; - -@Component({ - selector: 'ds-home', - styleUrls: ['./home.component.scss'], - templateUrl: './home.component.html', - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class HomeComponent { - -} diff --git a/src/app/+home/top-level-community-list/top-level-community-list.component.html b/src/app/+home/top-level-community-list/top-level-community-list.component.html deleted file mode 100644 index 772eb66012..0000000000 --- a/src/app/+home/top-level-community-list/top-level-community-list.component.html +++ /dev/null @@ -1,6 +0,0 @@ -
-

{{'home.top-level-communities.head' | translate}}

-

{{'home.top-level-communities.help' | translate}}

- -
diff --git a/src/app/+item-page/full/full-item-page.component.html b/src/app/+item-page/full/full-item-page.component.html index 59511d7d9d..7b25f88753 100644 --- a/src/app/+item-page/full/full-item-page.component.html +++ b/src/app/+item-page/full/full-item-page.component.html @@ -1,25 +1,23 @@ -
- - +
+
+ - - - - - - - - + + + + + + +
{{metadatum.key}}{{metadatum.value}}{{metadatum.language}}
{{metadatum.key}}{{metadatum.value}}{{metadatum.language}}
- - - - - - + + +
+ + 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 c952954235..337c598021 100644 --- a/src/app/+item-page/full/full-item-page.component.ts +++ b/src/app/+item-page/full/full-item-page.component.ts @@ -1,4 +1,6 @@ import { Component, OnInit } from '@angular/core'; +import { animate, state, transition, trigger, style, keyframes } from '@angular/animations'; + import { Observable } from 'rxjs/Observable'; import { ItemPageComponent } from '../simple/item-page.component'; @@ -8,6 +10,8 @@ import { ActivatedRoute } from '@angular/router'; import { RemoteData } from '../../core/data/remote-data'; import { Item } from '../../core/shared/item.model'; +import { fadeInOut } from '../../shared/animations/fade'; + /** * This component renders a simple item page. * The route parameter 'id' is used to request the item it represents. @@ -18,6 +22,7 @@ import { Item } from '../../core/shared/item.model'; selector: 'ds-full-item-page', styleUrls: ['./full-item-page.component.scss'], templateUrl: './full-item-page.component.html', + animations: [fadeInOut] }) export class FullItemPageComponent extends ItemPageComponent implements OnInit { diff --git a/src/app/+item-page/simple/item-page.component.html b/src/app/+item-page/simple/item-page.component.html index 94ab99482f..10ceec2a6f 100644 --- a/src/app/+item-page/simple/item-page.component.html +++ b/src/app/+item-page/simple/item-page.component.html @@ -1,26 +1,27 @@ -
- +
+
+
-
- - - - - - - +
+ + + + + + +
+
+
+ + diff --git a/src/app/+item-page/simple/item-page.component.ts b/src/app/+item-page/simple/item-page.component.ts index 9ed694e80e..64c03b3594 100644 --- a/src/app/+item-page/simple/item-page.component.ts +++ b/src/app/+item-page/simple/item-page.component.ts @@ -1,12 +1,15 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; -import { Observable } from 'rxjs/Observable'; -import { Item } from '../../core/shared/item.model'; +import { Observable } from 'rxjs/Observable'; import { ItemDataService } from '../../core/data/item-data.service'; import { RemoteData } from '../../core/data/remote-data'; import { Bitstream } from '../../core/shared/bitstream.model'; +import { Item } from '../../core/shared/item.model'; + +import { fadeInOut } from '../../shared/animations/fade'; + /** * This component renders a simple item page. * The route parameter 'id' is used to request the item it represents. @@ -16,7 +19,8 @@ import { Bitstream } from '../../core/shared/bitstream.model'; selector: 'ds-item-page', styleUrls: ['./item-page.component.scss'], templateUrl: './item-page.component.html', - changeDetection: ChangeDetectionStrategy.OnPush + changeDetection: ChangeDetectionStrategy.OnPush, + animations: [fadeInOut] }) export class ItemPageComponent implements OnInit { diff --git a/src/app/search/search-options.model.ts b/src/app/+search-page/search-options.model.ts similarity index 100% rename from src/app/search/search-options.model.ts rename to src/app/+search-page/search-options.model.ts diff --git a/src/app/search-page/search-page-routing.module.ts b/src/app/+search-page/search-page-routing.module.ts similarity index 82% rename from src/app/search-page/search-page-routing.module.ts rename to src/app/+search-page/search-page-routing.module.ts index 76b7d9ad0c..de2d64c6c9 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: 'search', component: SearchPageComponent } + { path: '', component: SearchPageComponent } ]) ] }) diff --git a/src/app/+search-page/search-page.component.html b/src/app/+search-page/search-page.component.html new file mode 100644 index 0000000000..d5e3a831eb --- /dev/null +++ b/src/app/+search-page/search-page.component.html @@ -0,0 +1,9 @@ +
+ + + +
diff --git a/src/app/search-page/search-page.component.scss b/src/app/+search-page/search-page.component.scss similarity index 100% rename from src/app/search-page/search-page.component.scss rename to src/app/+search-page/search-page.component.scss diff --git a/src/app/+search-page/search-page.component.spec.ts b/src/app/+search-page/search-page.component.spec.ts new file mode 100644 index 0000000000..a3e314db69 --- /dev/null +++ b/src/app/+search-page/search-page.component.spec.ts @@ -0,0 +1,110 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ActivatedRoute, Router } from '@angular/router'; +import { Observable } from 'rxjs/Observable'; +import { CommunityDataService } from '../core/data/community-data.service'; +import { SearchPageComponent } from './search-page.component'; +import { SearchService } from './search-service/search.service'; +import { Community } from '../core/shared/community.model'; +import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; +import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model'; + +describe('SearchPageComponent', () => { + let comp: SearchPageComponent; + let fixture: ComponentFixture; + let searchServiceObject: SearchService; + const mockResults = ['test', 'data']; + const searchServiceStub = { + search: () => mockResults + }; + const queryParam = 'test query'; + const scopeParam = '7669c72a-3f2a-451f-a3b9-9210e7a4c02f'; + const activatedRouteStub = { + queryParams: Observable.of({ + query: queryParam, + scope: scopeParam + }) + }; + const mockCommunityList = []; + const communityDataServiceStub = { + findAll: () => mockCommunityList, + findById: () => new Community() + }; + + class RouterStub { + navigateByUrl(url: string) { + return url; + } + } + + beforeEach(async(() => { + TestBed.configureTestingModule({ + // imports: [ SearchPageModule ], + declarations: [SearchPageComponent], + providers: [ + { provide: SearchService, useValue: searchServiceStub }, + { provide: ActivatedRoute, useValue: activatedRouteStub }, + { provide: CommunityDataService, useValue: communityDataServiceStub }, + { provide: Router, useClass: RouterStub } + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SearchPageComponent); + comp = fixture.componentInstance; // SearchPageComponent test instance + fixture.detectChanges(); + searchServiceObject = (comp as any).service; + }); + + it('should set the scope and query based on the route parameters', () => { + expect(comp.query).toBe(queryParam); + expect((comp as any).scope).toBe(scopeParam); + }); + + describe('when update search results is called', () => { + let pagination; + let sort; + beforeEach(() => { + pagination = Object.assign( + {}, + new PaginationComponentOptions(), + { + currentPage: 5, + pageSize: 15 + } + ); + sort = Object.assign({}, + new SortOptions(), + { + direction: SortDirection.Ascending, + field: 'test-field' + } + ); + }); + + it('should call the search function of the search service with the right parameters', () => { + spyOn(searchServiceObject, 'search').and.callThrough(); + + (comp as any).updateSearchResults({ + pagination: pagination, + sort: sort + }); + + expect(searchServiceObject.search).toHaveBeenCalledWith(queryParam, scopeParam, { + pagination: pagination, + sort: sort + }); + }); + + it('should update the results', () => { + spyOn(searchServiceObject, 'search').and.callThrough(); + + (comp as any).updateSearchResults({}); + + expect(comp.results as any).toBe(mockResults); + }); + + }); +}); diff --git a/src/app/search-page/search-page.component.ts b/src/app/+search-page/search-page.component.ts similarity index 80% rename from src/app/search-page/search-page.component.ts rename to src/app/+search-page/search-page.component.ts index a35f6f01a3..1592dc3c24 100644 --- a/src/app/search-page/search-page.component.ts +++ b/src/app/+search-page/search-page.component.ts @@ -1,12 +1,12 @@ -import { Component, OnInit, OnDestroy } from '@angular/core'; -import { SearchService } from '../search/search.service'; -import { ActivatedRoute, Router } from '@angular/router'; +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy } from '@angular/core'; +import { SearchService } from './search-service/search.service'; +import { ActivatedRoute } from '@angular/router'; import { RemoteData } from '../core/data/remote-data'; -import { SearchResult } from '../search/search-result.model'; +import { SearchResult } from './search-result.model'; import { DSpaceObject } from '../core/shared/dspace-object.model'; import { SortOptions } from '../core/cache/models/sort-options.model'; import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; -import { SearchOptions } from '../search/search-options.model'; +import { SearchOptions } from './search-options.model'; import { CommunityDataService } from '../core/data/community-data.service'; import { isNotEmpty } from '../shared/empty.util'; import { Community } from '../core/shared/community.model'; @@ -21,20 +21,25 @@ import { Community } from '../core/shared/community.model'; selector: 'ds-search-page', styleUrls: ['./search-page.component.scss'], templateUrl: './search-page.component.html', + changeDetection: ChangeDetectionStrategy.OnPush }) export class SearchPageComponent implements OnInit, OnDestroy { + private sub; - query: string; private scope: string; + + query: string; scopeObject: RemoteData; results: RemoteData>>; currentParams = {}; searchOptions: SearchOptions; scopeList: RemoteData; - constructor(private service: SearchService, - private route: ActivatedRoute, - private communityService: CommunityDataService,) { + constructor( + private service: SearchService, + private route: ActivatedRoute, + private communityService: CommunityDataService + ) { this.scopeList = communityService.findAll(); // Initial pagination config const pagination: PaginationComponentOptions = new PaginationComponentOptions(); @@ -53,8 +58,8 @@ export class SearchPageComponent implements OnInit, OnDestroy { this.currentParams = params; this.query = params.query || ''; this.scope = params.scope; - const page = +params.page || this.searchOptions.pagination.currentPage; - const pageSize = +params.pageSize || this.searchOptions.pagination.pageSize; + const page = +params.page || this.searchOptions.pagination.currentPage; + const pageSize = +params.pageSize || this.searchOptions.pagination.pageSize; const sortDirection = +params.sortDirection || this.searchOptions.sort.direction; const pagination = Object.assign({}, this.searchOptions.pagination, @@ -80,7 +85,6 @@ export class SearchPageComponent implements OnInit, OnDestroy { private updateSearchResults(searchOptions) { // Resolve search results this.results = this.service.search(this.query, this.scope, searchOptions); - } ngOnDestroy() { diff --git a/src/app/search-page/search-page.module.ts b/src/app/+search-page/search-page.module.ts similarity index 91% rename from src/app/search-page/search-page.module.ts rename to src/app/+search-page/search-page.module.ts index 67a88f579c..bfb7db89be 100644 --- a/src/app/search-page/search-page.module.ts +++ b/src/app/+search-page/search-page.module.ts @@ -7,11 +7,11 @@ import { TranslateModule } from '@ngx-translate/core'; import { SharedModule } from '../shared/shared.module'; import { SearchPageRoutingModule } from './search-page-routing.module'; import { SearchPageComponent } from './search-page.component'; -import { SearchResultsComponent } from './search-results/search-results.compontent'; -import { SearchModule } from '../search/search.module'; +import { SearchResultsComponent } from './search-results/search-results.component'; import { ItemSearchResultListElementComponent } from '../object-list/search-result-list-element/item-search-result/item-search-result-list-element.component'; import { CollectionSearchResultListElementComponent } from '../object-list/search-result-list-element/collection-search-result/collection-search-result-list-element.component'; import { CommunitySearchResultListElementComponent } from '../object-list/search-result-list-element/community-search-result/community-search-result-list-element.component'; +import { SearchService } from './search-service/search.service'; @NgModule({ imports: [ @@ -19,8 +19,7 @@ import { CommunitySearchResultListElementComponent } from '../object-list/search CommonModule, TranslateModule, RouterModule, - SharedModule, - SearchModule + SharedModule ], declarations: [ SearchPageComponent, @@ -29,6 +28,9 @@ import { CommunitySearchResultListElementComponent } from '../object-list/search CollectionSearchResultListElementComponent, CommunitySearchResultListElementComponent ], + providers: [ + SearchService + ], entryComponents: [ ItemSearchResultListElementComponent, CollectionSearchResultListElementComponent, diff --git a/src/app/search/search-result.model.ts b/src/app/+search-page/search-result.model.ts similarity index 100% rename from src/app/search/search-result.model.ts rename to src/app/+search-page/search-result.model.ts diff --git a/src/app/+search-page/search-results/search-results.component.html b/src/app/+search-page/search-results/search-results.component.html new file mode 100644 index 0000000000..08e63d598d --- /dev/null +++ b/src/app/+search-page/search-results/search-results.component.html @@ -0,0 +1,7 @@ +

{{ 'search.results.title' | translate }}

+ + \ No newline at end of file diff --git a/src/app/+search-page/search-results/search-results.component.spec.ts b/src/app/+search-page/search-results/search-results.component.spec.ts new file mode 100644 index 0000000000..4f299c5c50 --- /dev/null +++ b/src/app/+search-page/search-results/search-results.component.spec.ts @@ -0,0 +1,142 @@ +import { ComponentFixture, TestBed, async, tick, fakeAsync } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core'; +import { ResourceType } from '../../core/shared/resource-type'; +import { Community } from '../../core/shared/community.model'; +import { TranslateModule } from '@ngx-translate/core'; +import { SearchResultsComponent } from './search-results.component'; + +describe('SearchResultsComponent', () => { + let comp: SearchResultsComponent; + let fixture: ComponentFixture; + let heading: DebugElement; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot()], + declarations: [SearchResultsComponent], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SearchResultsComponent); + comp = fixture.componentInstance; // SearchFormComponent test instance + heading = fixture.debugElement.query(By.css('heading')); + }); + + it('should display heading when results are not empty', fakeAsync(() => { + (comp as any).searchResults = 'test'; + (comp as any).searchConfig = {pagination: ''}; + fixture.detectChanges(); + tick(); + expect(heading).toBeDefined(); + })); + + it('should not display heading when results is empty', () => { + expect(heading).toBeNull(); + }); +}); + +export const objects = [ + Object.assign(new Community(), { + handle: '10673/11', + logo: { + self: { + _isScalar: true, + value: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/10b636d0-7890-4968-bcd6-0d83bf4e2b42', + scheduler: null + } + }, + collections: { + self: { + _isScalar: true, + value: '1506937433727', + scheduler: null + } + }, + self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/communities/7669c72a-3f2a-451f-a3b9-9210e7a4c02f', + id: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f', + uuid: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f', + type: ResourceType.Community, + name: 'OR2017 - Demonstration', + metadata: [ + { + key: 'dc.description', + language: null, + value: '' + }, + { + key: 'dc.description.abstract', + language: null, + value: 'This is a test community to hold content for the OR2017 demostration' + }, + { + key: 'dc.description.tableofcontents', + language: null, + value: '' + }, + { + key: 'dc.rights', + language: null, + value: '' + }, + { + key: 'dc.title', + language: null, + value: 'OR2017 - Demonstration' + } + ] + }), + Object.assign(new Community(), + { + handle: '10673/1', + logo: { + self: { + _isScalar: true, + value: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/f446c17d-6d51-45ea-a610-d58a73642d40', + scheduler: null + } + }, + collections: { + self: { + _isScalar: true, + value: '1506937433727', + scheduler: null + } + }, + self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/communities/9076bd16-e69a-48d6-9e41-0238cb40d863', + id: '9076bd16-e69a-48d6-9e41-0238cb40d863', + uuid: '9076bd16-e69a-48d6-9e41-0238cb40d863', + type: ResourceType.Community, + name: 'Sample Community', + metadata: [ + { + key: 'dc.description', + language: null, + value: '

This is the introductory text for the Sample Community on the DSpace Demonstration Site. It is editable by System or Community Administrators (of this Community).

\r\n

DSpace Communities may contain one or more Sub-Communities or Collections (of Items).

\r\n

This particular Community has its own logo (the DuraSpace logo).

' + }, + { + key: 'dc.description.abstract', + language: null, + value: 'This is a sample top-level community' + }, + { + key: 'dc.description.tableofcontents', + language: null, + value: '

This is the news section for this Sample Community. System or Community Administrators (of this Community) can edit this News field.

' + }, + { + key: 'dc.rights', + language: null, + value: '

If this Community had special copyright text to display, it would be displayed here.

' + }, + { + key: 'dc.title', + language: null, + value: 'Sample Community' + } + ] + } + ) +]; diff --git a/src/app/search-page/search-results/search-results.compontent.ts b/src/app/+search-page/search-results/search-results.component.ts similarity index 82% rename from src/app/search-page/search-results/search-results.compontent.ts rename to src/app/+search-page/search-results/search-results.component.ts index 505a3eeae4..c645489694 100644 --- a/src/app/search-page/search-results/search-results.compontent.ts +++ b/src/app/+search-page/search-results/search-results.component.ts @@ -1,20 +1,18 @@ import { Component, Input } from '@angular/core'; import { RemoteData } from '../../core/data/remote-data'; -import { SearchResult } from '../../search/search-result.model'; +import { SearchResult } from '../search-result.model'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; -import { SearchOptions } from '../../search/search-options.model'; +import { SearchOptions } from '../search-options.model'; /** * This component renders a simple item page. * The route parameter 'id' is used to request the item it represents. * All fields of the item that should be displayed, are defined in its template. */ - @Component({ selector: 'ds-search-results', templateUrl: './search-results.component.html', }) - export class SearchResultsComponent { @Input() searchResults: RemoteData>>; @Input() searchConfig: SearchOptions; diff --git a/src/app/+search-page/search-service/facet-value.model.ts b/src/app/+search-page/search-service/facet-value.model.ts new file mode 100644 index 0000000000..a323970bc7 --- /dev/null +++ b/src/app/+search-page/search-service/facet-value.model.ts @@ -0,0 +1,7 @@ + +export class FacetValue { + + value: string; + count: number; + search: string; +} diff --git a/src/app/+search-page/search-service/filter-type.model.ts b/src/app/+search-page/search-service/filter-type.model.ts new file mode 100644 index 0000000000..fba0edfac4 --- /dev/null +++ b/src/app/+search-page/search-service/filter-type.model.ts @@ -0,0 +1,5 @@ +export enum FilterType { + text, + range, + hierarchy +} diff --git a/src/app/+search-page/search-service/search-filter-config.model.ts b/src/app/+search-page/search-service/search-filter-config.model.ts new file mode 100644 index 0000000000..2335e1bb25 --- /dev/null +++ b/src/app/+search-page/search-service/search-filter-config.model.ts @@ -0,0 +1,16 @@ +import { FilterType } from './filter-type.model'; + +export class SearchFilterConfig { + + name: string; + type: FilterType; + hasFacets: boolean; + isOpenByDefault: boolean; + /** + * Name of this configuration that can be used in a url + * @returns Parameter name + */ + get paramName(): string { + return 'f.' + this.name; + } +} diff --git a/src/app/search/search.service.ts b/src/app/+search-page/search-service/search.service.ts similarity index 53% rename from src/app/search/search.service.ts rename to src/app/+search-page/search-service/search.service.ts index 4236cad9ac..e2804960ef 100644 --- a/src/app/search/search.service.ts +++ b/src/app/+search-page/search-service/search.service.ts @@ -1,15 +1,18 @@ import { Injectable } from '@angular/core'; -import { RemoteData } from '../core/data/remote-data'; +import { RemoteData } from '../../core/data/remote-data'; import { Observable } from 'rxjs/Observable'; -import { SearchResult } from './search-result.model'; -import { ItemDataService } from '../core/data/item-data.service'; -import { PageInfo } from '../core/shared/page-info.model'; -import { DSpaceObject } from '../core/shared/dspace-object.model'; -import { SearchOptions } from './search-options.model'; -import { hasValue, isNotEmpty } from '../shared/empty.util'; -import { Metadatum } from '../core/shared/metadatum.model'; -import { Item } from '../core/shared/item.model'; -import { ItemSearchResult } from '../object-list/search-result-list-element/item-search-result/item-search-result.model'; +import { SearchResult } from '../search-result.model'; +import { ItemDataService } from '../../core/data/item-data.service'; +import { PageInfo } from '../../core/shared/page-info.model'; +import { DSpaceObject } from '../../core/shared/dspace-object.model'; +import { SearchOptions } from '../search-options.model'; +import { hasValue, isNotEmpty } from '../../shared/empty.util'; +import { Metadatum } from '../../core/shared/metadatum.model'; +import { Item } from '../../core/shared/item.model'; +import { ItemSearchResult } from '../../object-list/search-result-list-element/item-search-result/item-search-result.model'; +import { SearchFilterConfig } from './search-filter-config.model'; +import { FilterType } from './filter-type.model'; +import { FacetValue } from './facet-value.model'; function shuffle(array: any[]) { let i = 0; @@ -42,6 +45,37 @@ export class SearchService { 'The QSAR DataBank (QsarDB) repository', ); + config: SearchFilterConfig[] = [ + Object.assign(new SearchFilterConfig(), + { + name: 'scope', + type: FilterType.hierarchy, + hasFacets: true, + isOpenByDefault: true + }), + Object.assign(new SearchFilterConfig(), + { + name: 'author', + type: FilterType.text, + hasFacets: true, + isOpenByDefault: false + }), + Object.assign(new SearchFilterConfig(), + { + name: 'date', + type: FilterType.range, + hasFacets: true, + isOpenByDefault: false + }), + Object.assign(new SearchFilterConfig(), + { + name: 'subject', + type: FilterType.text, + hasFacets: false, + isOpenByDefault: false + }) + ]; + constructor(private itemDataService: ItemDataService) { } @@ -63,8 +97,7 @@ export class SearchService { if (isNotEmpty(searchOptions) && hasValue(searchOptions.sort.field)) { self += `&sortField=${searchOptions.sort.field}`; } - const requestPending = Observable.of(false); - const responsePending = Observable.of(false); + const errorMessage = Observable.of(undefined); const statusCode = Observable.of('200'); const returningPageInfo = new PageInfo(); @@ -84,8 +117,8 @@ export class SearchService { }); const pageInfo = itemsRD.pageInfo.map((info: PageInfo) => { - info.totalElements = info.totalElements > 20 ? 20 : info.totalElements; - return info; + const totalElements = info.totalElements > 20 ? 20 : info.totalElements; + return Object.assign({}, info, { totalElements: totalElements }); }); const payload = itemsRD.payload.map((items: Item[]) => { @@ -103,8 +136,8 @@ export class SearchService { return new RemoteData( Observable.of(self), - requestPending, - responsePending, + itemsRD.isRequestPending, + itemsRD.isResponsePending, itemsRD.hasSucceeded, errorMessage, statusCode, @@ -112,4 +145,51 @@ export class SearchService { payload ) } + + getConfig(): RemoteData { + const requestPending = Observable.of(false); + const responsePending = Observable.of(false); + const isSuccessful = Observable.of(true); + const errorMessage = Observable.of(undefined); + const statusCode = Observable.of('200'); + const returningPageInfo = Observable.of(new PageInfo()); + return new RemoteData( + Observable.of('https://dspace7.4science.it/dspace-spring-rest/api/search'), + requestPending, + responsePending, + isSuccessful, + errorMessage, + statusCode, + returningPageInfo, + Observable.of(this.config) + ); + } + + getFacetValuesFor(searchFilterConfigName: string): RemoteData { + const values: FacetValue[] = []; + for (let i = 0; i < 5; i++) { + const value = searchFilterConfigName + ' ' + (i + 1); + values.push({ + value: value, + count: Math.floor(Math.random() * 20) + 20 * (5 - i), // make sure first results have the highest (random) count + search: 'https://dspace7.4science.it/dspace-spring-rest/api/search?f.' + searchFilterConfigName + '=' + encodeURI(value) + }); + } + const requestPending = Observable.of(false); + const responsePending = Observable.of(false); + const isSuccessful = Observable.of(true); + const errorMessage = Observable.of(undefined); + const statusCode = Observable.of('200'); + const returningPageInfo = Observable.of(new PageInfo()); + return new RemoteData( + Observable.of('https://dspace7.4science.it/dspace-spring-rest/api/search'), + requestPending, + responsePending, + isSuccessful, + errorMessage, + statusCode, + returningPageInfo, + Observable.of(values) + ); + } } diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 8fd2a03f3d..b3b8eacfce 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -7,10 +7,11 @@ import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component'; imports: [ RouterModule.forRoot([ { path: '', redirectTo: '/home', pathMatch: 'full' }, - { path: 'home', loadChildren: './+home/home.module#HomeModule' }, + { path: 'home', loadChildren: './+home-page/home-page.module#HomePageModule' }, { path: 'communities', loadChildren: './+community-page/community-page.module#CommunityPageModule' }, { path: 'collections', loadChildren: './+collection-page/collection-page.module#CollectionPageModule' }, { path: 'items', loadChildren: './+item-page/item-page.module#ItemPageModule' }, + { path: 'search', loadChildren: './+search-page/search-page.module#SearchPageModule' }, { path: '**', pathMatch: 'full', component: PageNotFoundComponent }, ]) ], diff --git a/src/app/app.component.html b/src/app/app.component.html index a227b80ab0..2a621be2c4 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,13 +1,13 @@ -
-
- - -
-
- -
-
- - -
-
+
+
+ + +
+
+ +
+
+ + +
+
diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 01ad0691aa..14719ed266 100755 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,7 +1,6 @@ import { NgModule } from '@angular/core'; import { CommonModule, APP_BASE_HREF } from '@angular/common'; import { HttpModule } from '@angular/http'; -import { FormsModule } from '@angular/forms'; import { RouterModule } from '@angular/router'; import { EffectsModule } from '@ngrx/effects'; diff --git a/src/app/app.reducer.ts b/src/app/app.reducer.ts index 646d7a26b5..baa3250549 100644 --- a/src/app/app.reducer.ts +++ b/src/app/app.reducer.ts @@ -1,17 +1,17 @@ -import { ActionReducerMap } from '@ngrx/store'; -import * as fromRouter from '@ngrx/router-store'; - -import { headerReducer, HeaderState } from './header/header.reducer'; -import { hostWindowReducer, HostWindowState } from './shared/host-window.reducer'; - -export interface AppState { - router: fromRouter.RouterReducerState; - hostWindow: HostWindowState; - header: HeaderState; -} - -export const appReducers: ActionReducerMap = { - router: fromRouter.routerReducer, - hostWindow: hostWindowReducer, - header: headerReducer -}; +import { ActionReducerMap } from '@ngrx/store'; +import * as fromRouter from '@ngrx/router-store'; + +import { headerReducer, HeaderState } from './header/header.reducer'; +import { hostWindowReducer, HostWindowState } from './shared/host-window.reducer'; + +export interface AppState { + router: fromRouter.RouterReducerState; + hostWindow: HostWindowState; + header: HeaderState; +} + +export const appReducers: ActionReducerMap = { + router: fromRouter.routerReducer, + hostWindow: hostWindowReducer, + header: headerReducer +}; diff --git a/src/app/core/cache/builders/remote-data-build.service.ts b/src/app/core/cache/builders/remote-data-build.service.ts index 744ef7fb25..046250efda 100644 --- a/src/app/core/cache/builders/remote-data-build.service.ts +++ b/src/app/core/cache/builders/remote-data-build.service.ts @@ -49,12 +49,20 @@ export class RemoteDataBuildService { requestHrefObs.flatMap((requestHref) => this.responseCache.get(requestHref)).filter((entry) => hasValue(entry)) ); - const requestPending = requestObs.map((entry: RequestEntry) => entry.requestPending).distinctUntilChanged(); + const requestPending = requestObs + .map((entry: RequestEntry) => entry.requestPending) + .startWith(true) + .distinctUntilChanged(); - const responsePending = requestObs.map((entry: RequestEntry) => entry.responsePending).distinctUntilChanged(); + const responsePending = requestObs + .map((entry: RequestEntry) => entry.responsePending) + .startWith(false) + .distinctUntilChanged(); const isSuccessFul = responseCacheObs - .map((entry: ResponseCacheEntry) => entry.response.isSuccessful).distinctUntilChanged(); + .map((entry: ResponseCacheEntry) => entry.response.isSuccessful) + .startWith(false) + .distinctUntilChanged(); const errorMessage = responseCacheObs .filter((entry: ResponseCacheEntry) => !entry.response.isSuccessful) @@ -133,12 +141,20 @@ export class RemoteDataBuildService { const responseCacheObs = hrefObs.flatMap((href: string) => this.responseCache.get(href)) .filter((entry) => hasValue(entry)); - const requestPending = requestObs.map((entry: RequestEntry) => entry.requestPending).distinctUntilChanged(); + const requestPending = requestObs + .map((entry: RequestEntry) => entry.requestPending) + .startWith(true) + .distinctUntilChanged(); - const responsePending = requestObs.map((entry: RequestEntry) => entry.responsePending).distinctUntilChanged(); + const responsePending = requestObs + .map((entry: RequestEntry) => entry.responsePending) + .startWith(false) + .distinctUntilChanged(); const isSuccessFul = responseCacheObs - .map((entry: ResponseCacheEntry) => entry.response.isSuccessful).distinctUntilChanged(); + .map((entry: ResponseCacheEntry) => entry.response.isSuccessful) + .startWith(false) + .distinctUntilChanged(); const errorMessage = responseCacheObs .filter((entry: ResponseCacheEntry) => !entry.response.isSuccessful) diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index cabf452f01..77b2a44814 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -18,7 +18,7 @@ export abstract class DataService protected abstract rdbService: RemoteDataBuildService; protected abstract store: Store; protected abstract linkName: string; - protected abstract EnvConfig: GlobalConfig + protected abstract EnvConfig: GlobalConfig; constructor( private normalizedResourceType: GenericConstructor, diff --git a/src/app/object-list/object-list.component.html b/src/app/object-list/object-list.component.html index 0765e00476..84c800fef4 100644 --- a/src/app/object-list/object-list.component.html +++ b/src/app/object-list/object-list.component.html @@ -1,18 +1,20 @@ - -
    -
  • - -
  • -
- + +
    +
  • + +
  • +
+ +
diff --git a/src/app/shared/object-list/object-list.component.ts b/src/app/object-list/object-list.component.ts similarity index 78% rename from src/app/shared/object-list/object-list.component.ts rename to src/app/object-list/object-list.component.ts index c3018b957c..3a746c32e9 100644 --- a/src/app/shared/object-list/object-list.component.ts +++ b/src/app/object-list/object-list.component.ts @@ -1,29 +1,35 @@ import { + ChangeDetectionStrategy, + ChangeDetectorRef, Component, EventEmitter, Input, - ViewEncapsulation, - ChangeDetectionStrategy, + OnChanges, OnInit, - Output, SimpleChanges, OnChanges, ChangeDetectorRef, DoCheck + Output, + SimpleChanges, + ViewEncapsulation } from '@angular/core'; import { Observable } from 'rxjs/Observable'; -import { RemoteData } from '../../core/data/remote-data'; -import { PageInfo } from '../../core/shared/page-info.model'; +import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model'; -import { PaginationComponentOptions } from '../pagination/pagination-component-options.model'; +import { RemoteData } from '../core/data/remote-data'; +import { PageInfo } from '../core/shared/page-info.model'; +import { ListableObject } from '../object-list/listable-object/listable-object.model'; -import { SortOptions, SortDirection } from '../../core/cache/models/sort-options.model'; -import { ListableObject } from '../../object-list/listable-object/listable-object.model'; +import { fadeIn } from '../shared/animations/fade'; + +import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; @Component({ changeDetection: ChangeDetectionStrategy.Default, encapsulation: ViewEncapsulation.Emulated, selector: 'ds-object-list', - styleUrls: ['../../object-list/object-list.component.scss'], - templateUrl: '../../object-list/object-list.component.html' + styleUrls: ['./object-list.component.scss'], + templateUrl: './object-list.component.html', + animations: [fadeIn] }) export class ObjectListComponent implements OnChanges, OnInit { diff --git a/src/app/object-list/search-result-list-element/collection-search-result/collection-search-result.model.ts b/src/app/object-list/search-result-list-element/collection-search-result/collection-search-result.model.ts index 081111e96c..fa7945dedd 100644 --- a/src/app/object-list/search-result-list-element/collection-search-result/collection-search-result.model.ts +++ b/src/app/object-list/search-result-list-element/collection-search-result/collection-search-result.model.ts @@ -1,4 +1,4 @@ -import { SearchResult } from '../../../search/search-result.model'; +import { SearchResult } from '../../../+search-page/search-result.model'; import { Collection } from '../../../core/shared/collection.model'; export class CollectionSearchResult extends SearchResult { diff --git a/src/app/object-list/search-result-list-element/community-search-result/community-search-result.model.ts b/src/app/object-list/search-result-list-element/community-search-result/community-search-result.model.ts index 522b41700e..79ea34b6cd 100644 --- a/src/app/object-list/search-result-list-element/community-search-result/community-search-result.model.ts +++ b/src/app/object-list/search-result-list-element/community-search-result/community-search-result.model.ts @@ -1,4 +1,4 @@ -import { SearchResult } from '../../../search/search-result.model'; +import { SearchResult } from '../../../+search-page/search-result.model'; import { Community } from '../../../core/shared/community.model'; export class CommunitySearchResult extends SearchResult { diff --git a/src/app/object-list/search-result-list-element/item-search-result/item-search-result.model.ts b/src/app/object-list/search-result-list-element/item-search-result/item-search-result.model.ts index dc5d282c25..d9af3539a0 100644 --- a/src/app/object-list/search-result-list-element/item-search-result/item-search-result.model.ts +++ b/src/app/object-list/search-result-list-element/item-search-result/item-search-result.model.ts @@ -1,4 +1,4 @@ -import { SearchResult } from '../../../search/search-result.model'; +import { SearchResult } from '../../../+search-page/search-result.model'; import { Item } from '../../../core/shared/item.model'; export class ItemSearchResult extends SearchResult { diff --git a/src/app/object-list/search-result-list-element/search-result-list-element.component.ts b/src/app/object-list/search-result-list-element/search-result-list-element.component.ts index 3a2bf51c97..4119bc3c2e 100644 --- a/src/app/object-list/search-result-list-element/search-result-list-element.component.ts +++ b/src/app/object-list/search-result-list-element/search-result-list-element.component.ts @@ -2,7 +2,7 @@ import { Component, Inject } from '@angular/core'; import { ObjectListElementComponent } from '../object-list-element/object-list-element.component'; import { ListableObject } from '../listable-object/listable-object.model'; -import { SearchResult } from '../../search/search-result.model'; +import { SearchResult } from '../../+search-page/search-result.model'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { Metadatum } from '../../core/shared/metadatum.model'; import { isEmpty, hasNoValue } from '../../shared/empty.util'; diff --git a/src/app/search-page/search-page.component.html b/src/app/search-page/search-page.component.html deleted file mode 100644 index a8747a6cf9..0000000000 --- a/src/app/search-page/search-page.component.html +++ /dev/null @@ -1,4 +0,0 @@ -
- - -
diff --git a/src/app/search-page/search-results/search-results.component.html b/src/app/search-page/search-results/search-results.component.html deleted file mode 100644 index 7d769dbeb0..0000000000 --- a/src/app/search-page/search-results/search-results.component.html +++ /dev/null @@ -1,3 +0,0 @@ -

{{ 'search.results.title' | translate }}

- \ No newline at end of file diff --git a/src/app/search/search.module.ts b/src/app/search/search.module.ts deleted file mode 100644 index 5a7f919a56..0000000000 --- a/src/app/search/search.module.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CoreModule } from '../core/core.module'; -import { SearchService } from './search.service'; - -@NgModule({ - imports: [ - - ], - declarations: [ - ], - exports: [ - ], - providers: [ - SearchService - ] -}) -export class SearchModule { } diff --git a/src/app/shared/animations/fade.ts b/src/app/shared/animations/fade.ts new file mode 100644 index 0000000000..09a0be66ba --- /dev/null +++ b/src/app/shared/animations/fade.ts @@ -0,0 +1,24 @@ +import { animate, style, transition, trigger } from '@angular/animations'; + +const fadeEnter = transition(':enter', [ + style({ opacity: 0 }), + animate(300, style({ opacity: 1 })) +]); + +const fadeLeave = transition(':leave', [ + style({ opacity: 1 }), + animate(400, style({ opacity: 0 })) +]); + +export const fadeIn = trigger('fadeIn', [ + fadeEnter +]); + +export const fadeOut = trigger('fadeOut', [ + fadeLeave +]); + +export const fadeInOut = trigger('fadeInOut', [ + fadeEnter, + fadeLeave +]); diff --git a/src/app/shared/error/error.component.html b/src/app/shared/error/error.component.html new file mode 100644 index 0000000000..d41760e258 --- /dev/null +++ b/src/app/shared/error/error.component.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/src/app/shared/error/error.component.scss b/src/app/shared/error/error.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/shared/error/error.component.spec.ts b/src/app/shared/error/error.component.spec.ts new file mode 100644 index 0000000000..a0226f7f86 --- /dev/null +++ b/src/app/shared/error/error.component.spec.ts @@ -0,0 +1,58 @@ +import { ComponentFixture, TestBed, async } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { TranslateModule, TranslateLoader, TranslateService } from '@ngx-translate/core'; + +import { MockTranslateLoader } from '../testing/mock-translate-loader'; + +import { ErrorComponent } from './error.component'; + +describe('ErrorComponent (inline template)', () => { + + let comp: ErrorComponent; + let fixture: ComponentFixture; + let de: DebugElement; + let el: HTMLElement; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: MockTranslateLoader + } + }), + ], + declarations: [ ErrorComponent ], // declare the test component + providers: [ TranslateService ] + }).compileComponents(); // compile template and css + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ErrorComponent); + + comp = fixture.componentInstance; // ErrorComponent test instance + + // query for the message